BrainMinder/assets/static/datastar/datastar.json

1 line
No EOL
62 KiB
JSON

{"hash":"451cf4728ff6863d","sourceSize":40057,"sourceSizeGzipped":14935,"compileTime":"30.465368ms","name":"datastar-1-0-0-beta-11-451cf4728ff6863d","downloadURL":"/bundler/download/datastar-1-0-0-beta-11-451cf4728ff6863d.zip","manifest":{"version":"1.0.0-beta.11","plugins":[{"label":"DELETE","name":"DELETE","path":"../plugins/official/backend/actions/delete","type":"","author":"","slug":"Use a DELETE request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"material-symbols:delete-outline","key":"static_library_source_plugins_official_backend_actions_delete_ts","contents":"// Icon: material-symbols:delete-outline\n// Slug: Use a DELETE request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n type ActionPlugin,\n PluginType,\n type RuntimeContext,\n} from '../../../../engine/types'\nimport { type SSEArgs, sse } from './sse'\n\nexport const DELETE: ActionPlugin = {\n type: PluginType.Action,\n name: 'delete',\n fn: async (ctx: RuntimeContext, url: string, args: SSEArgs) =\u003e {\n return sse(ctx, 'DELETE', url, { ...args })\n },\n}\n"},{"label":"GET","name":"GET","path":"../plugins/official/backend/actions/get","type":"","author":"","slug":"Use a GET request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"ic:baseline-get-app","key":"static_library_source_plugins_official_backend_actions_get_ts","contents":"// Icon: ic:baseline-get-app\n// Slug: Use a GET request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n type ActionPlugin,\n PluginType,\n type RuntimeContext,\n} from '../../../../engine/types'\nimport { type SSEArgs, sse } from './sse'\n\nexport const GET: ActionPlugin = {\n type: PluginType.Action,\n name: 'get',\n fn: async (ctx: RuntimeContext, url: string, args: SSEArgs) =\u003e {\n return sse(ctx, 'GET', url, { ...args })\n },\n}\n"},{"label":"PATCH","name":"PATCH","path":"../plugins/official/backend/actions/patch","type":"","author":"","slug":"Use a PATCH request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"fluent:patch-24-filled","key":"static_library_source_plugins_official_backend_actions_patch_ts","contents":"// Icon: fluent:patch-24-filled\n// Slug: Use a PATCH request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n type ActionPlugin,\n PluginType,\n type RuntimeContext,\n} from '../../../../engine/types'\nimport { type SSEArgs, sse } from './sse'\n\nexport const PATCH: ActionPlugin = {\n type: PluginType.Action,\n name: 'patch',\n fn: async (ctx: RuntimeContext, url: string, args: SSEArgs) =\u003e {\n return sse(ctx, 'PATCH', url, { ...args })\n },\n}\n"},{"label":"POST","name":"POST","path":"../plugins/official/backend/actions/post","type":"","author":"","slug":"Use a POST request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"ri:signpost-fill","key":"static_library_source_plugins_official_backend_actions_post_ts","contents":"// Icon: ri:signpost-fill\n// Slug: Use a POST request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n type ActionPlugin,\n PluginType,\n type RuntimeContext,\n} from '../../../../engine/types'\nimport { type SSEArgs, sse } from './sse'\n\nexport const POST: ActionPlugin = {\n type: PluginType.Action,\n name: 'post',\n fn: async (ctx: RuntimeContext, url: string, args: SSEArgs) =\u003e {\n return sse(ctx, 'POST', url, { ...args })\n },\n}\n"},{"label":"PUT","name":"PUT","path":"../plugins/official/backend/actions/put","type":"","author":"","slug":"Use a PUT request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"material-symbols:arrows-input","key":"static_library_source_plugins_official_backend_actions_put_ts","contents":"// Icon: material-symbols:arrows-input\n// Slug: Use a PUT request to fetch data from a server using Server-Sent Events matching the Datastar SDK interface\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n type ActionPlugin,\n PluginType,\n type RuntimeContext,\n} from '../../../../engine/types'\nimport { type SSEArgs, sse } from './sse'\n\nexport const PUT: ActionPlugin = {\n type: PluginType.Action,\n name: 'put',\n fn: async (ctx: RuntimeContext, url: string, args: SSEArgs) =\u003e {\n return sse(ctx, 'PUT', url, { ...args })\n },\n}\n"},{"label":"Indicator","name":"Indicator","path":"../plugins/official/backend/attributes/indicator","type":"","author":"Delaney Gillilan","slug":"Sets the indicator signal used when fetching data via SSE","description":"must be a valid signal name","icon":"material-symbols:clock-loader-60-sharp","key":"static_library_source_plugins_official_backend_attributes_indicator_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:clock-loader-60-sharp\n// Slug: Sets the indicator signal used when fetching data via SSE\n// Description: must be a valid signal name\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { modifyCasing, trimDollarSignPrefix } from '../../../../utils/text'\nimport {\n DATASTAR_SSE_EVENT,\n type DatastarSSEEvent,\n FINISHED,\n STARTED,\n} from '../shared'\n\nexport const Indicator: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'indicator',\n keyReq: Requirement.Exclusive,\n valReq: Requirement.Exclusive,\n onLoad: ({ el, key, mods, signals, value }) =\u003e {\n const signalName = key\n ? modifyCasing(key, mods)\n : trimDollarSignPrefix(value)\n const { signal } = signals.upsertIfMissing(signalName, false)\n const watcher = ((event: CustomEvent\u003cDatastarSSEEvent\u003e) =\u003e {\n const {\n type,\n elId,\n } = event.detail\n if (elId !== el.id) return\n switch (type) {\n case STARTED:\n signal.value = true\n break\n case FINISHED:\n signal.value = false\n // Remove the event listener only when finished, in case the element is removed while the request is still in progress\n document.removeEventListener(DATASTAR_SSE_EVENT, watcher)\n break\n }\n }) as EventListener\n\n document.addEventListener(DATASTAR_SSE_EVENT, watcher)\n },\n}\n"},{"label":"ExecuteScript","name":"ExecuteScript","path":"../plugins/official/backend/watchers/executeScript","type":"","author":"Delaney Gillilan","slug":"Execute JavaScript using a Server-Sent Event","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"tabler:file-type-js","key":"static_library_source_plugins_official_backend_watchers_execute_script_ts","contents":"// Authors: Delaney Gillilan\n// Icon: tabler:file-type-js\n// Slug: Execute JavaScript using a Server-Sent Event\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n DefaultExecuteScriptAttributes,\n DefaultExecuteScriptAutoRemove,\n EventTypes,\n} from '../../../../engine/consts'\nimport { initErr } from '../../../../engine/errors'\nimport { PluginType, type WatcherPlugin } from '../../../../engine/types'\nimport { isBoolString } from '../../../../utils/text'\nimport { datastarSSEEventWatcher } from '../shared'\n\nexport const ExecuteScript: WatcherPlugin = {\n type: PluginType.Watcher,\n name: EventTypes.ExecuteScript,\n onGlobalInit: async (ctx) =\u003e {\n datastarSSEEventWatcher(\n EventTypes.ExecuteScript,\n ({\n autoRemove: autoRemoveRaw = `${DefaultExecuteScriptAutoRemove}`,\n attributes: attributesRaw = DefaultExecuteScriptAttributes,\n script,\n }) =\u003e {\n const autoRemove = isBoolString(autoRemoveRaw)\n if (!script?.length) {\n throw initErr('NoScriptProvided', ctx)\n }\n const scriptEl = document.createElement('script')\n for (const attr of attributesRaw.split('\\n')) {\n const pivot = attr.indexOf(' ')\n const key = pivot ? attr.slice(0, pivot) : attr\n const value = pivot ? attr.slice(pivot) : ''\n scriptEl.setAttribute(key.trim(), value.trim())\n }\n scriptEl.text = script\n document.head.appendChild(scriptEl)\n if (autoRemove) {\n scriptEl.remove()\n }\n },\n )\n },\n}\n"},{"label":"MergeFragments","name":"MergeFragments","path":"../plugins/official/backend/watchers/mergeFragments","type":"","author":"Delaney Gillilan","slug":"Merge fragments into the DOM using a Server-Sent Event","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"material-symbols:sim-card-download-outline","key":"static_library_source_plugins_official_backend_watchers_merge_fragments_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:sim-card-download-outline\n// Slug: Merge fragments into the DOM using a Server-Sent Event\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n DefaultFragmentMergeMode,\n DefaultFragmentsUseViewTransitions,\n EventTypes,\n FragmentMergeModes,\n} from '../../../../engine/consts'\nimport { initErr } from '../../../../engine/errors'\nimport {\n type HTMLorSVGElement,\n type InitContext,\n PluginType,\n type WatcherPlugin,\n} from '../../../../engine/types'\nimport { attrHash, elUniqId, walkDOM } from '../../../../utils/dom'\nimport { isBoolString } from '../../../../utils/text'\nimport {\n docWithViewTransitionAPI,\n supportsViewTransitions,\n} from '../../../../utils/view-transtions'\nimport { Idiomorph } from '../../../../vendored/idiomorph.esm'\nimport { datastarSSEEventWatcher } from '../shared'\n\nexport const MergeFragments: WatcherPlugin = {\n type: PluginType.Watcher,\n name: EventTypes.MergeFragments,\n onGlobalInit: async (ctx) =\u003e {\n const fragmentContainer = document.createElement('template')\n datastarSSEEventWatcher(\n EventTypes.MergeFragments,\n ({\n fragments: fragmentsRaw = '\u003cdiv\u003e\u003c/div\u003e',\n selector = '',\n mergeMode = DefaultFragmentMergeMode,\n useViewTransition:\n useViewTransitionRaw = `${DefaultFragmentsUseViewTransitions}`,\n }) =\u003e {\n const useViewTransition = isBoolString(useViewTransitionRaw)\n\n fragmentContainer.innerHTML = fragmentsRaw.trim()\n const fragments = [...fragmentContainer.content.children]\n for (const fragment of fragments) {\n if (!(fragment instanceof Element)) {\n throw initErr('NoFragmentsFound', ctx)\n }\n\n const selectorOrID = selector || `#${fragment.getAttribute('id')}`\n const targets = [...(document.querySelectorAll(selectorOrID) || [])]\n if (!targets.length) {\n throw initErr('NoTargetsFound', ctx, { selectorOrID })\n }\n\n if (useViewTransition \u0026\u0026 supportsViewTransitions) {\n docWithViewTransitionAPI.startViewTransition(() =\u003e\n applyToTargets(ctx, mergeMode, fragment, targets),\n )\n } else {\n applyToTargets(ctx, mergeMode, fragment, targets)\n }\n }\n },\n )\n },\n}\n\nfunction applyToTargets(\n ctx: InitContext,\n mergeMode: string,\n fragment: Element,\n capturedTargets: Element[],\n) {\n for (const target of capturedTargets) {\n // Mark the target as a fragment merge target to force plugins to clean up and reapply\n (target as HTMLElement).dataset.fragmentMergeTarget = 'true'\n\n // Clone the fragment to merge to avoid modifying the original and force browsers to merge the fragment into the DOM\n const fragmentToMerge = fragment.cloneNode(true) as HTMLorSVGElement\n\n switch (mergeMode) {\n case FragmentMergeModes.Morph: {\n walkDOM(fragmentToMerge, (el) =\u003e {\n if (!el.id?.length \u0026\u0026 Object.keys(el.dataset).length) {\n el.id = elUniqId(el)\n }\n // Rehash the cleanup functions for this element to ensure that plugins are cleaned up and reapplied after merging.\n const elTracking = ctx.removals.get(el.id)\n if (elTracking) {\n const newElTracking = new Map()\n for (const [key, cleanup] of elTracking) {\n const newKey = attrHash(key, key)\n newElTracking.set(newKey, cleanup)\n elTracking.delete(key)\n }\n ctx.removals.set(el.id, newElTracking)\n }\n })\n\n Idiomorph.morph(target, fragmentToMerge)\n break\n }\n case FragmentMergeModes.Inner:\n // Replace the contents of the target element with the outer HTML of the response\n target.innerHTML = fragmentToMerge.outerHTML\n break\n case FragmentMergeModes.Outer:\n // Replace the entire target element with the response\n target.replaceWith(fragmentToMerge)\n break\n case FragmentMergeModes.Prepend:\n // Insert the response before the first child of the target element\n target.prepend(fragmentToMerge)\n break\n case FragmentMergeModes.Append:\n // Insert the response after the last child of the target element\n target.append(fragmentToMerge)\n break\n case FragmentMergeModes.Before:\n // Insert the response before the target element\n target.before(fragmentToMerge)\n break\n case FragmentMergeModes.After:\n // Insert the response after the target element\n target.after(fragmentToMerge)\n break\n case FragmentMergeModes.UpsertAttributes:\n // Upsert the attributes of the target element\n for (const attrName of fragmentToMerge.getAttributeNames()) {\n const value = fragmentToMerge.getAttribute(attrName)!\n target.setAttribute(attrName, value)\n }\n break\n default:\n throw initErr('InvalidMergeMode', ctx, { mergeMode })\n }\n }\n}\n"},{"label":"MergeSignals","name":"MergeSignals","path":"../plugins/official/backend/watchers/mergeSignals","type":"","author":"Delaney Gillilan","slug":"Merge signals using a Server-Sent Event","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"material-symbols:download-for-offline-outline","key":"static_library_source_plugins_official_backend_watchers_merge_signals_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:download-for-offline-outline\n// Slug: Merge signals using a Server-Sent Event\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n DefaultMergeSignalsOnlyIfMissing,\n EventTypes,\n} from '../../../../engine/consts'\nimport { PluginType, type WatcherPlugin } from '../../../../engine/types'\nimport { isBoolString, jsStrToObject } from '../../../../utils/text'\nimport { datastarSSEEventWatcher } from '../shared'\n\nexport const MergeSignals: WatcherPlugin = {\n type: PluginType.Watcher,\n name: EventTypes.MergeSignals,\n onGlobalInit: async (ctx) =\u003e {\n datastarSSEEventWatcher(\n EventTypes.MergeSignals,\n ({\n signals: raw = '{}',\n onlyIfMissing: onlyIfMissingRaw = `${DefaultMergeSignalsOnlyIfMissing}`,\n }) =\u003e {\n const { signals } = ctx\n const onlyIfMissing = isBoolString(onlyIfMissingRaw)\n signals.merge(jsStrToObject(raw), onlyIfMissing)\n },\n )\n },\n}\n"},{"label":"RemoveFragments","name":"RemoveFragments","path":"../plugins/official/backend/watchers/removeFragments","type":"","author":"Delaney Gillilan","slug":"Remove fragments from the DOM using a Server-Sent Event","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"material-symbols:scan-delete-outline","key":"static_library_source_plugins_official_backend_watchers_remove_fragments_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:scan-delete-outline\n// Slug: Remove fragments from the DOM using a Server-Sent Event\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport {\n DefaultFragmentsUseViewTransitions,\n EventTypes,\n} from '../../../../engine/consts'\nimport { initErr } from '../../../../engine/errors'\nimport { PluginType, type WatcherPlugin } from '../../../../engine/types'\nimport { isBoolString } from '../../../../utils/text'\nimport {\n docWithViewTransitionAPI,\n supportsViewTransitions,\n} from '../../../../utils/view-transtions'\nimport { datastarSSEEventWatcher } from '../shared'\n\nexport const RemoveFragments: WatcherPlugin = {\n type: PluginType.Watcher,\n name: EventTypes.RemoveFragments,\n onGlobalInit: async (ctx) =\u003e {\n datastarSSEEventWatcher(\n EventTypes.RemoveFragments,\n ({\n selector,\n useViewTransition:\n useViewTransitionRaw = `${DefaultFragmentsUseViewTransitions}`,\n }) =\u003e {\n if (!selector.length) {\n throw initErr('NoSelectorProvided', ctx)\n }\n\n const useViewTransition = isBoolString(useViewTransitionRaw)\n const removeTargets = document.querySelectorAll(selector)\n\n const applyToTargets = () =\u003e {\n for (const target of removeTargets) {\n target.remove()\n }\n }\n\n if (useViewTransition \u0026\u0026 supportsViewTransitions) {\n docWithViewTransitionAPI.startViewTransition(() =\u003e applyToTargets())\n } else {\n applyToTargets()\n }\n },\n )\n },\n}\n"},{"label":"RemoveSignals","name":"RemoveSignals","path":"../plugins/official/backend/watchers/removeSignals","type":"","author":"Delaney Gillilan","slug":"Remove signals using a Server-Sent Event","description":"Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.","icon":"material-symbols:variable-remove-outline","key":"static_library_source_plugins_official_backend_watchers_remove_signals_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:variable-remove-outline\n// Slug: Remove signals using a Server-Sent Event\n// Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client.\n\nimport { EventTypes } from '../../../../engine/consts'\nimport { initErr } from '../../../../engine/errors'\nimport { PluginType, type WatcherPlugin } from '../../../../engine/types'\nimport { datastarSSEEventWatcher } from '../shared'\n\nexport const RemoveSignals: WatcherPlugin = {\n type: PluginType.Watcher,\n name: EventTypes.RemoveSignals,\n onGlobalInit: async (ctx) =\u003e {\n datastarSSEEventWatcher(\n EventTypes.RemoveSignals,\n ({ paths: pathsRaw = '' }) =\u003e {\n const paths = pathsRaw.split('\\n').map((p) =\u003e p.trim())\n if (!paths?.length) {\n throw initErr('NoPathsProvided', ctx)\n }\n ctx.signals.remove(...paths)\n },\n )\n },\n}\n"},{"label":"Clipboard","name":"Clipboard","path":"../plugins/official/browser/actions/clipboard","type":"","author":"Delaney Gillilan","slug":"Copy text to the clipboard","description":"This action copies text to the clipboard using the Clipboard API.","icon":"mdi:clipboard","key":"static_library_source_plugins_official_browser_actions_clipboard_ts","contents":"// Authors: Delaney Gillilan\n// Icon: mdi:clipboard\n// Slug: Copy text to the clipboard\n// Description: This action copies text to the clipboard using the Clipboard API.\n\nimport { runtimeErr } from '../../../../engine/errors'\nimport { type ActionPlugin, PluginType } from '../../../../engine/types'\n\nexport const Clipboard: ActionPlugin = {\n type: PluginType.Action,\n name: 'clipboard',\n fn: (ctx, text) =\u003e {\n if (!navigator.clipboard) {\n throw runtimeErr('ClipboardNotAvailable', ctx)\n }\n navigator.clipboard.writeText(text)\n },\n}\n"},{"label":"CustomValidity","name":"CustomValidity","path":"../plugins/official/browser/attributes/customValidity","type":"","author":"Ben Croker","slug":"Add custom validity to an element using an expression","description":"This plugin allows you to add custom validity to an element using an expression. The expression should evaluate to a string that will be set as the custom validity message. This can be used to provide custom error messages for form validation.","icon":"mdi-message-alert","key":"static_library_source_plugins_official_browser_attributes_custom_validity_ts","contents":"// Authors: Ben Croker\n// Icon: mdi-message-alert\n// Slug: Add custom validity to an element using an expression\n// Description: This plugin allows you to add custom validity to an element using an expression. The expression should evaluate to a string that will be set as the custom validity message. This can be used to provide custom error messages for form validation.\n\nimport { runtimeErr } from '../../../../engine/errors'\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\n\nexport const CustomValidity: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'customValidity',\n keyReq: Requirement.Denied,\n valReq: Requirement.Must,\n onLoad: (ctx) =\u003e {\n const { el, genRX, effect } = ctx\n if (!(el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement)) {\n throw runtimeErr('CustomValidityInvalidElement', ctx)\n }\n const rx = genRX()\n return effect(() =\u003e {\n const result = rx\u003cstring\u003e()\n if (typeof result !== 'string') {\n throw runtimeErr('CustomValidityInvalidExpression', ctx, { result })\n }\n el.setCustomValidity(result)\n })\n },\n}\n"},{"label":"OnIntersect","name":"OnIntersect","path":"../plugins/official/browser/attributes/onIntersect","type":"","author":"Delaney Gillilan","slug":"Executes an expression when an element intersects with the viewport","description":"An attribute that executes an expression when an element intersects with the viewport.","icon":"mdi-light:vector-intersection","key":"static_library_source_plugins_official_browser_attributes_on_intersect_ts","contents":"// Authors: Delaney Gillilan\n// Icon: mdi-light:vector-intersection\n// Slug: Executes an expression when an element intersects with the viewport\n// Description: An attribute that executes an expression when an element intersects with the viewport.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { modifyTiming } from '../../../../utils/timing'\nimport { modifyViewTransition } from '../../../../utils/view-transtions'\n\nexport const OnIntersect: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'onIntersect',\n keyReq: Requirement.Denied,\n onLoad: ({ el, rawKey, mods, genRX }) =\u003e {\n let callback = modifyTiming(genRX(), mods)\n callback = modifyViewTransition(callback, mods)\n\n const options = { threshold: 0 }\n if (mods.has('full')) {\n options.threshold = 1\n } else if (mods.has('half')) {\n options.threshold = 0.5\n }\n\n const observer = new IntersectionObserver((entries) =\u003e {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n callback()\n\n if (mods.has('once')) {\n observer.disconnect()\n delete el.dataset[rawKey]\n }\n }\n }\n }, options)\n \n observer.observe(el)\n\n return () =\u003e observer.disconnect()\n },\n}\n"},{"label":"OnInterval","name":"OnInterval","path":"../plugins/official/browser/attributes/onInterval","type":"","author":"Ben Croker","slug":"Runs an expression on an interval","description":"This attribute runs an expression on an interval. The interval can be set to a specific duration, and can be set to trigger immediately.","icon":"material-symbols:timer-outline","key":"static_library_source_plugins_official_browser_attributes_on_interval_ts","contents":"// Authors: Ben Croker\n// Icon: material-symbols:timer-outline\n// Slug: Runs an expression on an interval\n// Description: This attribute runs an expression on an interval. The interval can be set to a specific duration, and can be set to trigger immediately.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { tagHas, tagToMs } from '../../../../utils/tags'\nimport { modifyViewTransition } from '../../../../utils/view-transtions'\n\nexport const OnInterval: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'onInterval',\n keyReq: Requirement.Denied,\n valReq: Requirement.Must,\n onLoad: ({ mods, genRX }) =\u003e {\n const callback = modifyViewTransition(genRX(), mods)\n\n let duration = 1000\n const durationArgs = mods.get('duration')\n if (durationArgs) {\n duration = tagToMs(durationArgs)\n const leading = tagHas(durationArgs, 'leading', false)\n if (leading) {\n callback()\n }\n }\n\n const intervalId = setInterval(callback, duration)\n\n return () =\u003e {\n clearInterval(intervalId)\n }\n },\n}\n"},{"label":"OnLoad","name":"OnLoad","path":"../plugins/official/browser/attributes/onLoad","type":"","author":"Ben Croker","slug":"Runs an expression when the element is loaded","description":"This attribute runs an expression when the element is loaded.","icon":"material-symbols:timer-play-outline","key":"static_library_source_plugins_official_browser_attributes_on_load_ts","contents":"// Authors: Ben Croker\n// Icon: material-symbols:timer-play-outline\n// Slug: Runs an expression when the element is loaded\n// Description: This attribute runs an expression when the element is loaded.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { tagToMs } from '../../../../utils/tags'\nimport { modifyViewTransition } from '../../../../utils/view-transtions'\n\nexport const OnLoad: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'onLoad',\n keyReq: Requirement.Denied,\n valReq: Requirement.Must,\n onLoad: ({ mods, genRX }) =\u003e {\n const callback = modifyViewTransition(genRX(), mods)\n\n let wait = 0\n const delayArgs = mods.get('delay')\n if (delayArgs) {\n wait = tagToMs(delayArgs)\n }\n\n setTimeout(callback, wait)\n\n return () =\u003e {}\n },\n}\n"},{"label":"OnRaf","name":"OnRaf","path":"../plugins/official/browser/attributes/onRaf","type":"","author":"Ben Croker","slug":"Runs an expression on every animation frame","description":"This attribute runs an expression on every animation frame.","icon":"material-symbols:animated-images-outline","key":"static_library_source_plugins_official_browser_attributes_on_raf_ts","contents":"// Authors: Ben Croker\n// Icon: material-symbols:animated-images-outline\n// Slug: Runs an expression on every animation frame\n// Description: This attribute runs an expression on every animation frame.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { modifyTiming } from '../../../../utils/timing'\nimport { modifyViewTransition } from '../../../../utils/view-transtions'\n\nexport const OnRaf: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'onRaf',\n keyReq: Requirement.Denied,\n valReq: Requirement.Must,\n onLoad: ({ mods, genRX }) =\u003e {\n let callback = modifyTiming(genRX(), mods)\n callback = modifyViewTransition(callback, mods)\n\n let rafId: number | undefined\n const raf = () =\u003e {\n callback()\n rafId = requestAnimationFrame(raf)\n }\n rafId = requestAnimationFrame(raf)\n\n return () =\u003e {\n if (rafId) {\n cancelAnimationFrame(rafId)\n }\n }\n },\n}\n"},{"label":"OnSignalChange","name":"OnSignalChange","path":"../plugins/official/browser/attributes/onSignalChange","type":"","author":"Ben Croker","slug":"Runs an expression whenever a signal changes","description":"This attribute runs an expression whenever a signal changes.","icon":"material-symbols:bigtop-updates","key":"static_library_source_plugins_official_browser_attributes_on_signal_change_ts","contents":"// Authors: Ben Croker\n// Icon: material-symbols:bigtop-updates\n// Slug: Runs an expression whenever a signal changes\n// Description: This attribute runs an expression whenever a signal changes. \n\nimport {\n type AttributePlugin,\n DATASTAR_SIGNAL_EVENT,\n type DatastarSignalEvent,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { pathMatchesPattern } from '../../../../utils/paths'\nimport { modifyCasing } from '../../../../utils/text'\nimport { modifyTiming } from '../../../../utils/timing'\nimport { modifyViewTransition } from '../../../../utils/view-transtions'\nimport { effect, type Signal } from '../../../../vendored/preact-core'\n\nexport const OnSignalChange: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'onSignalChange',\n valReq: Requirement.Must,\n onLoad: ({ key, mods, signals, genRX }) =\u003e {\n let callback = modifyTiming(genRX(), mods)\n callback = modifyViewTransition(callback, mods)\n\n if (key === '') {\n const signalFn = (event: CustomEvent\u003cDatastarSignalEvent\u003e) =\u003e\n callback(event)\n document.addEventListener(DATASTAR_SIGNAL_EVENT, signalFn)\n\n return () =\u003e {\n document.removeEventListener(DATASTAR_SIGNAL_EVENT, signalFn)\n }\n }\n\n const pattern = modifyCasing(key, mods)\n const signalValues = new Map\u003cSignal, any\u003e()\n signals.walk((path, signal) =\u003e {\n if (pathMatchesPattern(path, pattern)) {\n signalValues.set(signal, signal.value)\n }\n })\n\n return effect(() =\u003e {\n for (const [signal, prev] of signalValues) {\n if (prev !== signal.value) {\n callback()\n signalValues.set(signal, signal.value)\n }\n }\n })\n },\n}\n"},{"label":"Persist","name":"Persist","path":"../plugins/official/browser/attributes/persist","type":"","author":"Delaney Gillilan","slug":"Persist data to local storage or session storage","description":"This plugin allows you to persist data to local storage or session storage. Once you add this attribute the data will be persisted to local storage or session storage.","icon":"mdi:floppy-variant","key":"static_library_source_plugins_official_browser_attributes_persist_ts","contents":"// Authors: Delaney Gillilan\n// Icon: mdi:floppy-variant\n// Slug: Persist data to local storage or session storage\n// Description: This plugin allows you to persist data to local storage or session storage. Once you add this attribute the data will be persisted to local storage or session storage.\n\nimport { DATASTAR } from '../../../../engine/consts'\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { getMatchingSignalPaths } from '../../../../utils/paths'\n\nexport const Persist: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'persist',\n keyReq: Requirement.Denied,\n onLoad: ({ effect, mods, signals, value }) =\u003e {\n const key = DATASTAR\n const storage = mods.has('session') ? sessionStorage : localStorage\n \n // If the value is empty, persist all signals\n const paths = value !== '' ? value : '**'\n\n const storageToSignals = () =\u003e {\n const data = storage.getItem(key) || '{}'\n const nestedValues = JSON.parse(data)\n signals.merge(nestedValues)\n }\n\n const signalsToStorage = () =\u003e {\n const signalPaths = getMatchingSignalPaths(signals, paths)\n const nv = signals.subset(...signalPaths)\n storage.setItem(key, JSON.stringify(nv))\n }\n\n storageToSignals()\n return effect(() =\u003e {\n signalsToStorage()\n })\n },\n}\n"},{"label":"ReplaceUrl","name":"ReplaceUrl","path":"../plugins/official/browser/attributes/replaceUrl","type":"","author":"Delaney Gillilan","slug":"Replace the current URL with a new URL","description":"This plugin allows you to replace the current URL with a new URL. Once you add this attribute the current URL will be replaced with the new URL.","icon":"carbon:url","key":"static_library_source_plugins_official_browser_attributes_replace_url_ts","contents":"// Authors: Delaney Gillilan\n// Icon: carbon:url\n// Slug: Replace the current URL with a new URL\n// Description: This plugin allows you to replace the current URL with a new URL. Once you add this attribute the current URL will be replaced with the new URL.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\n\nexport const ReplaceUrl: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'replaceUrl',\n keyReq: Requirement.Denied,\n valReq: Requirement.Must,\n onLoad: ({ effect, genRX }) =\u003e {\n const rx = genRX()\n return effect(() =\u003e {\n const url = rx\u003cstring\u003e()\n const baseUrl = window.location.href\n const fullUrl = new URL(url, baseUrl).toString()\n window.history.replaceState({}, '', fullUrl)\n })\n },\n}\n"},{"label":"ScrollIntoView","name":"ScrollIntoView","path":"../plugins/official/browser/attributes/scrollIntoView","type":"","author":"Delaney Gillilan","slug":"Scroll an element into view","description":"This attribute scrolls the element into view.","icon":"hugeicons:mouse-scroll-01","key":"static_library_source_plugins_official_browser_attributes_scroll_into_view_ts","contents":"// Authors: Delaney Gillilan\n// Icon: hugeicons:mouse-scroll-01\n// Slug: Scroll an element into view\n// Description: This attribute scrolls the element into view.\n\nimport { runtimeErr } from '../../../../engine/errors'\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\n\nconst SMOOTH = 'smooth'\nconst INSTANT = 'instant'\nconst AUTO = 'auto'\nconst HSTART = 'hstart'\nconst HCENTER = 'hcenter'\nconst HEND = 'hend'\nconst HNEAREST = 'hnearest'\nconst VSTART = 'vstart'\nconst VCENTER = 'vcenter'\nconst VEND = 'vend'\nconst VNEAREST = 'vnearest'\nconst CENTER = 'center'\nconst START = 'start'\nconst END = 'end'\nconst NEAREST = 'nearest'\nconst FOCUS = 'focus'\n\n// Scrolls the element into view\nexport const ScrollIntoView: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'scrollIntoView',\n keyReq: Requirement.Denied,\n valReq: Requirement.Denied,\n onLoad: (ctx) =\u003e {\n const { el, mods, rawKey } = ctx\n if (!el.tabIndex) el.setAttribute('tabindex', '0')\n const opts: ScrollIntoViewOptions = {\n behavior: SMOOTH,\n block: CENTER,\n inline: CENTER,\n }\n if (mods.has(SMOOTH)) opts.behavior = SMOOTH\n if (mods.has(INSTANT)) opts.behavior = INSTANT\n if (mods.has(AUTO)) opts.behavior = AUTO\n if (mods.has(HSTART)) opts.inline = START\n if (mods.has(HCENTER)) opts.inline = CENTER\n if (mods.has(HEND)) opts.inline = END\n if (mods.has(HNEAREST)) opts.inline = NEAREST\n if (mods.has(VSTART)) opts.block = START\n if (mods.has(VCENTER)) opts.block = CENTER\n if (mods.has(VEND)) opts.block = END\n if (mods.has(VNEAREST)) opts.block = NEAREST\n\n if (!(el instanceof HTMLElement || el instanceof SVGElement)) {\n throw runtimeErr('ScrollIntoViewInvalidElement', ctx)\n }\n if (!el.tabIndex) {\n el.setAttribute('tabindex', '0')\n }\n\n el.scrollIntoView(opts)\n if (mods.has(FOCUS)) {\n el.focus()\n }\n\n delete el.dataset[rawKey]\n },\n}\n"},{"label":"ViewTransition","name":"ViewTransition","path":"../plugins/official/browser/attributes/viewTransition","type":"","author":"Delaney Gillilan","slug":"Setup view transitions","description":"This attribute plugin sets up view transitions for the current view. This plugin requires the view transition API to be enabled in the browser. If the browser does not support view transitions, an error will be logged to the console.","icon":"material-symbols:masked-transitions","key":"static_library_source_plugins_official_browser_attributes_view_transition_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:masked-transitions\n// Slug: Setup view transitions\n// Description: This attribute plugin sets up view transitions for the current view. This plugin requires the view transition API to be enabled in the browser. If the browser does not support view transitions, an error will be logged to the console.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { supportsViewTransitions } from '../../../../utils/view-transtions'\n\nconst VIEW_TRANSITION = 'view-transition'\n\nexport const ViewTransition: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'viewTransition',\n keyReq: Requirement.Denied,\n valReq: Requirement.Must,\n onGlobalInit() {\n let hasViewTransitionMeta = false\n for (const node of document.head.childNodes) {\n if (node instanceof HTMLMetaElement \u0026\u0026 node.name === VIEW_TRANSITION) {\n hasViewTransitionMeta = true\n }\n }\n\n if (!hasViewTransitionMeta) {\n const meta = document.createElement('meta')\n meta.name = VIEW_TRANSITION\n meta.content = 'same-origin'\n document.head.appendChild(meta)\n }\n },\n onLoad: ({ effect, el, genRX }) =\u003e {\n if (!supportsViewTransitions) {\n console.error('Browser does not support view transitions')\n return\n }\n const rx = genRX()\n return effect(() =\u003e {\n const name = rx\u003cstring\u003e()\n if (!name?.length) return\n const elVTASTyle = el.style as unknown as CSSStyleDeclaration\n elVTASTyle.viewTransitionName = name\n })\n },\n}\n"},{"label":"Attr","name":"Attr","path":"../plugins/official/dom/attributes/attr","type":"","author":"Delaney Gillilan","slug":"Bind attributes to expressions","description":"Any attribute can be bound to an expression. The attribute will be updated reactively whenever the expression signal changes.","icon":"material-symbols:edit-attributes-outline","key":"static_library_source_plugins_official_dom_attributes_attr_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:edit-attributes-outline\n// Slug: Bind attributes to expressions\n// Description: Any attribute can be bound to an expression. The attribute will be updated reactively whenever the expression signal changes.\n\nimport {\n type AttributePlugin,\n type NestedValues,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { kebab } from '../../../../utils/text'\n\nexport const Attr: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'attr',\n valReq: Requirement.Must,\n onLoad: ({ el, key, effect, genRX }) =\u003e {\n const rx = genRX()\n if (key === '') {\n return effect(async () =\u003e {\n const binds = rx\u003cNestedValues\u003e()\n for (const [key, val] of Object.entries(binds)) {\n if (val === false) {\n el.removeAttribute(key)\n } else {\n el.setAttribute(key, val)\n }\n }\n })\n }\n\n // Attributes are always kebab-case\n key = kebab(key)\n\n return effect(async () =\u003e {\n let value = false\n try {\n value = rx()\n } catch (e) {} //\n let v: string\n if (typeof value === 'string') {\n v = value\n } else {\n v = JSON.stringify(value)\n }\n if (!v || v === 'false' || v === 'null' || v === 'undefined') {\n el.removeAttribute(key)\n } else {\n el.setAttribute(key, v)\n }\n })\n },\n}\n"},{"label":"Bind","name":"Bind","path":"../plugins/official/dom/attributes/bind","type":"","author":"Delaney Gillilan","slug":"Bind attributes to expressions","description":"Any attribute can be bound to an expression. The attribute will be updated reactively whenever the expression signal changes.","icon":"akar-icons:link-chain","key":"static_library_source_plugins_official_dom_attributes_bind_ts","contents":"// Authors: Delaney Gillilan\n// Icon: akar-icons:link-chain\n// Slug: Bind attributes to expressions\n// Description: Any attribute can be bound to an expression. The attribute will be updated reactively whenever the expression signal changes.\n\nimport { runtimeErr } from '../../../../engine/errors'\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { modifyCasing, trimDollarSignPrefix } from '../../../../utils/text'\n\nconst dataURIRegex = /^data:(?\u003cmime\u003e[^;]+);base64,(?\u003ccontents\u003e.*)$/\nconst updateEvents = ['change', 'input', 'keydown']\n\nexport const Bind: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'bind',\n keyReq: Requirement.Exclusive,\n valReq: Requirement.Exclusive,\n onLoad: (ctx) =\u003e {\n const { el, key, mods, signals, value, effect } = ctx\n const input = el as HTMLInputElement\n const signalName = key\n ? modifyCasing(key, mods)\n : trimDollarSignPrefix(value)\n \n const tnl = el.tagName.toLowerCase()\n const isInput = tnl.includes('input')\n const isSelect = tnl.includes('select')\n const type = el.getAttribute('type')\n const hasValueAttribute = el.hasAttribute('value')\n\n let signalDefault: string | boolean | number | File = ''\n const isCheckbox = isInput \u0026\u0026 type === 'checkbox'\n if (isCheckbox) {\n signalDefault = hasValueAttribute ? '' : false\n }\n const isNumber = isInput \u0026\u0026 type === 'number'\n if (isNumber) {\n signalDefault = 0\n }\n const isRadio = isInput \u0026\u0026 type === 'radio'\n if (isRadio) {\n const name = el.getAttribute('name')\n if (!name?.length) {\n el.setAttribute('name', signalName)\n }\n }\n // Can't set a default value for a file input, yet\n const isFile = isInput \u0026\u0026 type === 'file'\n\n const { signal, inserted } = signals.upsertIfMissing(\n signalName,\n signalDefault,\n )\n\n let arrayIndex = -1\n if (Array.isArray(signal.value)) {\n if (el.getAttribute('name') === null) {\n el.setAttribute('name', signalName)\n }\n arrayIndex = [\n ...document.querySelectorAll(`[name=\"${signalName}\"]`),\n ].findIndex((el) =\u003e el === ctx.el)\n }\n const isArray = arrayIndex \u003e= 0\n\n const signalArray = () =\u003e [...(signals.value(signalName) as any[])]\n\n const setElementFromSignal = () =\u003e {\n let value = signals.value(signalName)\n if (isArray \u0026\u0026 !isSelect) {\n // May be undefined if the array is shorter than the index\n value = (value as any)[arrayIndex] || signalDefault\n }\n const stringValue = `${value}`\n if (isCheckbox || isRadio) {\n if (typeof value === 'boolean') {\n input.checked = value\n } else {\n input.checked = stringValue === input.value\n }\n } else if (isSelect) {\n const select = el as HTMLSelectElement\n if (select.multiple) {\n if (!isArray) {\n throw runtimeErr('BindSelectMultiple', ctx)\n }\n for (const opt of select.options) {\n if (opt?.disabled) return\n const incoming = isNumber ? Number(opt.value) : opt.value\n opt.selected = (value as any[]).includes(incoming)\n }\n } else {\n select.value = stringValue\n }\n } else if (isFile) {\n // File input reading from a signal is not supported\n } else if ('value' in el) {\n el.value = stringValue\n } else {\n el.setAttribute('value', stringValue)\n }\n }\n\n const setSignalFromElement = async () =\u003e {\n let currentValue = signals.value(signalName)\n if (isArray) {\n // Push as many default signal values onto the array as necessary to reach the index\n const currentArray = currentValue as any[]\n while (arrayIndex \u003e= currentArray.length) {\n currentArray.push(signalDefault)\n }\n currentValue = currentArray[arrayIndex] || signalDefault\n }\n\n const update = (signalName: string, value: any) =\u003e {\n let newValue = value\n if (isArray \u0026\u0026 !isSelect) {\n newValue = signalArray()\n newValue[arrayIndex] = value\n }\n signals.setValue(signalName, newValue)\n }\n\n // Files are a special flower\n if (isFile) {\n const files = [...(input?.files || [])]\n const allContents: string[] = []\n const allMimes: string[] = []\n const allNames: string[] = []\n\n await Promise.all(\n files.map((f) =\u003e {\n return new Promise\u003cvoid\u003e((resolve) =\u003e {\n const reader = new FileReader()\n reader.onload = () =\u003e {\n if (typeof reader.result !== 'string') {\n throw runtimeErr('InvalidFileResultType', ctx, {\n resultType: typeof reader.result,\n })\n }\n const match = reader.result.match(dataURIRegex)\n if (!match?.groups) {\n throw runtimeErr('InvalidDataUri', ctx, {\n result: reader.result,\n })\n }\n allContents.push(match.groups.contents)\n allMimes.push(match.groups.mime)\n allNames.push(f.name)\n }\n reader.onloadend = () =\u003e resolve(void 0)\n reader.readAsDataURL(f)\n })\n }),\n )\n update(signalName, allContents)\n update(`${signalName}Mimes`, allMimes)\n update(`${signalName}Names`, allNames)\n return\n }\n\n const value = input.value || ''\n let newValue: any\n\n if (isCheckbox) {\n const checked =\n input.checked || input.getAttribute('checked') === 'true'\n\n // We must check for an attribute value because a checked value defaults to `on`.\n if (hasValueAttribute) {\n newValue = checked ? value : ''\n } else {\n newValue = checked\n }\n } else if (isSelect) {\n const select = el as HTMLSelectElement\n const selectedOptions = [...select.selectedOptions]\n if (isArray) {\n newValue = selectedOptions\n .filter((opt) =\u003e opt.selected)\n .map((opt) =\u003e opt.value)\n } else {\n newValue = selectedOptions[0]?.value || signalDefault\n }\n } else if (typeof currentValue === 'boolean') {\n newValue = Boolean(value)\n } else if (typeof currentValue === 'number') {\n newValue = Number(value)\n } else {\n newValue = value || ''\n }\n\n update(signalName, newValue)\n }\n\n // If the signal was inserted, attempt to set the the signal value from the element.\n if (inserted) {\n setSignalFromElement()\n }\n\n for (const event of updateEvents) {\n el.addEventListener(event, setSignalFromElement)\n }\n\n /*\n * The signal value needs to be updated after the \"pageshow\" event.\n * Sometimes, the browser might populate inputs with previous values\n * when navigating between pages using the back/forward navigation.\n *\n * For more information, read about bfcache:\n * https://web.dev/articles/bfcache\n */\n const onPageshow = (ev: PageTransitionEvent) =\u003e {\n if (!ev.persisted) return\n setSignalFromElement()\n }\n window.addEventListener('pageshow', onPageshow)\n\n const reset = effect(() =\u003e setElementFromSignal())\n\n return () =\u003e {\n reset()\n\n for (const event of updateEvents) {\n el.removeEventListener(event, setSignalFromElement)\n }\n \n window.removeEventListener('pageshow', onPageshow)\n }\n },\n}\n"},{"label":"Class","name":"Class","path":"../plugins/official/dom/attributes/class","type":"","author":"Delaney Gillilan","slug":"Add or remove classes from an element reactively","description":"This action adds or removes classes from an element reactively based on the expression provided. The expression should be an object where the keys are the class names and the values are booleans. If the value is true, the class is added. If the value is false, the class is removed.","icon":"ic:baseline-format-paint","key":"static_library_source_plugins_official_dom_attributes_class_ts","contents":"// Authors: Delaney Gillilan\n// Icon: ic:baseline-format-paint\n// Slug: Add or remove classes from an element reactively\n// Description: This action adds or removes classes from an element reactively based on the expression provided. The expression should be an object where the keys are the class names and the values are booleans. If the value is true, the class is added. If the value is false, the class is removed.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { kebab, modifyCasing } from '../../../../utils/text'\n\nexport const Class: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'class',\n valReq: Requirement.Must,\n onLoad: ({ el, key, mods, effect, genRX }) =\u003e {\n const cl = el.classList\n const rx = genRX()\n return effect(() =\u003e {\n if (key === '') {\n const classes = rx\u003cRecord\u003cstring, boolean\u003e\u003e()\n for (const [k, v] of Object.entries(classes)) {\n const classNames = k.split(/\\s+/)\n if (v) {\n cl.add(...classNames)\n } else {\n cl.remove(...classNames)\n }\n }\n } else {\n // Default to kebab-case and allow modifying\n let className = kebab(key)\n className = modifyCasing(className, mods)\n \n const shouldInclude = rx\u003cboolean\u003e()\n if (shouldInclude) {\n cl.add(className)\n } else {\n cl.remove(className)\n }\n }\n })\n },\n}\n"},{"label":"On","name":"On","path":"../plugins/official/dom/attributes/on","type":"","author":"Delaney Gillilan","slug":"Add an event listener to an element","description":"This plugin adds an event listener to an element. The event listener can be triggered by a variety of events, such as clicks, keypresses, and more. The event listener can also be set to trigger only once, or to be passive or capture. The event listener can also be debounced or throttled. The event listener can also be set to trigger only when the event target is outside the element.","icon":"material-symbols:mail","key":"static_library_source_plugins_official_dom_attributes_on_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:mail\n// Slug: Add an event listener to an element\n// Description: This plugin adds an event listener to an element. The event listener can be triggered by a variety of events, such as clicks, keypresses, and more. The event listener can also be set to trigger only once, or to be passive or capture. The event listener can also be debounced or throttled. The event listener can also be set to trigger only when the event target is outside the element.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { kebab, modifyCasing } from '../../../../utils/text'\nimport { modifyTiming } from '../../../../utils/timing'\nimport { modifyViewTransition } from '../../../../utils/view-transtions'\nimport { DATASTAR_SSE_EVENT } from '../../backend/shared'\n\nexport const On: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'on',\n keyReq: Requirement.Must,\n valReq: Requirement.Must,\n argNames: ['evt'],\n onLoad: ({ el, key, mods, genRX }) =\u003e {\n const rx = genRX()\n let target: Element | Window | Document = el\n if (mods.has('window')) target = window\n\n let callback = (evt?: Event) =\u003e {\n if (evt) {\n // Always prevent default on submit events (because forms)\n if (mods.has('prevent') || key === 'submit') evt.preventDefault()\n if (mods.has('stop')) evt.stopPropagation()\n }\n rx(evt)\n }\n\n callback = modifyTiming(callback, mods)\n callback = modifyViewTransition(callback, mods)\n\n const evtListOpts: AddEventListenerOptions = {\n capture: false,\n passive: false,\n once: false,\n }\n if (mods.has('capture')) evtListOpts.capture = true\n if (mods.has('passive')) evtListOpts.passive = true\n if (mods.has('once')) evtListOpts.once = true\n\n const testOutside = mods.has('outside')\n if (testOutside) {\n target = document\n const cb = callback\n const targetOutsideCallback = (e?: Event) =\u003e {\n const targetHTML = e?.target as HTMLElement\n if (!el.contains(targetHTML)) {\n cb(e)\n }\n }\n callback = targetOutsideCallback\n }\n\n // Default to kebab-case and allow modifying\n let eventName = kebab(key)\n eventName = modifyCasing(eventName, mods)\n\n // Listen for Datastar SSE events on the document\n if (eventName === DATASTAR_SSE_EVENT) {\n target = document\n }\n\n target.addEventListener(eventName, callback, evtListOpts)\n return () =\u003e {\n target.removeEventListener(eventName, callback)\n }\n },\n}\n"},{"label":"Ref","name":"Ref","path":"../plugins/official/dom/attributes/ref","type":"","author":"Delaney Gillilan","slug":"Create a reference to an element","description":"This attribute creates a reference to an element that can be used in other expressions.","icon":"mdi:cursor-pointer","key":"static_library_source_plugins_official_dom_attributes_ref_ts","contents":"// Authors: Delaney Gillilan\n// Icon: mdi:cursor-pointer\n// Slug: Create a reference to an element\n// Description: This attribute creates a reference to an element that can be used in other expressions.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\nimport { modifyCasing, trimDollarSignPrefix } from '../../../../utils/text'\n\n// Sets the value of the element\nexport const Ref: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'ref',\n keyReq: Requirement.Exclusive,\n valReq: Requirement.Exclusive,\n onLoad: ({ el, key, mods, signals, value }) =\u003e {\n const signalName = key ? modifyCasing(key, mods) : trimDollarSignPrefix(value)\n signals.setValue(signalName, el)\n },\n}\n"},{"label":"Show","name":"Show","path":"../plugins/official/dom/attributes/show","type":"","author":"Delaney Gillilan","slug":"Show or hide an element","description":"This attribute shows or hides an element based on the value of the expression. If the expression is true, the element is shown. If the expression is false, the element is hidden. The element is hidden by setting the display property to none.","icon":"streamline:interface-edit-view-eye-eyeball-open-view","key":"static_library_source_plugins_official_dom_attributes_show_ts","contents":"// Authors: Delaney Gillilan\n// Icon: streamline:interface-edit-view-eye-eyeball-open-view\n// Slug: Show or hide an element\n// Description: This attribute shows or hides an element based on the value of the expression. If the expression is true, the element is shown. If the expression is false, the element is hidden. The element is hidden by setting the display property to none.\n\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\n\nconst NONE = 'none'\nconst DISPLAY = 'display'\n\nexport const Show: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'show',\n keyReq: Requirement.Denied,\n valReq: Requirement.Must,\n onLoad: ({ el: { style: s }, genRX, effect }) =\u003e {\n const rx = genRX()\n return effect(async () =\u003e {\n const shouldShow = rx\u003cboolean\u003e()\n if (shouldShow) {\n if (s.display === NONE) {\n s.removeProperty(DISPLAY)\n }\n } else {\n s.setProperty(DISPLAY, NONE)\n }\n })\n },\n}\n"},{"label":"Text","name":"Text","path":"../plugins/official/dom/attributes/text","type":"","author":"Delaney Gillilan","slug":"Set the text content of an element","description":"This attribute sets the text content of an element to the result of the expression.","icon":"tabler:typography","key":"static_library_source_plugins_official_dom_attributes_text_ts","contents":"// Authors: Delaney Gillilan\n// Icon: tabler:typography\n// Slug: Set the text content of an element\n// Description: This attribute sets the text content of an element to the result of the expression.\n\nimport { runtimeErr } from '../../../../engine/errors'\nimport {\n type AttributePlugin,\n PluginType,\n Requirement,\n} from '../../../../engine/types'\n\nexport const Text: AttributePlugin = {\n type: PluginType.Attribute,\n name: 'text',\n keyReq: Requirement.Denied,\n valReq: Requirement.Must,\n onLoad: (ctx) =\u003e {\n const { el, effect, genRX } = ctx\n const rx = genRX()\n if (!(el instanceof HTMLElement)) {\n runtimeErr('TextInvalidElement', ctx)\n }\n return effect(() =\u003e {\n const res = rx(ctx)\n el.textContent = `${res}`\n })\n },\n}\n"},{"label":"Fit","name":"Fit","path":"../plugins/official/logic/actions/fit","type":"","author":"Delaney Gillilan","slug":"Clamp a value to a new range","description":"This action clamps a value to a new range. The value is first scaled to the new range, then clamped to the new range. This is useful for scaling a value to a new range, then clamping it to that range.","icon":"material-symbols:fit-screen-outline","key":"static_library_source_plugins_official_logic_actions_fit_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:fit-screen-outline\n// Slug: Clamp a value to a new range\n// Description: This action clamps a value to a new range. The value is first scaled to the new range, then clamped to the new range. This is useful for scaling a value to a new range, then clamping it to that range.\n\nimport {\n type ActionPlugin,\n PluginType,\n type RuntimeContext,\n} from '../../../../engine/types'\n\nconst { round, max, min } = Math\nexport const Fit: ActionPlugin = {\n type: PluginType.Action,\n name: 'fit',\n fn: (\n _: RuntimeContext,\n v: number,\n oldMin: number,\n oldMax: number,\n newMin: number,\n newMax: number,\n shouldClamp = false,\n shouldRound = false,\n ) =\u003e {\n let fitted = ((v - oldMin) / (oldMax - oldMin)) * (newMax - newMin) + newMin\n if (shouldRound) {\n fitted = round(fitted)\n }\n if (shouldClamp) {\n fitted = max(newMin, min(newMax, fitted))\n }\n return fitted\n },\n}\n"},{"label":"SetAll","name":"SetAll","path":"../plugins/official/logic/actions/setAll","type":"","author":"Delaney Gillilan","slug":"Set all signals that match the signal path","description":"Set all signals that match one or more space-separated paths in which `*` can be used as a wildcard","icon":"ion:checkmark-round","key":"static_library_source_plugins_official_logic_actions_set_all_ts","contents":"// Authors: Delaney Gillilan\n// Icon: ion:checkmark-round\n// Slug: Set all signals that match the signal path\n// Description: Set all signals that match one or more space-separated paths in which `*` can be used as a wildcard\n\nimport { type ActionPlugin, PluginType } from '../../../../engine/types'\nimport { getMatchingSignalPaths } from '../../../../utils/paths'\n\nexport const SetAll: ActionPlugin = {\n type: PluginType.Action,\n name: 'setAll',\n fn: ({ signals }, paths: string, newValue) =\u003e {\n const signalPaths = getMatchingSignalPaths(signals, paths)\n for (const path of signalPaths) {\n signals.setValue(path, newValue)\n }\n },\n}\n"},{"label":"ToggleAll","name":"ToggleAll","path":"../plugins/official/logic/actions/toggleAll","type":"","author":"Delaney Gillilan","slug":"Toggle all signals that match the signal path","description":"Toggle all signals that match one or more space-separated paths in which `*` can be used as a wildcard","icon":"material-symbols:toggle-off","key":"static_library_source_plugins_official_logic_actions_toggle_all_ts","contents":"// Authors: Delaney Gillilan\n// Icon: material-symbols:toggle-off\n// Slug: Toggle all signals that match the signal path\n// Description: Toggle all signals that match one or more space-separated paths in which `*` can be used as a wildcard\n\nimport { type ActionPlugin, PluginType } from '../../../../engine/types'\nimport { getMatchingSignalPaths } from '../../../../utils/paths'\n\nexport const ToggleAll: ActionPlugin = {\n type: PluginType.Action,\n name: 'toggleAll',\n fn: ({ signals }, paths: string) =\u003e {\n const signalPaths = getMatchingSignalPaths(signals, paths)\n for (const path of signalPaths) {\n signals.setValue(path, !signals.value(path))\n }\n },\n}\n"}],"alias":""}}