GithubHelp home page GithubHelp logo

stimulus-use / stimulus-use Goto Github PK

View Code? Open in Web Editor NEW
1.4K 17.0 67.0 4.86 MB

A collection of composable behaviors for your Stimulus Controllers

Home Page: https://stimulus-use.github.io/stimulus-use

License: MIT License

JavaScript 41.58% HTML 24.70% TypeScript 33.70% CSS 0.03%
stimulusjs stimulus stimulus-controller hotwire stimulus-use turbo typescript

stimulus-use's Introduction

A collection of composable behaviors for your Stimulus Controllers

npm version minified + gzip size types included license Sauce test status


Stimulus Use Example


  • New lifecycle behaviors: adds new standard behaviors to your Stimulus controllers.
  • Composable: compose at will different behaviors in a single controller with mixins.
  • Modular: built as ES6 modules, just import what you need and tree shaking will remove the rest.
  • Typescript: Types available, better autocompletion.
  • Tiny: 3k gzip + tree shaking 🌳🌳🌳

Getting Started

Stimulus 3

If you want to use stimulus-use with Stimulus 3 you can use the version 0.50.0+. This and all future versions are designed to work with the @hotwired/stimulus npm package.

Note: If other packages still depend on the stimulus npm package you can safely keep that in your package.json, this won't break the stimulus-use compability.

Using npm

npm i stimulus-use @hotwired/stimulus

Using yarn

yarn add stimulus-use @hotwired/stimulus

Stimulus 2 and below

If you want to use stimulus-use with Stimulus 2 (or below) you can use version 0.41.0. This version is designed to work with the stimulus npm package.

Using npm

Using yarn

Documentation

We got you covered πŸ‘‰ stimulus-use.github.io/stimulus-use

Mixins

Observers

This set of mixins is built around the Observer APIs and custom events to enhance your controllers with new behaviors.

Mixin Description NEW Callbacks
useClickOutside Tracks the clicks outside of the element and adds a new lifecycle callback clickOutside. clickOutside
useHotkeys Registers hotkeys using the hotkeys-js library and binds them to handler methods
useHover Tracks the user's mouse movements over an element and adds mouseEnter and mouseLeave callbacks to your controller. mouseEnter mouseLeave
useIdle Tracks if the user is idle on your page and adds away and back callbacks to your controller. away
back
useIntersection Tracks the element's intersection and adds appear, disappear callbacks to your controller. appear
disappear
useMatchMedia Tracks if the window matches a media query string. is[Name], not[Name] and [name]Changed
useMutation Tracks mutations on an element, its attributes and/or subtree. Adds a mutate callback to your controller. mutate
useResize Tracks the element's size and adds a new lifecycle callback resize. resize
useTargetMutation Tracks when targets are added or removed from the controller's scope, or their contents changed. Adds [target]TargetAdded , [target]TargetRemoved and [target]TargetChanged callback to your controller for each specified target. [target]TargetAdded [target]TargetRemoved [target]TargetChanged
useVisibility
Tracks the page visibility and adds visible, invisible callbacks to your controller. visible
invisible
useWindowFocus
Tracks the window focus and adds focus, unfocus callbacks to your controller. focus
unfocus
useWindowResize Tracks the size of the window object and adds a new lifecycle callback windowResize. windowResize

Optimization

A set of mixin to optimize performances.

Mixin Description
useDebounce Adds the ability to specify an array "debounces" of functions to debounce.
useMemo Memoize expensive getters by mixing in useMemo and adding a static memos array.
useThrottle Adds the ability to specify an array "throttles" of functions to throttle.

Animation

A set of mixin and controllers to build animations.

Mixin Description
useTransition Mixin or controller to apply classes to various stages of an element's transition.

Application

Mixin Description
useApplication, ApplicationController supercharged controller for your application.
useDispatch Adds a dispatch helper function to emit custom events. Useful to communicate between different controllers.
useMeta Adds getters to easily access meta values.

Extend or compose

Stimulus-use can be used in two ways: composing with mixins or extending built-in controllers

Composing with mixins

This is the prefered approach as it bring the most flexibility. Simply import a mixin and apply it in the connect or initialize to adds new behaviors to you controller. You can combine several mixins within the same controller.

import { Controller } from '@hotwired/stimulus'
import { useIntersection, useResize } from 'stimulus-use'

export default class extends Controller {
  connect() {
    useIntersection(this)
    useResize(this)
  }

  appear(entry) {
    // triggered when the element appears within the viewport
  }

  resize({ height, width }) {
    // trigered when the element is resized
  }
}

Extending built-in controllers

You can create your Stimulus controller from a pre-built Stimulus-use controller which offers the new behavior you're looking for. This method works perfectly when you only need a single behavior for your controller.

import { IntersectionController } from 'stimulus-use'

export default class extends IntersectionController {
  appear(entry) {
    // triggered when the element appears within the viewport
  }
}

Development

  • Fork the project locally
  • yarn install
  • yarn start - to run the local dev server with examples
  • yarn test - to run the unit tests
  • yarn lint - to run the linter with ESLint
  • yarn format - to format changes with Prettier
  • yarn build - to bundle the app into static files for production

