{"id":30257410,"url":"https://github.com/allmarkedup/stimulus-x","last_synced_at":"2025-09-28T09:39:41.234Z","repository":{"id":306825818,"uuid":"1023036502","full_name":"allmarkedup/stimulus-x","owner":"allmarkedup","description":"StimulusX brings the power of reactive programming to Stimulus JS.","archived":false,"fork":false,"pushed_at":"2025-08-10T14:24:38.000Z","size":1017,"stargazers_count":55,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-18T09:32:58.473Z","etag":null,"topics":["dom","dom-binding","reactive-programming","stimulus","stimulusjs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/allmarkedup.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-20T11:47:42.000Z","updated_at":"2025-08-11T08:48:56.000Z","dependencies_parsed_at":"2025-07-27T22:08:11.813Z","dependency_job_id":null,"html_url":"https://github.com/allmarkedup/stimulus-x","commit_stats":null,"previous_names":["allmarkedup/stimulus-x"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/allmarkedup/stimulus-x","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allmarkedup%2Fstimulus-x","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allmarkedup%2Fstimulus-x/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allmarkedup%2Fstimulus-x/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allmarkedup%2Fstimulus-x/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/allmarkedup","download_url":"https://codeload.github.com/allmarkedup/stimulus-x/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allmarkedup%2Fstimulus-x/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277352184,"owners_count":25803851,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-28T02:00:08.834Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dom","dom-binding","reactive-programming","stimulus","stimulusjs"],"created_at":"2025-08-15T16:02:05.043Z","updated_at":"2025-09-28T09:39:41.198Z","avatar_url":"https://github.com/allmarkedup.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\".github/assets/logo.png\" width=\"250\"\u003e\n \n_Reactivity engine for Stimulus controllers_\n\u003cbr\u003e\n\n![NPM Version](https://img.shields.io/npm/v/stimulus-x)\n [![CI](https://github.com/allmarkedup/stimulus-x/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/allmarkedup/stimulus-x/actions/workflows/ci.yml)\n\n\u003c/div\u003e\n\n---\n\n_StimulusX_ brings modern **reactive programming paradigms** to [Stimulus](https://stimulus.hotwired.dev) controllers.\n\n**Features include:**\n\n❎ \u0026nbsp;Automatic UI updates with reactive DOM bindings\u003cbr\u003e\n❎ \u0026nbsp;Declarative binding syntax based on Stimulus' [action descriptors](https://stimulus.hotwired.dev/reference/actions#descriptors)\u003cbr\u003e\n❎ \u0026nbsp;Chainable value modifiers \u003cbr\u003e\n❎ \u0026nbsp;Property watcher callback \u003cbr\u003e\n❎ \u0026nbsp;Extension API\n\u003cbr\u003e\n\u003cbr\u003e\n**Who is StimulusX for?**\n\nIf you are a Stimulus user and are tired of writing repetitive DOM manipulation code then StimulusX's declarative, live-updating **controller\u0026rarr;HTML bindings** might be just what you need to brighten up your day. _StimulusX_ will make your controllers cleaner \u0026 leaner whilst ensuring they are less tightly coupled to a specific markup structure.\n\nHowever if you are _not_ currently a Stimulus user then I'd definitely recommend looking at something like [Alpine](https://alpinejs.dev), [VueJS](https://vuejs.org/) or [Svelte](https://svelte.dev/) first before considering a `Stimulus + StimulusX` combo, as they will likely provide a more elegant fit for your needs.\n\n[ \u0026darr; Skip examples and jump to the docs \u0026darr;](#installation)\n\n### Example: A simple counter\n\nBelow is an example of a simple `counter` controller implemented using StimulusX's reactive DOM bindings.\n\n\u003e [!TIP]\n\u003e _You can find a [runnable version of this example on JSfiddle \u0026rarr;](https://jsfiddle.net/allmarkedup/q293ay8v/)_\n\n\u003cimg src=\".github/assets/counter.gif\" width=\"120\"\u003e\n\n```html\n\u003cdiv data-controller=\"counter\"\u003e\n  \u003cspan data-bind-attr=\"class~counter#displayClasses\"\u003e\n    \u003cspan data-bind-text=\"counter#count\"\u003e\u003c/span\u003e of\n    \u003cspan data-bind-text=\"counter#max\"\u003e\u003c/span\u003e\n  \u003c/span\u003e\n\n  \u003cbutton data-action=\"counter#increment\"\u003e⬆️\u003c/button\u003e\n  \u003cbutton\n    data-bind-attr=\"disabled~counter#count:lte(0)\"\n    data-action=\"counter#decrement\"\n  \u003e⬇️\u003c/button\u003e\n\u003c/div\u003e\n```\n\n```js\n// controllers/counter_controller.js\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  initialize(){\n    this.count = 0;\n    this.max = 5;\n  }\n\n  increment(){\n    this.count++;\n  }\n\n  decrement(){\n    this.count--;\n  }\n\n  get displayClasses(){\n    return {\n      \"text-green\": this.count \u003c= this.max,\n      \"text-red font-bold\": this.count \u003e this.max,\n    }\n  }  \n}\n```\n\n---\n\n## Installation\n\nAdd the `stimulus-x` package to your `package.json`:\n\n#### Using NPM:\n\n```\nnpm i stimulus-x\n```\n\n#### Using Yarn:\n\n```\nyarn add stimulus-x\n```\n\n#### Without a bundler\n\nYou can use StimulusX with native browser `module` imports by loading from it from [Skypack](https://skypack.dev):\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n  \u003cmeta charset=\"utf-8\"\u003e\n  \u003cscript type=\"module\"\u003e\n    import { Application } from \"https://cdn.skypack.dev/@hotwired/stimulus\"\n    import StimulusX from \"https://cdn.skypack.dev/stimulus-x\"\n    // ...see docs below for usage info\n  \u003c/script\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n## Usage\n\nStimulusX hooks into your Stimulus application instance via the `StimulusX.init` method.\n\n```js\nimport { Application, Controller } from \"@hotwired/stimulus\";\nimport StimulusX from \"stimulus-x\";\n\nwindow.Stimulus = Application.start();\n\n// You must call the `StimulusX.init` method _before_ registering any controllers.\nStimulusX.init(Stimulus); \n\n// Register controllers as usual...\nStimulus.register(\"example\", ExampleController);\n```\n\nBy default, **all registered controllers** will automatically have access to StimulusX's reactive features - including [attribute bindings](#️attribute-bindings) (e.g. class names, `data-` and `aria-` attributes, `hidden` etc), [text content bindings](#text-bindings), [HTML bindings](#html-bindings) and more.\n\n\u003ch3 id=\"controller-opt-in\"\u003e Explicit controller opt-in\u003c/h3\u003e\n\nIf you don't want to automatically enable reactivity for all of your controllers you can instead choose to _opt-in_ to StimulusX features on a controller-by-controller basis.\n\nTo enable individual controller opt-in set the `optIn` option to `true` when initializing StimulusX:\n\n```js\nStimulusX.init(Stimulus, { optIn: true }); \n```\n\nTo then enable reactive features on a per-controller basis, set the `static reactive` variable to `true` in the controller class:\n\n```js\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static reactive = true; // enable StimulusX reactive features for this controller\n  // ...\n}\n```\n\n\u003ch2 id=\"dom-bindings-overview\"\u003eReactive DOM bindings - overview\u003c/h2\u003e\n\n[HTML attributes](#attribute-binding), [text](#text-binding) and [HTML content](#text-binding) can be tied to the value of controller properties using `data-bind-*` attributes in your HTML.\n\nThese bindings are _reactive_ which means the DOM is **automatically updated** when the value of the controller properties change.\n\n### Binding descriptors\n\nBindings are specified declaratively in your HTML using `data-bind-(attr|text|html)` attributes where the _value_ of the attribute is a **binding descriptor**.\n\n**Attribute** binding descriptors take the form `attribute~identifier#property` where `attribute` is the name of the **HTML attribute** to set, `identifier` is the **controller identifier** and `property` is the **name of the property** to bind to.\n\n```html\n\u003c!-- keep the `src` attribute value in sync with the value of the lightbox controller `.imageUrl` property --\u003e\n\u003cimg data-bind-attr=\"src~lightbox#imageUrl\"\u003e\n```\n\n📚 ***Read more: [Attribute bindings \u0026rarr;](#attribute-binding)***\n\n**Text** and **HTML** binding descriptors take the form `identifier#property` where `identifier` is the **controller identifier** and `property` is the **name of the property** to bind to.\n\n```html\n\u003c!-- keep `element.textContent` in sync with the value of the article controller `.title` property --\u003e\n\u003ch1 data-bind-text=\"article#title\"\u003e\u003c/h1\u003e\n\n\u003c!-- keep `element.innerHTML` in sync with the value of the article controller `.proseContent` property --\u003e\n\u003cdiv data-bind-html=\"article#proseContent\"\u003e\u003c/div\u003e\n```\n\n📚 ***Read more: [text bindings](#text-binding)*** _and_ ***[HTML bindings \u0026rarr;](#html-binding)***\n\n\u003e [!NOTE]\n\u003e _If you are familiar with Stimulus [action descriptors](https://stimulus.hotwired.dev/reference/actions#descriptors) then binding descriptors should feel familiar as they have a similar role and syntax._\n\n### Value modifiers\n\nBinding _value modifiers_ are a convenient way to transform or test property values in-situ before updating the DOM.\n\n```html\n\u003ch1 data-bind-text=\"article#title:upcase\"\u003e\u003c/h1\u003e\n\u003cinput data-bind-attr=\"disabled~workflow#status:is('complete')\"\u003e\n```\n\n📚 ***Read more: [Binding value modifiers \u0026rarr;](#binding-value-modifiers)***\n\n### Negating property values\n\nBoolean property values can be negated (inverted) by prefixing the `identifier#property` part of the binding descriptor with an exclaimation mark:.\n\n```html\n\u003cdetails data-bind-attr=\"open~!panel#closed\"\u003e\u003c/details\u003e\n```\n\n\u003e [!NOTE]\n\u003e _The `!` prefix is really just an more concise alternative syntax for applying [the `:not` modifier](#binding-value-modifiers)._\n\n### Shallow vs deep reactivity\n\nBy default StimulusX only tracks changes to **top level** controller properties to figure out when to update the DOM. This is _shallow reactivity_.\n\nTo enable _deep reactivity_ for a controller (i.e. the ability to track changes to **properties in nested objects**) you can can set the static `reactive` property to `\"deep\"` within your controller:\n\n```js\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static reactive = \"deep\"; // enable deep reactivity mode\n  // ...\n}\n```\n\nAlternatively you can enable deep reactivity for **all** controllers using the `trackDeep` option when [initializing StimulusX](#usage):\n\n```js\nStimulusX.init(Stimulus, { trackDeep: true }); \n```\n\n\u003ch2 id=\"attribute-bindings\"\u003eAttribute bindings\u003c/h2\u003e\n\nAttribute bindings connect **HTML attribute values** to **controller properties**, and ensure that the attribute value is automatically updated so as to stay in sync with the value of the controller property at all times.\n\nThey are specified using `data-bind-attr` attributes with [value descriptors](#binding-descriptors) that take the general form `{attribute}~{identifier}#{property}`.\n\n```html\n\u003cdiv data-controller=\"lightbox\"\u003e\n  \u003cimg data-bind-attr=\"src~lightbox#imageUrl\"\u003e\n\u003c/div\u003e\n```\n\n```js\nexport default class extends Controller {\n  initialize(){\n    this.imageUrl = \"https://placeholder.com/kittens.jpg\";\n  }\n}\n```\n\nIn the attribute binding descriptor `src~lightbox#imageUrl` above:\n\n* `src` is the **HTML attribute** to be added/updated/remove\n* `lightbox` is the **controller identifier**\n* `imageUrl` is the **name of the property** that the attribute value should be bound to\n\nSo the image `src` attribute will initially be set to the default value of the `imageUrl` property (i.e. `https://placeholder.com/kittens.jpg`). And whenever the `imageUrl` property is changed, the image `src` attribute value in the DOM will be automatically updated to reflect the new value.\n\n```js\nthis.imageUrl = \"https://kittens.com/daily-kitten.jpg\"\n// \u003cimg src=\"https://kittens.com/daily-kitten.jpg\"\u003e\n```\n\n\n### Boolean attributes\n\n[Boolean attributes](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes) such as `checked`, `disabled`, `open` etc will be _added_ if the value of the property they are bound to is `true`, and _removed completely_ when it is `false`.\n\n```html\n\u003cdiv data-controller=\"example\"\u003e\n  \u003cbutton data-bind-attr=\"disabled~example#incomplete\"\u003esubmit\u003c/button\u003e\n\u003c/div\u003e\n```\n\n```js\nexport default class extends Controller {\n  initialize(){\n    this.incomplete = true;\n  }\n}\n```\n\nBoolean attribute bindings often pair nicely with **[comparison modifiers](#comparison-modifiers)** such as `:is`:\n\n```html\n\u003cdiv data-controller=\"form\"\u003e\n  \u003cinput type=\"text\" data-action=\"form#checkCompleted\"\u003e\n  \u003cbutton data-bind-attr=\"disabled~form#status:is('incomplete')\"\u003esubmit\u003c/button\u003e\n\u003c/div\u003e\n```\n\n```js\nexport default class extends Controller {\n  initialize(){\n    this.status = \"incomplete\";\n  }\n\n  // called when the text input value is changed\n  checkCompleted({ currentTarget }){\n    if (currentTarget.value?.length \u003e 0) {\n      this.status === \"complete\"; // button will be enabled\n    }\n  }\n}\n```\n\n### Binding classes\n\n`class` attribute bindings let you set specific classes on an element based on controller state.\n\n```html\n\u003cdiv data-controller=\"counter\"\u003e\n  \u003cdiv data-bind-attr=\"class~counter#validityClasses\"\u003e\n    ...\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n```js\n// controllers/counter_controller.js\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  initialize(){\n    this.count = 0;\n  }\n\n  get validityClasses(){\n    if (this.count \u003e 10) {\n      return \"text-red font-bold\";\n    } else {\n      return \"text-green\";\n    }\n  }\n}\n```\n\nIn the example above, the value of the `validityClasses` property is a string of classes that depends on whether or not the value of the `count` property is greater than `10`:\n\n* If `this.count \u003e 10` then the element `class` attribute will be set to `\"text-red font-bold\"`.\n* If `this.count \u003c 10` then the element `class` attribute will be set to `\"text-green\"`.\n\nThe list of classes can be returned as a **string** or as an **array** - or as a special [class object](#class-objects).\n\n#### Class objects\n\nIf you prefer, you can use a class object syntax to specify the class names. These are objects where the classes are the keys and booleans are the values.\n\nThe example above could be rewritten to use a class object as follows:\n\n```js\nexport default class extends Controller {\n  // ...\n  get validityClasses(){\n    return {\n      \"text-red font-bold\": this.count \u003e 10,\n      \"text-green\": this.count \u003c= 10,\n    }\n  }\n}\n```\n\nThe list of class names will be resolved by merging all the class names from keys with a value of `true` and ignoring all the rest.\n\n\u003ch2 id=\"text-bindings\"\u003eText content bindings\u003c/h2\u003e\n\nText content bindings connect the **`textContent`** of an element to a **controller property**. They are useful when you want to dynamically update text on the page based on controller state.\n\nText content bindings are specified using `data-bind-text` attributes where the value is a binding descriptor in the form `{identifier}#{property}`.\n\n```html\n\u003cdiv data-controller=\"workflow\"\u003e\n  Status: \u003cspan data-bind-text=\"workflow#status\"\u003e\u003c/span\u003e\n\u003c/div\u003e\n```\n\n```js\nexport default class extends Controller {\n  static values = {\n    status: {\n      type: String,\n      default: \"in progress\"\n    }\n  }\n}\n```\n\n\u003ch2 id=\"html-bindings\"\u003eHTML bindings\u003c/h2\u003e\n\nHTML bindings are very similar to [text content bindings](#️text-bindings) except they update the element's `innerHTML` instead of `textContent`.\n\nHTML bindings are specified using `data-bind-html` attributes where the value is a binding descriptor in the form `{identifier}#{property}`.\n\n```html\n\u003cdiv data-controller=\"workflow\"\u003e\n  \u003cdiv class=\"status-icon\" data-bind-html=\"workflow#statusIcon\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n```\n\n```js\nexport default class extends Controller {\n  initialize(){\n    this.status = \"in progress\";\n  }\n\n  get statusIcon(){\n    if (this.status === \"complete\"){\n      return `\u003ci data-icon=\"in-complete\"\u003e\u003c/i\u003e`;\n    } else {\n      return `\u003ci data-icon=\"in-progress\"\u003e\u003c/i\u003e`;\n    }\n  }\n}\n```\n\n\u003ch2 id=\"binding-value-modifiers\"\u003eBinding value modifiers\u003c/h2\u003e\n\nInline _value modifiers_ are a convenient way to transform or test property values before updating the DOM.\n\nModifiers are appended to the end of [binding descriptors](#binding-descriptors) and are separated from the descriptor (or from each other) by a `:` colon.\n\nThe example below uses the `upcase` modifier to transform the title to upper case before displaying it on the page:\n\n```html\n\u003ch1 data-bind-text=\"article#title:upcase\"\u003e\u003c/h1\u003e\n```\n\n\u003e [!TIP]\n\u003e _Multiple modifiers can be piped together one after each other, separated by colons, e.g. `article#title:upcase:trim`_\n\n\u003ch3 id=\"string-transform-modifiers\"\u003eString transform modifiers\u003c/h3\u003e\n\nString transform modifiers provide stackable output transformations for string values.\n\n#### Available string modifiers:\n\n* `:upcase` - transform text to uppercase\n* `:downcase` - transform text to lowercase\n* `:strip` - strip leading and trailing whitespace\n\n\u003ch5 id=\"upcase-modifier\"\u003e\u003ccode\u003e:upcase\u003c/code\u003e\u003c/h5\u003e\n\nConverts the string to uppercase.\n\n```html\n\u003ch1 data-bind-text=\"article#title:upcase\"\u003e\u003c/h1\u003e\n```\n\n\u003ch5 id=\"downcase-modifier\"\u003e\u003ccode\u003e:downcase\u003c/code\u003e\u003c/h5\u003e\n\nConverts the string to lowercase.\n\n```html\n\u003ch1 data-bind-text=\"article#title:downcase\"\u003e\u003c/h1\u003e\n```\n\n\u003ch5 id=\"downcase-modifier\"\u003e\u003ccode\u003e:strip\u003c/code\u003e\u003c/h5\u003e\n\nStrips leading and trailing whitespace from the string value.\n\n```html\n\u003ch1 data-bind-text=\"article#title:downcase\"\u003e\u003c/h1\u003e\n```\n\n\u003ch3 id=\"comparison-modifiers\"\u003eComparison modifiers\u003c/h3\u003e\n\n_Comparison modifiers_ compare the resolved **controller property value** against a **provided test value**.\n\n```html\n\u003cinput data-bind-attr=\"disabled~workflow#status:is('complete')\"\u003e\n```\n\nThey are primarily intended for use with [boolean attribute bindings](#boolean-attributes) to conditionally add or remove attributes based on the result of value comparisons.\n\n\u003e [!TIP]\n\u003e _Comparison modifiers play nicely with other chained modifiers - the comparison will be done against the property value **after** it has been transformed by any other preceeding modifiers_:\n\u003e ```html\n\u003e \u003cinput data-bind-attr=\"disabled~workflow#status:upcase:is('COMPLETE')\"\u003e`\n\u003e ```\n\n#### Available comparison modifiers:\n\n* `:is(\u003cvalue\u003e)` - equality test ([read more](#is-modifier))\n* `:isNot(\u003cvalue\u003e)` - negated equality test ([read more](#is-not-modifier))\n* `:gt(\u003cvalue\u003e)` - 'greater than' test ([read more](#gt-modifier))\n* `:gte(\u003cvalue\u003e)` - 'greater than or equal to' test ([read more](#gte-modifier))\n* `:lt(\u003cvalue\u003e)` - 'less than' test ([read more](#lt-modifier))\n* `:lte(\u003cvalue\u003e)` - 'less than or equal to' test ([read more](#lte-modifier))\n\n\u003ch5 id=\"is-modifier\"\u003e\u003ccode\u003e:is(\u0026lt;value\u0026gt;)\u003c/code\u003e\u003c/h5\u003e\n\nThe `:is` modifier compares the resolved property value with the `\u003cvalue\u003e` provided as an argument, returning `true` if they match and `false` if not.\n\n```html\n\u003c!-- input is disabled if `workflow#status` === \"complete\" --\u003e\n\u003cinput data-bind-attr=\"disabled~workflow#status:is('complete')\"\u003e\n```\n\n* **String** comparison: `:is('single quoted string')`, `:is(\"double quoted string\")`\n* **Integer** comparison: `:is(123)`\n* **Float** comparison: `:is(1.23)`\n* **Boolean** comparison: `:is(true)`, `:is(false)`\n\n\u003ch5 id=\"is-not-modifier\"\u003e\u003ccode\u003e:isNot(\u0026lt;value\u0026gt;)\u003c/code\u003e\u003c/h5\u003e\n\nThe `:isNot` modifier works exactly the same as the [`:is` modifier](#is-modifier), but returns `true` if the value comparison fails and `false` if the values match.\n\n\u003e [!IMPORTANT]\n\u003e _The `:is` and `:isNot` modifiers only accept simple `String`, `Number` or `Boolean` values. `Object` and `Array` values are not supported._\n\n\u003ch5 id=\"gt-modifier\"\u003e\u003ccode\u003e:gt(\u0026lt;value\u0026gt;)\u003c/code\u003e\u003c/h5\u003e\n\nThe `:gt` modifier returns `true` if the resolved property value is **greater than** the numeric `\u003cvalue\u003e` provided as an argument.\n\n```html\n\u003c!-- button is disabled if `counter#count` is \u003e 9 --\u003e\n\u003cbutton data-bind-attr=\"disabled~counter#count:gt(9)\"\u003e+\u003c/button\u003e\n```\n\n\u003ch5 id=\"gte-modifier\"\u003e\u003ccode\u003e:gte(\u0026lt;value\u0026gt;)\u003c/code\u003e\u003c/h5\u003e\n\nThe `:gte` modifier returns `true` if the resolved property value is **greater than or equal to** the numeric `\u003cvalue\u003e` provided as an argument.\n\n```html\n\u003c!-- button is disabled if `counter#count` is \u003e= 10 --\u003e\n\u003cbutton data-bind-attr=\"disabled~counter#count:gte(10)\"\u003e+\u003c/button\u003e\n```\n\n\u003ch5 id=\"lt-modifier\"\u003e\u003ccode\u003e:lt(\u0026lt;value\u0026gt;)\u003c/code\u003e\u003c/h5\u003e\n\nThe `:lt` modifier returns `true` if the resolved property value is **less than** the numeric `\u003cvalue\u003e` provided as an argument.\n\n```html\n\u003c!-- button is disabled if `counter#count` is \u003c 1 --\u003e\n\u003cbutton data-bind-attr=\"disabled~counter#count:lt(1)\"\u003e-\u003c/button\u003e\n```\n\n\u003ch5 id=\"lte-modifier\"\u003e\u003ccode\u003e:lte(\u0026lt;value\u0026gt;)\u003c/code\u003e\u003c/h5\u003e\n\nThe `:lte` modifier returns `true` if the resolved property value is **less than or equal to** the numeric `\u003cvalue\u003e` provided as an argument.\n\n```html\n\u003c!-- button is disabled if `counter#count` is \u003c= 0 --\u003e\n\u003cbutton data-bind-attr=\"disabled~counter#count:lte(0)\"\u003e-\u003c/button\u003e\n```\n\n\u003ch3 id=\"other-modifiers\"\u003eOther modifiers\u003c/h3\u003e\n\n* `:not` - negate (invert) a boolean value\n\n\u003e [!TIP]\n\u003e _You can add your own **custom modifiers** if required. See [Extending StimulusX](#extending) for more info._\n\n\u003ch2 id=\"watching-properties\"\u003eWatching properties for changes\u003c/h2\u003e\n\n```js\nimport { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static watch = [\"enabled\", \"userInput\"];\n\n  connect(){\n    this.enabled = false;\n    this.userInput = \"\";\n  }\n\n  enabledPropertyChanged(currentValue, previousValue){\n    if (currentValue) {\n      console.log(\"Controller is enabled\");\n    } else {\n      console.log(\"Controller has been disabled\");\n    }\n  }\n\n  userInputPropertyChanged(currentValue, previousValue){\n    console.log(`User input has changed from \"${previousValue}\" to \"${currentValue}\"`);\n  }\n\n  // ...\n}\n```\n\n🚧 _More docs coming soon..._\n\n\n\u003ch2 id=\"extending\"\u003eExtending StimulusX\u003c/h2\u003e\n\n### Custom modifiers\n\nYou can add your own modifiers using the `StimulusX.modifier` method:\n\n```js\nStimulusX.modifier(\"modifierName\", (value) =\u003e {\n  // Do something to `value` and return the result of the transformation.\n  const transformedValue = doSomethingTo(value);\n  return transformedValue;\n}); \n```\n\n### Custom directives\n\n🚧 _Documentation coming soon..._\n\n## Known issues, caveats and workarounds\n\n### ❌ Private properties and methods  \n\nUnfortunately it is not possible to use StimulusX with controllers that define [private methods or properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_elements) (i.e. with names using the `#` prefix). [See Lea Verou's excellent blog post on the topic for more details.](https://lea.verou.me/blog/2023/04/private-fields-considered-harmful/)\n\nIf you have existing controllers with private methods and want to add new StimulusX-based controllers alongside them then you should [enable explicit controller opt-in](#controller-opt-in) to prevent errors being thrown at initialization time.\n\n## Credits and inspiration\n\nStimulusX uses [VueJS's reactivity engine](https://github.com/vuejs/core/tree/main/packages/reactivity) under the hood and was inspired by (and borrows much of its code from) the excellent [Alpine.JS](https://alpinejs.dev/directives/bind) library.\n\n## License\n\nStimulusX is available as open source under the terms of the MIT License.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallmarkedup%2Fstimulus-x","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fallmarkedup%2Fstimulus-x","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallmarkedup%2Fstimulus-x/lists"}