{"_id":"@dolanske/beskydy","_rev":"2-446a3a33e78c3ac14702a7c155869cd5","name":"@dolanske/beskydy","dist-tags":{"latest":"3.0.0"},"versions":{"2.0.2":{"name":"@dolanske/beskydy","version":"2.0.2","keywords":["javascript","typescript","vue-petite","alpine-js","DOM"],"author":{"name":"dolanske"},"license":"GPL-3.0","_id":"@dolanske/beskydy@2.0.2","maintainers":[{"name":"dolanske","email":"dolanovsky@gmail.com"}],"homepage":"https://github.com/dolanske/beskydy#readme","bugs":{"url":"https://github.com/dolanske/beskydy/issues"},"dist":{"shasum":"c30599f53ea9593d1d3eb93aa9e0241f15b81203","tarball":"https://registry.npmjs.org/@dolanske/beskydy/-/beskydy-2.0.2.tgz","fileCount":6,"integrity":"sha512-IravLoc96lwvBQzBcqPOvORccOvDltH7SsOFiTA6cx9eFWIhunz5k8wbTJgCeLqQxg1yyGeEsMyjep337aMRQQ==","signatures":[{"sig":"MEQCIG7nXX3m8FlLKiZUePCcCxWtnYlUuhPMxvz81jZUJLHEAiA7F2bOgPSDpe93CPH2FhlhxMuU+hEixY5f6Ua7HnTyoA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":152897},"main":"./dist/beskydy.js","type":"module","types":"./dist/index.d.ts","module":"./dist/beskydy.js","exports":{".":{"import":"./dist/beskydy.js"}},"gitHead":"93ffaba6c21ac0f90a304a77494025294c62f460","private":false,"scripts":{"dev":"vite","lint":"eslint .","test":"vitest","build":"tsc && vite build","preview":"vite preview","coverage":"vitest run --coverage","lint:fix":"eslint . --fix"},"_npmUser":{"name":"dolanske","email":"dolanovsky@gmail.com"},"repository":{"url":"git+https://github.com/dolanske/beskydy.git","type":"git"},"_npmVersion":"10.7.0","description":"Like alps, but smaller. Inspired by alpine.js and petite-vue with my own simplified implementation.","directories":{},"_nodeVersion":"21.7.1","dependencies":{"jsdom":"^24.0.0","@vue/reactivity":"^3.4.18"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.1.1","eslint":"^8.56.0","vitest":"^1.2.2","typescript":"^5.3.3","vite-plugin-dts":"^3.7.2","@vitest/coverage-v8":"^1.2.2","@antfu/eslint-config":"^2.6.4"},"_npmOperationalInternal":{"tmp":"tmp/beskydy_2.0.2_1731679410116_0.9865809490481559","host":"s3://npm-registry-packages"}},"3.0.0":{"name":"@dolanske/beskydy","type":"module","version":"3.0.0","private":false,"description":"Like alps, but smaller. Inspired by alpine.js and petite-vue with my own simplified implementation.","author":{"name":"dolanske"},"license":"GPL-3.0","repository":{"type":"git","url":"git+https://github.com/dolanske/beskydy.git"},"keywords":["javascript","typescript","vue-petite","alpine-js","DOM"],"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/beskydy.js"}},"main":"./dist/beskydy.js","module":"./dist/beskydy.js","types":"./dist/index.d.ts","scripts":{"dev":"vite","build":"tsc && vite build","preview":"vite preview","test":"vitest","coverage":"vitest run --coverage","lint":"eslint .","lint:fix":"eslint . --fix"},"dependencies":{"@vue/reactivity":"^3.4.18"},"devDependencies":{"@antfu/eslint-config":"^2.6.4","@vitest/coverage-v8":"^1.2.2","eslint":"^8.56.0","jsdom":"^24.0.0","typescript":"^5.3.3","vite":"^5.1.1","vite-plugin-dts":"^3.7.2","vitest":"^1.2.2"},"gitHead":"9ba59ab8c9a07eea581502677544c08f4a86129d","_id":"@dolanske/beskydy@3.0.0","bugs":{"url":"https://github.com/dolanske/beskydy/issues"},"homepage":"https://github.com/dolanske/beskydy#readme","_nodeVersion":"24.7.0","_npmVersion":"11.11.1","dist":{"integrity":"sha512-NeVAefplLpmtUB4l7Uli5ZmZ/N8mr9hEGDBPh0njqccQ8db4m8O2Q0SAnPZL+HenvmdbWQXGeQsLIQcjkBc6fA==","shasum":"8f06c8110f2fb728aeac8a13c2407ca4fb6f8f9c","tarball":"https://registry.npmjs.org/@dolanske/beskydy/-/beskydy-3.0.0.tgz","fileCount":6,"unpackedSize":162368,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGsWY2zOj18z3sNgF4cUMsNIwlz4E5fQ3D9dsq8iMDI6AiEA97BAmIlsYlHednQuXa4FBJ6sqYdSxGERIz/F8t0XI0E="}]},"_npmUser":{"name":"dolanske","email":"dolanovsky@gmail.com"},"directories":{},"maintainers":[{"name":"dolanske","email":"dolanovsky@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/beskydy_3.0.0_1789147154551_0.1806012496171414"},"_hasShrinkwrap":false}},"time":{"created":"2024-11-15T14:03:29.985Z","modified":"2026-09-11T17:19:14.896Z","2.0.2":"2024-11-15T14:03:30.425Z","3.0.0":"2026-09-11T17:19:14.731Z"},"bugs":{"url":"https://github.com/dolanske/beskydy/issues"},"author":{"name":"dolanske"},"license":"GPL-3.0","homepage":"https://github.com/dolanske/beskydy#readme","keywords":["javascript","typescript","vue-petite","alpine-js","DOM"],"repository":{"type":"git","url":"git+https://github.com/dolanske/beskydy.git"},"description":"Like alps, but smaller. Inspired by alpine.js and petite-vue with my own simplified implementation.","maintainers":[{"name":"dolanske","email":"dolanovsky@gmail.com"}],"readme":"# beskydy\r\n\r\n Like alps, but smaller. Alpine / vue-petite inspired but mostly implemented by me. Define small interactive partitions within your HTML without needing to write javascript.\r\n\r\n ```bash\r\nnpm i @dolanske/beskydy\r\n ```\r\n\r\nEvery directive has a runnable example in [`src/examples`](./src/examples). Clone the repository and run `npm run dev` to browse them all in one page.\r\n\r\n## The Concept\r\n\r\nCreate a reactive partition by adding `x-scope` directive on an element. This will create a reactive scope for said element and expose all of its properties to its descendants.\r\n\r\n ```html\r\n<div x-scope=\"{ count: 0 }\">\r\n  <button\r\n    x-data=\"{ incrBy: 2 }\"\r\n    x-on:click=\"count += incrBy\"\r\n    x-text=\"`Add ${incrBy}`\"\r\n  ></button>\r\n  <span>Count is: {{ count }}</span>\r\n</div>\r\n ```\r\n\r\nThe only real JS we need to write is the app initialization. This can be done by simply create `new Beskydy()` class and calling the `collect` function.\r\nOptionally, we can provide global reactive properties into the constructor, which will be shared and available across all scopes.\r\n\r\n```ts\r\nimport { Beskydy } from '@dolanske/beskydy'\r\n\r\nconst app = new Beskydy({\r\n  characters: [],\r\n  isLoading: false,\r\n  async fetchCharacters() {\r\n    this.isLoading = true\r\n    this.characters = await fetch('https://swapi.dev/api/people/')\r\n    this.isLoading = false\r\n  }\r\n})\r\n\r\n// Calling this method will start the app initialization.\r\n// All declared scopes will be collected and activated.\r\napp.collect()\r\n\r\n// If needed, you can destroy Beskydy instance.\r\n// This will remove event listeners, all reactive bindings\r\n// and turn the DOM back into being static\r\napp.teardown()\r\n```\r\n\r\nBeskydy offers a flexible and very extensible API. You can creative as many new directives as you please or extend model & event modifiers. Below are a few examples.\r\n\r\n```ts\r\nimport { Beskydy } from '@dolanske/beskydy'\r\n\r\nconst app = new Beskydy()\r\n\r\n// Change the delimiters, which Beskydy uses to collect text content expressions\r\n// Default: {{ & }}\r\napp.setDelimiters('[', ']')\r\n\r\n// Run code once all the scopes have been initialized\r\napp.onInit(() => {})\r\n\r\n// Run code when the app instance is closed\r\napp.onTeardown(() => {})\r\n\r\n// Add custom directives\r\n// This directive will append HIHI after the provided text property\r\napp.defineDirective('x-funny', (ctx, node, attr) => {\r\n  // Usage\r\n  // <div x-data=\"{ text: 'hello' }\">\r\n  // -> <span x-funny=\"text\"></span>\r\n\r\n  // Whenever a reactive property is updated (text), this function is ran\r\n  ctx.effect(() => {\r\n    // Eval returns a value from a string we provide\r\n    // In our example, attr.value is a \"text\",\r\n    // which means we're referencing a value defined in the data object\r\n    const value = ctx.eval(attr.value)\r\n\r\n    // Here's the funny, we add HAHAHA before the text value\r\n    // Result is <span>hello HIHI</span>\r\n    node.textContent = `${String(value)} HIHI`\r\n\r\n    // If the text property is changed to 'world' (or anything else)\r\n    // This element will automatically update\r\n    // <span>world HIHI</span>\r\n  })\r\n})\r\n\r\n// Add custom event modifier. Read more about modifiers in the `x-on` section below.\r\n// The modifier must return true for the event handler to run.\r\napp.defineEventModifier('save', (event, customState, param) => {\r\n  localStorage.setItem(String(param), String(event.data))\r\n  return true\r\n})\r\n\r\n// Add custom `x-model` modifier. Also supports modifiers with parameters\r\napp.defineModelModifier('toLowerCase', (newValue, oldValue, param) => {\r\n  return String(newValue).toLowerCase()\r\n})\r\n```\r\n\r\nCustom directives receive the scope's `Context`. Besides `ctx.effect` and `ctx.eval`, the context offers `ctx.data` (the reactive dataset), `ctx.cleanup(fn)` to register a function which runs when the app is torn down, and `ctx.define({ ... })` to add properties to the scope.\r\n\r\n## Expressions\r\n\r\nEach expression is a piece of code that gets evaluated. Because it's all in a string, you don't have autocomplete available. Expessions expose a few additional properties, which you can use to your advantage.\r\n\r\n- `$el`: Expose the current element we're writing expression for\r\n- `$event`: Expose the event, if used within event listeners\r\n- `$refs`: Expose the scope's element refs\r\n\r\nInline expressions can be added to a text content of any element. You need to wrap these with delimiters `{{ test }}`, and they do not expose anything, but it's the best way to add reactive pieces into text.\r\n\r\n## Directives\r\n\r\nThere are 18 directives in total. Each simplifying the way we can interact or update the DOM.\r\n\r\n[x-scope](#x-scope) • [x-data](#x-data) • [x-if](#x-if-x-else-if-x-else) • [x-switch](#x-switch) • [x-show](#x-show) • [x-for](#x-for) • [x-portal](#x-portal) • [x-spy](#x-spy) • [x-ref](#x-ref) • [x-on](#x-on) • [x-model](#x-model) • [x-bind](#x-bind) • [x-class](#x-class) • [x-style](#x-style) • [x-text](#x-text) • [x-html](#x-html) • [x-init](#x-init) • [x-processed](#x-processed)\r\n\r\n### `x-scope`\r\n\r\nInitializes a reactive scope. Every directive only works if it exists within a scope.\r\n\r\n```html\r\n<div x-scope=\"{ initialData: 0, count: 10 }\">\r\n  ...\r\n</div>\r\n\r\n```\r\n\r\n### `x-data`\r\n\r\nAppend data into scope's dataset. Every `x-data` within a scope is registered before any other directive runs, so its properties are available to the whole scope, including elements which come before it in the markup.\r\n\r\n```html\r\n<div x-scope=\"{ initialData: 0, count: 10 }\">\r\n  <div x-data=\"{ someMoreData: 'hello' }\">\r\n    ...\r\n  </div>\r\n</div>\r\n```\r\n\r\n### `x-if`, `x-else-if`, `x-else`\r\n\r\nConditionally render elements based on the expression results.\r\n\r\n```html\r\n<div x-scope=\"{ count: 0 }\">\r\n  <span x-if=\"count < 3\">Less than 3</span>\r\n  <span x-else-if=\"count >= 3 && count <= 6\">Between and including 3 and 6</span>\r\n  <span x-else>More than 6</span>\r\n</div>\r\n```\r\n\r\n### `x-switch`\r\n\r\nCleaner way to write many of conditional statements for a single reactive value.\r\n\r\n```html\r\n<div x-scope=\"{ htmlNodeType: 1 }\">\r\n  <div x-switch=\"htmlNodeType\">\r\n    <span x-case=\"1\">Element Node</span>\r\n    <span x-case=\"2\">Attribute Node</span>\r\n    <span x-case=\"3\">Text node</span>\r\n    <span x-case=\"11\">Document Fragment</span>\r\n    <span x-default>Other nodes</span>\r\n  </div>\r\n</div>\r\n\r\n```\r\n\r\n### `x-show`\r\n\r\nShow or hide the element based on the expression result. It adds `display:none` when hiding and reverts back to the original setting of the `display` property.\r\n\r\n```html\r\n<div x-scope=\"{ visible: false }\">\r\n  <p x-show=\"visible\">I am still in the DOM but just hidden</p>\r\n</div>\r\n```\r\n\r\n### `x-for`\r\n\r\nRender list of elements based on the provided array, object or range. Both `in` and `of` are accepted. The rendered elements are placed where the template element was, so surrounding siblings are kept.\r\n\r\nEach item gets its own scope holding the iteration variables. Everything else is inherited from the parent scope, and assignments to parent properties (for example `@click=\"selected = item\"`) update the parent.\r\n\r\n#### Range\r\n\r\n```html\r\n<ul x-scope=\"{ items: 10 }\">\r\n  <li x-for=\"item in items\">{{item + 1}}</li>\r\n</ul>\r\n```\r\n\r\n#### Array\r\n\r\nIterator exposes the property and the index.\r\n\r\n```html\r\n<ul x-scope=\"{ people: ['Jan', 'Andrew', 'Jokum', 'Anton'] }\">\r\n  <li x-for=\"(name, personIndex) in people\">{{ personIndex + 1 }} {{ name }}</li>\r\n</ul>\r\n```\r\n\r\n#### Object\r\n\r\nIterator exposes the property, property key and the index.\r\n\r\n```html\r\n<table x-scope=\"{ people: { name: 'Jan', age: 52 } }\">\r\n  <tr x-for=\"(value, key) in people\">\r\n    <th>{{ key }}</th>\r\n    <td>{{ value }}</td>\r\n  </tr>\r\n</table>\r\n```\r\n\r\n### `x-portal`\r\n\r\nAllows you to move piece of a scope anywhere in the DOM, while retaining its reactive context.\r\n\r\n```html\r\n<!-- Original scope -->\r\n<div x-scope=\"{ text: 'Hello World' }\">\r\n  <input type=\"text\" x-model=\"text\">\r\n  <div class=\"wrapper blue\">\r\n    <div>\r\n      <span x-portal=\"#target\" x-data=\"{ append: ' hehe' }\">{{ text + append }}</span>\r\n    </div>\r\n  </div>\r\n</div>\r\n\r\n<!-- Anywhere else in the DOM -->\r\n<div class=\"wrapper red\" id=\"target\" />\r\n```\r\n\r\nThe `<span>` will act like it's always been part of the `#target` element, but it'll have access to all the properties defined within the original scope.\r\n\r\n### `x-spy`\r\n\r\nAllows you to execute a provided callback whenever the reactive scope is updated. Without parameters the spy reacts to any change, no matter how deeply nested. The callback also runs once when the scope is initialized.\r\n\r\n```html\r\n<div x-scope=\"{ first: 1 }\">\r\n  <button @click=\"first++\" x-spy=\"console.log('Updated!', first)\">Increment</button>\r\n</div>\r\n```\r\n\r\nIf you want to spy on a specific property, you can add its key as a parameter to `x-spy`. Just note, this way you can only watch for changes in the top-level properties in your scope.\r\n\r\n```html\r\n<div x-scope=\"{ first: 1, second: 10 }\">\r\n  <button @click=\"first++\">Increment First</button>\r\n  <button @click=\"second++\">Increment Second</button>\r\n\r\n  <!-- The spy callback only runs if the `second` property is updated -->\r\n  <div x-spy:second=\"console.log('Updated second', second)\"></div>\r\n</div>\r\n```\r\n\r\n### `x-ref`\r\n\r\nSaves the element to the `$refs` object which is available in the scope. Any changes made to the ref element will trigger reactive updates.\r\n\r\n```html\r\n<div x-scope=\"{ text: 'Hello' }\">\r\n  <input x-model=\"text\" />\r\n    <!-- When we change the input, the ref element's textContent is updated -->\r\n    <span x-ref=\"item\">{{ text }}</span>\r\n    <!-- $refs object is updated whenever the element is modified -->\r\n    <span>{{ $refs.item.textContent }}</span>\r\n  </div>\r\n</div>\r\n```\r\n\r\n### `x-on`\r\n\r\nBinds an event listener with optional modifiers.\r\n\r\n```html\r\n<div x-on:eventName.modifier.modifier=\"expression\" />\r\n<div @eventName.modifier.modifier=\"expression\" />\r\n```\r\n\r\n```html\r\n<div x-scope=\"{ open: false }\">\r\n  <button x-on:click=\"open = !open\">Toggle</button>\r\n  <p x-if=\"open\">I am visible</p>\r\n</div>\r\n```\r\n\r\nIf the expression is a reference to a function, the function is called with the event as its argument.\r\n\r\n```html\r\n<div x-scope=\"{ submit(event) { console.log(event.type) } }\">\r\n  <button @click=\"submit\">Submit</button>\r\n</div>\r\n```\r\n\r\n#### Modifiers\r\n\r\n- **once**: Runs only once\r\n- **self**: Runs the expression only if `event.target` equals to `event.currentTarget`\r\n- **left**, **middle**, **right**: Filter mouse clicks\r\n- **prevent**: Runs `event.preventDefault()`\r\n- **stop**: Runs `event.stopPropagation()`\r\n- **stopImmediate**: Runs `event.stopImmediatePropagation()`\r\n\r\n#### Modifiers with parameters\r\n\r\nYou can provide a single parameter to the modifier using this syntax. You can also pass in a property defined in the `x-scope` or `x-data` directives. Note: it does not accept expressions, only variables/primitive values.\r\n\r\n```html\r\n<button x-on:click.only[5]=\"doNothingAfterFiveClicks()\" />\r\n\r\n<div x-scope=\"{ limit: 5 }\">\r\n  <button x-on:click.only[limit]=\"doNothingAfterFiveClicks()\" />\r\n</div>\r\n```\r\n\r\n- **only** (default=1): Run until the provided amount is reached\r\n- **if**: Runs if the provided value is truthy. The value is read again on every event, so it can be a reactive property\r\n- **throttle**: (default=300) Limits the amount of calls within the specified timeframe in milliseconds\r\n\r\nCall counters and timestamps only advance when the handler actually ran, so a throttled or blocked event never counts against `only`.\r\n\r\n### `x-model`\r\n\r\nProvides two way data binding to a input/textarea/select/details. It listens to an input event as well as binding the reactive data to the element's value/state.\r\n\r\n```html\r\n<div x-scope=\"{ text: 'Hello' }\">\r\n  <!-- The input will start by having \"Hello\" written within. Any change to the input from said element will update the reactive `text` property -->\r\n  <input x-model=\"text\" />\r\n</div>\r\n```\r\n\r\nThe following example works exactly the same as the one above.\r\n\r\n```html\r\n<input x-on:input=\"text = $event.target.value\" x-bind:value=\"text\" />\r\n```\r\n\r\nThe bound expression can be a nested path such as `x-model=\"form.name\"`. Modifiers can be chained and run in order, `x-model.trim.number=\"age\"`. The built-in modifiers are **trim** and **number**.\r\n\r\nElement specific behaviour:\r\n\r\n- **checkbox**: bound to an array, the checkbox `value` is added or removed. Without a `value` attribute the checked state is stored as a boolean. With a `value` attribute the value is stored when checked and `null` otherwise.\r\n- **radio**: stores the `value` of the checked radio.\r\n- **select**: stores the selected option's value. A `<select multiple>` stores an array of the selected values.\r\n- **details**: stores the open state as a boolean.\r\n\r\nIf the model property is empty on initialization, the element's own `value` (or `checked`) attribute is used as the initial value.\r\n\r\n### `x-bind`\r\n\r\nBinds an attribute or an attribute object to an element. Attributes bound to `null`, `undefined` or `false` are removed. `:class` and `:style` behave exactly like `x-class` and `x-style` below.\r\n\r\n```html\r\n<div x-scope=\"{ isDisabled: false }\">\r\n  <div x-bind:disabled=\"isDisabled\" />\r\n  <div :disabled=\"isDisabled\" />\r\n  <div x-bind=\"{\r\n    disabled: isDisabled,\r\n    class: isDisabled ? 'is-disabled' : 'is-enabled'\r\n  }\" />\r\n</div>\r\n```\r\n\r\n### `x-class`\r\n\r\nBinds a class or class list to an element. The expression can evaluate to a string (multiple classes separated by spaces), an object whose truthy keys are applied, or an array of both. Classes which are no longer part of the result are removed.\r\n\r\n```html\r\n<div x-scope=\"{ visible: true, isActive: false }\">\r\n  <!-- Inline -->\r\n  <p x-class=\"visible ? 'is-visible' : null\"></p>\r\n  <!-- Object syntax -->\r\n  <p x-class=\"{ 'is-visible': visible }\"></p>\r\n  <!-- Array syntax (combines both previous ones) -->\r\n  <p x-class=\"[{ 'is-visible': visible }, isActive ? 'active' : null]\"></p>\r\n</div>\r\n\r\n```\r\n\r\n### `x-style`\r\n\r\nBinds reactive style object to an element. The properties can be written both in camel case and kebab case. Properties which disappear from the object are removed from the element.\r\n\r\n```html\r\n<div x-scope=\"{ offset: 10 }\">\r\n  <div class=\"ellipse\" :style=\"{ top: offset + '%' }\">\r\n</div>\r\n```\r\n\r\n### `x-text`\r\n\r\nUpdate the element's `textContent`\r\n\r\n```html\r\n<div x-scope=\"{ count: 5 }\">\r\n  <span x-text=\"`The count is ${count}`\"></span>\r\n</div>\r\n```\r\n\r\nNote, you can get the same result when writing expressions within the delimiters anywhere in the scope. Both of these examples have the exact same result.\r\n\r\n```html\r\n<div x-scope=\"{ count: 5 }\">\r\n  <span>The count is {{ count }}</span>\r\n</div>\r\n```\r\n\r\n**Note**\r\nWhen using `x-text`, the entire element's `textContent` as well as any of its child elements will be overwritten by the provided expression.\r\n\r\n### `x-html`\r\n\r\nSame as with `x-text`, but sets the `element.innerHTML` instead.\r\n\r\n```html\r\n<div x-scope=\"{ data: '<span>some fetched html</span>' }\">\r\n  <div class='conten-wrapper' x-html=\"data\"></div>\r\n</div>\r\n```\r\n\r\n### `x-init`\r\n\r\nRuns the provided expression when the scope's data has been registered, before any other directive on the element. The order of attributes in the markup does not matter.\r\nIf you want to run some code when the entire app instance has been initialized, use the `app.onInit` hook instead.\r\n\r\n```html\r\n<div x-scope=\"{ scopeLoaded: false }\" x-init=\"scopeLoaded = true\">\r\n  Loaded {{ scopeLoaded }}\r\n</div>\r\n```\r\n\r\n### `x-processed`\r\n\r\nRuns the provided expression when all of the element's directives have been processed, regardless of the attribute order.\r\n\r\n```html\r\n<div x-scope=\"{ scopeProcessed: false }\" x-processed=\"scopeProcessed = true\">\r\n  Loaded {{ scopeProcessed }}\r\n</div>\r\n```\r\n","readmeFilename":"README.md"}