Contributors ✨

Made with ❀️ by @adrienpoly, @marcoroth and all these wonderful contributors (emoji key):

Marco Roth
Marco Roth

πŸš‡ πŸ’» πŸ‘€ πŸ›
Philipp Daun
Philipp Daun

πŸ›
M. E. Patterson
M. E. Patterson

πŸ›
Jonathan Sundqvist
Jonathan Sundqvist

πŸ“–
Rui Freitas
Rui Freitas

πŸ“–
Nicolas Filzi
Nicolas Filzi

πŸ“–
Benjamin Darcet
Benjamin Darcet

πŸ“–
juancarlosasensio
juancarlosasensio

πŸ“–
lidqqq
lidqqq

πŸš‡ πŸ›
Julian Rubisch
Julian Rubisch

πŸ’» πŸ‘€
Takuya Fukuju
Takuya Fukuju

πŸ“–
Justin Coyne
Justin Coyne

πŸ“–
Asger Behncke Jacobsen
Asger Behncke Jacobsen

πŸ“–
Dan Callaghan
Dan Callaghan

πŸ“–
Konnor Rogers
Konnor Rogers

πŸ› πŸ’»
Francisco Presencia
Francisco Presencia

πŸ“–
Takayuki Shimada
Takayuki Shimada

πŸ›
Dylan Clarke
Dylan Clarke

πŸ’» πŸ“–
Martin Tomov
Martin Tomov

πŸ“–
Ryan Weaver
Ryan Weaver

πŸ“– πŸ›
Adrien S
Adrien S

πŸ›
Felix Albroscheit
Felix Albroscheit

πŸ›
Guillaume Briday
Guillaume Briday

πŸ’»
craisp
craisp

πŸ› πŸ’»
Gabriel
Gabriel

πŸ› πŸ’»
Donnie Flood
Donnie Flood

πŸ’»
Γ“scar Carretero
Γ“scar Carretero

πŸ‘€ πŸ›
Ikko Ashimine
Ikko Ashimine

πŸ“–
Michael Coyne
Michael Coyne

πŸ›
Ollie Harridge
Ollie Harridge

πŸ“–
Leon Vogt
Leon Vogt

πŸš‡ πŸ’»
Thomas KΓΆnig
Thomas KΓΆnig

πŸ“–
Scott
Scott

πŸ›
Daniel Rikowski
Daniel Rikowski

πŸ›
Marc KΓΆhlbrugge
Marc KΓΆhlbrugge

πŸ€”
Leon Vogt
Leon Vogt

πŸ“–
Ted H. Tran
Ted H. Tran

πŸ“–

This project follows the all-contributors specification. Contributions of any kind welcome!

Acknowledgments

Continuous integration and cross browser testing is generously provided Sauce Labs.

Testing Powered By SauceLabs

stimulus-use's People

Contributors

achmiral avatar adrienpoly avatar allcontributors[bot] avatar asgerb avatar barnabed avatar bdarcet avatar dependabot[bot] avatar eltociear avatar floodfx avatar franciscop avatar gabriel-curtino avatar hvt avatar intrepidd avatar jcoyne avatar jonathan-s avatar julianrubisch avatar konnorrogers avatar leevigraham avatar leonvogt avatar marcoroth avatar mjc-gh avatar mtomov avatar nfilzi avatar ollietb avatar omarluq avatar rodloboz avatar sub-xaero avatar tsmd avatar vafilor avatar weaverryan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stimulus-use's Issues

PR Feedback?

Hey there -

Thanks for the library. I submitted a PR #205 a while back. I didn't see any details on how you would like to take contributions if this is insufficient. LMK

umd format is not working in browser with umd format 'stimulus'

Hi πŸ¦–

I found an error when I try to use this package in browser with umd format.

Repl code is below.

<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/[email protected]/dist/stimulus.umd.js" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/[email protected]/dist/index.umd.js" crossorigin="anonymous"></script>
    <script>console.log(window.stimulusUse)</script>
</head>
<body>
</body>
</html>

I've tried to investigate, then found that stimulus umd js expose the stimulus object to 'window.Stimulus', but stimulus-use umd js try to use window.stimulus to invoke factory method.
https://unpkg.com/[email protected]/dist/index.umd.js#:~:text=e.stimulus

That's why undefined.Controller cause a Error.

γ‚Ήγ‚―γƒͺγƒΌγƒ³γ‚·γƒ§γƒƒγƒˆ 2020-09-02 17 55 49

γ‚Ήγ‚―γƒͺγƒΌγƒ³γ‚·γƒ§γƒƒγƒˆ 2020-09-02 17 56 01

Is there anything we can do to resolve that? 🌡

`useLazyLoad`: Undocumented behavior

Hi again!

I was going through the source tonight and noticed useLazyLoad. It's not documented or in the playground. Is that simply accidental? Is it functional and usable?

Thanks!

isElementInViewport might be too strict

While using useClickOutside I ran into a bug where it was firing on the majority of browsers/devices, however failing to fire on some mobile devices (e.g. iPhone 8 iOS).

After debugging the issue for a while I figured out the issue: isElementInViewport was checking that the entirety of the target element fell within the viewport.

For the moment I'm just going to disable the onlyVisible option, but it might be an idea to make the method a bit looser i.e. if any part of the element is within the viewport.

`useClickOutside`: Documentation Maybe Unclear?

Hi there, maybe I'm doing something wrong but when I try to use the event example in the docs here, I can't seem to get it to work. I've changed modal to the name of my controller and tried giving it a custom prefix. Below is my implementation in my ERB template:

data-action="header-menu:click:outside->header-menu#close"

And my controller:

// header_menu_controller.js
import { Controller } from 'stimulus';
import { useClickOutside } from 'stimulus-use';

export default class extends Controller {
    connect() {
        useClickOutside(this)
    }

    expandMobileMenu() {
        const menu = this.element.querySelector("#mobileMenu")
        const menuOpen = this.element.querySelector("#mobileMenuOpen")
        const menuClose = this.element.querySelector("#mobileMenuClose")
        if(menu.classList.contains("hidden")) {
            menu.classList.remove("hidden")
            menuOpen.classList.add("hidden")
            menuClose.classList.remove("hidden")
        } else {
            menu.classList.add("hidden")
            menuOpen.classList.remove("hidden")
            menuClose.classList.add("hidden")
        }
    }

    expandUserMenu() {
        console.log("user menu");
    }

    clickOutside(event) {
        console.log('close?');
        console.log(this);
        console.log(event);
    }

    close(event) {
        event.preventDefault();
        console.log('close');
        console.log(event)
        console.log(this)
    }
}

Currently only the clickOutside event gets called. When I remove it nothing happens. Other aspects of the controller using default Stimulus work fine, it just seems to be this event based useClickOutside that isn't so far.

Am I just missing something, or is something broken?

EDIT:

I am using the navbar example as my base code to test out Stimulus as seen here: https://tailwindui.com/components/application-ui/navigation/navbars#component-70a9bdf83ef2c8568c5cddf6c39c2331.

`useTransition`: How to implement?

Hello there!

I am very interested in this useTransition component.

But there seems to be no documentation on that.

Can someone give me a quick explanation to jump into this?

`useResize`: ResizeObserver loop completed with undelivered notifications.

Hi Guys,

simple use of useResize shows "ResizeObserver loop completed with undelivered notifications." error in browser console.

import { Controller } from 'stimulus'
import { useResize } from 'stimulus-use'

export default class extends Controller {
  static targets = ['width']

  connect() {
    useResize(this)
  }

  resize({ width }) {
    this.element.style.height = width + 'px'
  }
}

Any suggestions to avoid such message?

Thx!

[bug] Duplication name properties are never called in controller

Hi πŸŽƒ

I found a bug that duplication name properties are never called in controller object because Object.assign break original object.

For example, readme's sample code is below.

...
  connect() {
    useIntersection(this)
    useResize(this)
  }
...

And, we change some code in stimulus-use codes.

// src/use-resize/use-resize.ts
    unObserve() {
      console.log('ResizeObserver: unObserve')
      this.observer.unobserve(targetElement)
    },
// src/use-intersection/use-intersection.ts
    unObserve() {
      console.log('IntersectionObserver: unObserve')
      this.observer.unobserve(targetElement)
    },

Next, remove element which is connected with useIntersection and useResize mixins function, then unObserve function is called twice which is connected later(in this code, useResize is connected later).

γ‚Ήγ‚―γƒͺγƒΌγƒ³γ‚·γƒ§γƒƒγƒˆ 2020-09-04 9 22 17

This behavior have a risk of memory leak because call back function would be never cleared.

One way we have to fix this problem is to name methods by using a Symbol object. Symbol is not conflict its same name that's why we can use same name in mixin functions between mixins functions each other.

Or, we don't merge observe mixin functions to original controller object.

However, this procedure which add mixin function to original controller object is written in betterstimulus, so I'm confused this approach are well known in stimulus community?

What do you think about it ? πŸ‘€

useClickOutside Event not working

When i want to use the event:

<div class="mt-5 flex-1 h-0 overflow-y-auto" data-action="rock-sidebar:click:outside->rock-sidebar#close">
    <nav class="px-2">
        {{ knp_menu_render('rock_core.dashboard_navigation', {
            'template': '@RockCore/menu/dashboard_navigation.html.twig',
            'allow_safe_labels': true,
            'currentClass' : 'bg-gray-700',
        }) }}
    </nav>
</div>

to call:

close(event) {
    event.preventDefault()
    this.toggle()
}

Nothing happens.

My controller is set up correctly and a direct click works on this element with the same method #close.
Just the rock-sidebar:click:outside->rock-sidebar#close event don't work as expected.

Did I something wrong or is this a bug - or was there breaking changes?

`useTransition` with multiple targets within a controller

It seems the current architecture of userTransition doesn't support multiple animated elements within a controller.

Was trying to implement Tailwind UI's modal component, which animates the backdrop separately from the modal dialog. However calling this.leave() only animates the last element passed to useTransition. In my case the dialog:

// "controllers/modal_controller.js"
import { Controller } from "stimulus"
import { useTransition } from 'stimulus-use';

export default class extends Controller {
  static targets = ['backdrop', 'dialog']
  connect() {
    useTransition(this, {
      element: this.backdropTarget,
      enterActive: 'ease-out duration-300',
      enterFrom: 'opacity-0',
      enterTo: 'opacity-100',
      leaveActive: 'ease-in duration-200',
      leaveFrom: 'opacity-100',
      leaveTo: 'opacity-0',
      hiddenClass: 'hidden'
    });

   // Only this animation is ran:
    useTransition(this, {
      element: this.dialogTarget,
      enterActive: 'ease-out duration-300',
      enterFrom: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95',
      enterTo: 'opacity-100 translate-y-0 sm:scale-100',
      leaveActive: 'ease-in duration-200',
      leaveFrom: 'opacity-100 translate-y-0 sm:scale-100',
      leaveTo: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95',
      hiddenClass: 'hidden'
    });

    this.open();
  }

  close() {
    this.leave()
  }

  open() {
    this.enter();
  }
}

Was wondering whether it's a use case useTransition would want to support in the future or whether it's intended to be used with just one element per controller?

Introduce useRequestIdleCallback optimization hook

Why not introduce useRequestIdleCallback to use window.requestIdleCallback() easily in controllers?

Queues a function to be called during a browser's idle periods. This enables developers to perform background and low priority work on the main event loop.

Can I use requestIdleCallback

WIP hook example in JavaScript
/**
 * @typedef {import('@hotwired/stimulus').Controller} Controller
 *
 * @typedef {Object} HookOptions
 * @property {number} [maxWait]
 */

const defaultOptions = {
  maxWait: undefined,
};

/**
 * @param {Controller} controller
 * @param {HookOptions} options
 * @returns {[function(): void, function(): void]}
 */
export const useRequestIdleCallback = (controller, options) => {
  const {
    maxWait,
  } = { ...defaultOptions, ...options };

  const idleCallbackIds = [];

  const idling = (fn, timeout) => {
    if (requestIdleCallback) {
      return function (...args) {
        const idleCallbackId = requestIdleCallback(deadline => fn.call(this, ...args, deadline), { timeout });

        idleCallbackIds.push(idleCallbackId);
      }
    }

    const start = Date.now();

    return function (...args) {
      const timeoutId = setTimeout(() => {
        fn.call(this, ...args, { didTimeout: false, timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) });
      }, 1);

      idleCallbackIds.push(timeoutId);
    };
  };

  const cancelIdling = id =>
    (cancelIdleCallback || clearTimeout)(id);

  const observe = () => {
    controller.constructor.idles?.forEach(func => {
      if ('string' === typeof func) {
        controller[func] = idling(controller[func].bind(controller), maxWait);
      }

      if ('object' === typeof func) {
        const { name, maxWait } = { ...func, ...options };
        if (!name) {
          return;
        }

        controller[name] = idling(controller[name].bind(controller), maxWait);
      }
    });
  };

  const unobserve = () => {
    idleCallbackIds.forEach(id => cancelIdling(id));
  };

  const controllerDisconnect = controller.disconnect.bind(controller);

  Object.assign(controller, {
    disconnect() {
      unobserve();
      controllerDisconnect();
    },
  });

  observe();

  return [observe, unobserve];
}

Usage example:

<a data-controller="test"
  data-action="test#foo">
  Invoke using useRequestIdleCallback
</a>
// test_controller.js
import { Controller } from '@hotwired/stimulus';
import { useRequestIdleCallback } from './hooks/useRequestIdleCallback';

export default class extends Controller {
  static idles = ['foo'];

  connect() {
    useRequestIdleCallback(this);
  }

  foo(e, deadline) {
    console.log({ e, deadline });
  }
}

`useTransition`: out of sync when used multiple times quickly

What would be the proper way to handle the situation when leave/enter is called multiple times in a short time window that causes element being out of state? Element is hidden instead of being shown.

Is there a lock or promise cancellation method so I can be sure that final state of an element is correct?

`useIntersection` for multiple elements not calls `disappear`

Controller example:

import { Controller } from '@hotwired/stimulus';
import { useIntersection } from 'stimulus-use';

class ListController extends Controller {
  static targets = ['item'];

  connect() {
    for (const item of this.itemTargets) {
      useIntersection(this, { element: item, root: this.element });
    }
  }

  appear() {
    console.log('βœ… appear');
  }

  disappear() {
    console.log('❌ disappear');
  }
}

Example: https://stackblitz.com/edit/js-1pzvcb?file=index.js

screenshort.mov

`useTransition`: options being applied to a different controller

I'm currently using hotwire (turbo and stimulus). I'm using useTransition with the default options to show/hide a dropdown as part of the page layout and it has its own controller. Then I have an element where I transition between 2 states so I used useTransition with options hiddenClass: false and removeToClasses: false and this is in its own controller as well.

The problem I'm having is when I navigate away from the page with the element that switches states, my dropdown in the page layout just shows up by default. It seems that the hidden class isn't being applied to it anymore and the toClasses are being retained as well. This made me think that the options I used in my state switching element gets somehow applied to the dropdown in my page layout.

When I explicitly specified those options in my dropdown, it seems to have solved the problem.

Hopefully you'd be able to understand my description of the problem. Thanks!

Add powermoves to stimulus-use

Powermoves allow you to access controller functions as element properties, "powermove" being coined by this blog:
https://dev.to/leastbad/the-best-one-line-stimulus-power-move-2o90

Not sure how much this breaks Stimulus's semantics on how controllers should work and communicate. But If you think of a controller added to an element, as an enclosed component. This will make more sense.
You can use this to register certain attributes (& maybe overwrite default html attributes πŸ€”) to the element which can be called from anywhere.

Made a rough concept, will be willing to share if you agree that this is cool.

  1. In this example it may not make sense to use such functionality seeing as I could also just implement the logic on the left from within the AjaxFormController on the right. But does show roughly what is possible.
    image
  2. In this example you can see where this may become powerful. Allowing other stimulus controllers, and maybe non-stimulus logic to call functions on the Dom defined in a controller
    image

image

`useDispatch`: deprecate mixin

Since Stimulus now got a dispatch() function built-in (see: hotwired/stimulus#302) we should think about deprecating (and later removing) the useDispatch() mixin from Stimulus Use.

While it's still handy to have it included, so older apps can also use it, we should go through a deprecation cycle and start printing deprecation warnings to people still using it and let them know how they can migrate it.

Follow-up of #174

useTransition: "enterTo" remains during "leave" (and vice versa on "enter")

Hi there!

Thanks for the wonderful library - i'm building a tutorial with that uses it right now - it's awesome!

I'm testing out the beta transitions. I'm not using Tailwind in this app, so the enter-leave classes are not the same as the sandbox. I've noticed a slight issue. For example, suppose we have:

    connect() {
        useClickOutside(this);
        useDebounce(this);
        useTransition(this, {
            element: this.resultTarget,
            enter: 'transition-fade-enter',
            enterActive: 'transition-fade-enter-active',
            enterTo: 'transition-fade-enter-to',
            leave: 'transition-fade-leave',
            leaveActive: 'transition-fade-leave-active',
            leaveTo: 'transition-fade-leave-active-to',
        });
    }

The enter() works great, the element looks like this after:

<div
    class="transition-fade-enter-to"
    data-search-preview-target="result"
> ....</div>

However, when I leave(), the enterTo class (which is transition-fade-enter-to) remains on the element permanently. For example, once leave() finishes, the HTML looks like this:

<div
    class="transition-fade-enter-to transition-fade-leave-active-to hidden"
    data-search-preview-target="result"
> ....</div>

Basically, both the enterTo and leaveTo classes are "stuck" on there permanently. This kills all future transitions because, for example, if transition-fade-enter-to has opacity: 1 that overrides the opacity 0 set by transition-fade-leave-active-to.

I think the solution is "simply" to, on leave, remove the enterTo class if it's there. And on enter, remove the leaveTo class if it is there.

Let me know if that makes sense :).

Cheers!

unmet peer dependency "stimulus@>=3"

Here is my package.json

"dependencies": {
"@hotwired/stimulus": "3.0.1",
...

but installing stimulus-use is returning the following error message:
warning " > [email protected]" has unmet peer dependency "stimulus@>=3".

I also try by adding stimulus-use using yarn add stimulus-use@beta but that does not change anything.

Purging the node_modules folder does not change anything either.

useClickOutside seems to prevent `this` from resolving controller in a disconnect

Without useClickOutside (expected behavior)

import { Controller } from "stimulus";
import { useClickOutside } from "stimulus-use";

export default class extends Controller {
  initialize() {}

  connect() {
    super.connect();

    console.log(this);
    console.log(this);
  }

  disconnect() {
    console.log(this);
  }
}

Screen Shot 2020-08-26 at 1 21 11 PM
(the first 2 log messages are the two from connect, the 3rd one is when you navigate to another page and it disconnects)

With useClickOutside (unexpected behavior)

import { Controller } from "stimulus";
import { useClickOutside } from "stimulus-use";

export default class extends Controller {
  initialize() {}

  connect() {
    super.connect();

    console.log(this);
    useClickOutside(this);
    console.log(this);
  }

  disconnect() {
    console.log(this);
  }
}

Screen Shot 2020-08-26 at 1 23 21 PM
(notice the 3rd one is undefined when you navigate away and it runs the disconnect)

Is there something I'm doing wrong? Or should I not be expecting to have this resolve to the controller in a disconnect after using this?

useTransition: Tweak ideas from usage

Hi!

I was just playing with the latest useTransition on 0.24 beta - it's working beautifully! I had a few notes/ideas from a usability perspective:

  1. The tripped over myself a bunch getting the option & class names right. For my example (not tailwind), I was using:
useTransition(this, {
    enter: 'transition-fade-enter',
    enterActive: 'transition-fade-enter-active',
    enterTo: 'transition-fade-enter-to',
    leave: 'transition-fade-leave',
    leaveActive: 'transition-fade-leave-active',
    leaveTo: 'transition-fade-leave-to',
});

I'd like to propose 2 changes: (a) we (in the docs) show enterActive and leaveActive first. That's the class that establishes the transition duration and it feels logically like we should always show it first. And (b) I'd like to rename enter to enterFrom. Vue just changed from using v-enter in Vue 2 to v-enter-from in Vue 3. Having a enterTo and enterFrom helps identify that these are the two classes you use to apply your styles from one state to the other (e.g. opacity 0 to opacity 1).

My example would then look like:

useTransition(this, {
    enterActive: 'transition-fade-enter-active',
    enterFrom: 'transition-fade-enter-from',
    enterTo: 'transition-fade-enter-to',
    leaveActive: 'transition-fade-leave-active',
    leaveFrom: 'transition-fade-leave-from',
    leaveTo: 'transition-fade-leave-to',
});
  1. A console.log() snuck into the code ;) - https://github.com/stimulus-use/stimulus-use/blob/master/src/use-transition/use-transition.ts#L79

That's all! Thank you!

`useClickOutside`: Unexpected token 'export'

I just upgraded stimulus-use in an application. Now I get the following error in my Jest tests:

  /app/node_modules/stimulus-use/dist/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){export { useIntersection, IntersectionController } from './use-intersection/index';
...                          
    > 4 | import { useClickOutside } from "stimulus-use"

The issue seems to have been introduced in v0.25.0. The latest version before that one works. I am not familiar enough with all these JS magic happening. For me the diff between these versions does not seem to contain anything suspicious or an intended change of behaviour.

v0.25.0: Installation fails

Calling a "npm install stimulus-use" fails when trying to install latest version 0.25.0. This seems to be related to "hotkeys-js" which is declared a peerDependency. Npm output:

npm WARN ERESOLVE overriding peer dependency
npm WARN Found: hotkeys-js@undefined
npm WARN node_modules/hotkeys-js
npm WARN 
npm WARN Could not resolve dependency:
npm WARN peer hotkeys-js@">3" from [email protected]
npm WARN node_modules/stimulus-use
npm WARN   stimulus-use@"*" from the root project
npm ERR! code ETARGET
npm ERR! notarget No matching version found for hotkeys-js@>3.
npm ERR! notarget In most cases you or one of your dependencies are requesting
npm ERR! notarget a package version that doesn't exist.

As one would expect, regular install of <= 0.24 works without troubles.

Node version: 14.16.0
Npm version: 7.11.2

`useClickOutside`: [bug?] multiple instances of controller

Hi!

I have 2 instances of widget controller called search-form. One instance is used for mobile view and the other for larger screens. Both of them are placed in a totally different areas of a page (no parent-child relation). Both of them are implemented as:

  connect() {
    useClickOutside(this);
  }

  clickOutside(event) {
    event.preventDefault();
    this._setState(false); // hide page backdrop, suggestions popup and so on
  }

Im observing the following unexpected behaviour where mobile instance of controller is capturing outside-clicks and triggers clickOutside method of all controller instances . I'd expect it will call this method for its own instance, not all of them.

Is this a bug or expected behaviour?

Use Object.defineProperty to copy getters onto controller

useApplication adds two getters to the controller, isPreview and csrfToken. However, it uses Object.assign to do that, which won't copy them as getters but as primitives, reflecting the value at the point of assignment.

Using Object.defineProperty will copy them over as getters. See this StackOverflow comment for details.

Simplified example:

const instances = new Set()

export default (controller) => {

  Object.defineProperty(controller, 'instances', {
    get() {
      return [...instances]
    }
  })

  Object.assign(controller, {
    connect() {
      instances.add(this)
    },
    disconnect() {
      instances.delete(this)
    }
  })
}

useIntersection is unable to dispatch events

Hello,

In a dedicated controller, I am using

initialize() {
  console.log('Appearable', this.element);
  useIntersection(this, {
    dispatchEvent: true
  });
}

In a parent controller frontend--search-loader, I defined:

lastRow.dataset.action = "appear->frontend--search-loader#fetchNextPage";

lastRow is the last item of a visible list, but this item is never visible when loading it in DOM.
The purpose is to load the rest of the list when reaching this element.

The appear event is never triggered by stimulus-use.

This is working properly if I dispatch the event by myself in the appear method.

appear() {
  console.log('Appear', this.element);
  this.dispatchEvent(this.element, 'appear');// Custom method to dispatch custom event
}

I may not know how to use properly this but I read all the manuals with no success...

constructor.bless is not a function on the playground - my mistake?

Hi!

I love the lib - thanks so much for it ❀️

I wanted to demo the playground, so I followed the docs to set it up:

git clone [email protected]:stimulus-use/stimulus-use.git
cd stimulus-use
yarn install
yarn build
yarn start

No problems with any of that :). But when I load the playground, I see:

Screen Shot 2021-02-25 at 8 03 45 PM

Am I doing something wrong? Or is there an issue with the playground?

Thanks!

isPreview Error with useApplication

Hi,

Using useApplication like this

import { Controller } from 'stimulus';
import { useApplication } from 'stimulus-use'

export default class extends Controller {
  connect() {
    useApplication(this);
  }
}

I get this error when I change routes using Turbo...

Error connecting controller

TypeError: Cannot redefine property: isPreview
    at Function.defineProperty (<anonymous>)
    at useApplication (use-application.js:4)
    at extended.connect (audio_controller.js:13)
    at Context.push../node_modules/@stimulus/core/dist/context.js.Context.connect (context.js:21)
    at Module.push../node_modules/@stimulus/core/dist/module.js.Module.connectContextForScope (module.js:34)
    at Router.push../node_modules/@stimulus/core/dist/router.js.Router.scopeConnected (router.js:92)
    at ScopeObserver.push../node_modules/@stimulus/core/dist/scope_observer.js.ScopeObserver.elementMatchedValue (scope_observer.js:41)
    at ValueListObserver.push../node_modules/@stimulus/mutation-observers/dist/value_list_observer.js.ValueListObserver.tokenMatched (value_list_observer.js:44)
    at TokenListObserver.push../node_modules/@stimulus/mutation-observers/dist/token_list_observer.js.TokenListObserver.tokenMatched (token_list_observer.js:60)
    at token_list_observer.js:53

I'm using

  • Rails 6.14
  • Webpacker 6 beta 7
  • Turbo Rails 0.6.0

With javascript packages

  • "@hotwired/turbo-rails": "^7.0.0-rc.1"
  • "stimulus": "^2.0.0"
  • "stimulus-use": "^0.25.4"

All assets are being generated with Webpacker

my application.js is

const images = require.context('../images', true)
const imagePath = (name) => images(name, true)

import 'core-js/stable'
import 'regenerator-runtime/runtime'
import '@hotwired/turbo-rails'
import 'controllers'
import 'channels'

and index.js is

import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"

const application = Application.start()
const context = require.context(".", true, /\.js$/)
application.load(definitionsFromContext(context))

`useTransition`: DOMException: Failed to execute 'add' on 'DOMTokenList': the token provided must not be empty.

Hi all,

I got an error when using useTransition, below is error's screenshots and codes, pls help me.

GfreshDirectory

GfreshDirectory

package.json

 "dependencies": {
    "@rails/actioncable": "^6.0.0",
    "@rails/activestorage": "^6.0.0",
    "@rails/ujs": "^6.0.0",
    "@rails/webpacker": "5.4.0",
    "@tailwindcss/forms": "^0.3.2",
    "autoprefixer": "^9",
    "choices.js": "^9.0.1",
    "flag-icon-css": "^3.5.0",
    "intersection-observer": "^0.12.0",
    "postcss": "^7",
    "stimulus": "^2.0.0",
    "stimulus-use": "^0.23.0",
    "tailwindcss": "npm:@tailwindcss/postcss7-compat",
    "turbolinks": "^5.2.0",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.12"
  },

dropdown_controller.js

import { Controller } from "stimulus";
import { useClickOutside, useTransition } from "stimulus-use";

export default class extends Controller {
  static targets = ["content"];

  connect() {
    useTransition(this, {
      element: this.contentTarget,
      enterActive: "fade-enter-active",
      enterFrom: "fade-enter-from",
      enterTo: "fade-enter-to",
      leaveActive: "fade-leave-active",
      leaveFrom: "fade-leave-from",
      leaveTo: "fade-leave-to",
    });
  }

  toggle() {
    console.log('toggle: ');
    this.enter();
  }
}

views

<div class="relative" data-controller="dropdown">
  <div>
    <button data-action="click->dropdown#toggle" type="button" class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
      Dropdown
    </button>
  </div>
  <div class="origin-top-right absolute left-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5" data-target="dropdown.content">
    <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Your Profile</a>
    <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Settings</a>
    <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Sign out</a>
  </div>
</div>

`useTransition` & others: use the Values API?

Hi again!

I was trying to use the transition behavior's controller class directly today without writing any custom JavaScript. It is, of course, totally possible to do this :). Except that the options - e.g. hiddenClass or transitioned don't appear to have a way to set them via HTML attributes.

Could we add a way to set these via HTML attributes? And I would love for it to use the Values API. In the Symfony world (where I come from), we have a helper that makes setting values on a controller dead simple and clean.

And while it is currently possible to use data attributes to set things like enter-active, I think these should also be settable via the values API. We don't need to remove the current way of setting them as data attributes, just add the ability to use options :).

WDYT? Thanks!

`useHotkeys`: does unbind all handlers attached to a key instead of just the one that the controller is disconnecting

For example, let's suppose we have to forms on the same page, each one attached to a controller that registers the same hotkey for both, when one of those forms is submitted the controller disconnects and unbinds the key, but it is unbinding both handlers for that given key instead of just that one of the form that was submitted.

Ex:

<form data-controller="example" id="new_name">
  <label for="name">Name:</label><br>
  <input type="text" id="name" name="name"><br>
  <input type="submit" name="commit" value="Submit" id="submit_new_name">
</form>
<!-- some other HTML here -->
<form data-controller="example" id="new_message">
  <label for="message">Message:</label><br>
  <input type="text" id="message" name="message"><br>
  <input type="submit" name="commit" value="Submit" id: "submit_new_message">
</form>
import { Controller } from "@hotwired/stimulus"
import { useHotkeys } from "stimulus-use"

// Connects to data-controller="example"
export default class extends Controller {
  connect() {
    useHotkeys(this, {
      hotkeys: {
        "ctrl+enter": {
          handler: this.submitForm,
          options: {
            element: this.element
          }
        },
        "cmd+enter": {
          handler: this.submitForm,
          options: {
            element: this.element
          }
        }
      },
      filter: this.filterForInput
    })
  }

  submitForm (evt) {
    const formToSubmit = evt.target.closest("form")
    const submitButton = formToSubmit.querySelector(`#submit_${formToSubmit.id}`)
    if (submitButton.disabled) return;

    submitButton.focus();
    submitButton.click();
    submitButton.disabled = true;
  }

  filterForInput(evt) {
    return evt.target.tagName === "INPUT"
  }
}

In this example above each form can be submitted when pressing Ctrl+Enter, if I for example submit one of those first, I can go to the next and provide a value to the input and press Ctrl+Enter and it won't submit, since the first submit did unbind the key for all handlers and not only for the handlers attached to that form.

From what I can see when Hotkeys.js unbinds a key it accepts as args the scope and the handler https://github.com/jaywcjlove/hotkeys/blob/master/src/index.js#L112 and here https://github.com/jaywcjlove/hotkeys/blob/master/src/index.js#L142 it checks if the method of a given handler is the same as the one that is being unbound, if use-hotkeys pass the handler to the unbind method then It wouldn't unbind handlers that it shouldn't.

I try it here and had to make the following changes to make it work as expected. I'm also not sure why in the bind method the handler is passed down as (e: KeyboardEvent) => handler(e, e as unknown as HotkeysEvent) instead of just handler (this would be a new function and never would match the handler passed down in the unbind method)

https://github.com/stimulus-use/stimulus-use/blob/main/src/use-hotkeys/use-hotkeys.ts#L42

bind = () => {
    for (const [hotkey, definition] of Object.entries(this.hotkeysOptions.hotkeys as any)) {
      const handler = (definition as HotkeyDefinition).handler.bind(this.controller)
-       hotkeys(hotkey, (definition as HotkeyDefinition).options, (e: KeyboardEvent) =>
-          handler(e, e as unknown as HotkeysEvent)
-        )
+      hotkeys(hotkey, (definition as HotkeyDefinition).options, handler)
    }
  }

https://github.com/stimulus-use/stimulus-use/blob/main/src/use-hotkeys/use-hotkeys.ts#L51

unbind = () => {
-    for (const hotkey in this.hotkeysOptions.hotkeys as any) {
-      hotkeys.unbind(hotkey)
+   for (const [hotkey, definition] of Object.entries(this.hotkeysOptions.hotkeys as any)) {
+     const handler = (definition as HotkeyDefinition).handler.bind(this.controller)
+     hotkeys.unbind(hotkey, (definition as HotkeyDefinition).scope, handler)
    }
  }

`useTransition`: the "transitioned" option and elements that begin "visible"

Hi again!

I've noticed what is, probably, just a missing spot in the docs for the new useTransition.

Suppose that you have an element that starts in a visible state (unlike a drop-down, which is hidden and only shown later):

<div data-controller="custom-close">
    <div data-custom-close-target="boxToClose">
        This element will fade out and in!
    </div>

    <button data-action="custom-close#leave">Close Box</button>
    <button data-action="custom-close#enter">Open Box</button>
</div>

(where the element option is set to this.boxToCloseTarget).

With this setup, on load, the boxToClose element is immediately hidden. Is that expected? What I mean is, if your element begins visible, it is required to pass the transitioned: true option when initializing useTransition (and so, this is something that just needs a mention in the docs)? Or is this not the expected behavior.

Thanks!

`useTransition`: Persisting the state on the DOM

Hi!

I'm just starting to use stimulus-use and it's great! Thanks for your work.

I've found myself with an issue with useTransition and the transitioned state. This state is being track on the js class instead on the DOM. Let me explain my case:

I have a sidebar that can can be opened/closed by two buttons. For that reason I'm instanciating the controller twice having each one of them tracking their own transitioned state. I think if we can persist this state in the target element (not in the controller element) in an attribute like data-transitioned-state, for example, then any controller instance will just read the current state from the element attribute.

This is also preferabe according the stimulus approach: "A Stimulus application’s state lives as attributes in the DOM; controllers themselves are largely stateless.

Thanks!

Typescript: Usage with mixins

This is not really an issue, but maybe someone has a best practice for that problem. I could not find anything on that topic - however I am also a newbie to typescript, so that I just don't see, how it works.
How do you use mixins like useDispatch together with typescript? Is there a way to let typescript "know", that the dispatch method exists and what it's type is? If useDispatch is called in the connect method, and I try to use this.dispatch, typescript complains about this.dispatch is not defined.

import { Controller }  from 'stimulus';
import { useDispatch } from "stimulus-use";

export default class extends Controller {
    connect() {
        useDispatch(this);
    }

    handleClick(event) {
        this.dispatch('myevent');
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.