@hyperfixi/behaviors 3.3.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +162 -0
- package/README.md +29 -9
- package/dist/all.browser.global.js +45 -100
- package/dist/all.browser.global.js.map +1 -1
- package/dist/autodismiss.browser.global.js +5 -11
- package/dist/autodismiss.browser.global.js.map +1 -1
- package/dist/behaviors/autodismiss.cjs +19 -22
- package/dist/behaviors/autodismiss.cjs.map +1 -1
- package/dist/behaviors/autodismiss.d.cts +4 -4
- package/dist/behaviors/autodismiss.d.ts +4 -4
- package/dist/behaviors/autodismiss.js +3 -3
- package/dist/behaviors/clickoutside.cjs +19 -18
- package/dist/behaviors/clickoutside.cjs.map +1 -1
- package/dist/behaviors/clickoutside.d.cts +4 -4
- package/dist/behaviors/clickoutside.d.ts +4 -4
- package/dist/behaviors/clickoutside.js +3 -3
- package/dist/behaviors/clipboard.cjs +18 -19
- package/dist/behaviors/clipboard.cjs.map +1 -1
- package/dist/behaviors/clipboard.d.cts +4 -4
- package/dist/behaviors/clipboard.d.ts +4 -4
- package/dist/behaviors/clipboard.js +3 -3
- package/dist/behaviors/draggable.cjs +20 -25
- package/dist/behaviors/draggable.cjs.map +1 -1
- package/dist/behaviors/draggable.d.cts +4 -4
- package/dist/behaviors/draggable.d.ts +4 -4
- package/dist/behaviors/draggable.js +3 -3
- package/dist/behaviors/focustrap.cjs +18 -19
- package/dist/behaviors/focustrap.cjs.map +1 -1
- package/dist/behaviors/focustrap.d.cts +4 -4
- package/dist/behaviors/focustrap.d.ts +4 -4
- package/dist/behaviors/focustrap.js +3 -3
- package/dist/behaviors/removable.cjs +18 -21
- package/dist/behaviors/removable.cjs.map +1 -1
- package/dist/behaviors/removable.d.cts +4 -3
- package/dist/behaviors/removable.d.ts +4 -3
- package/dist/behaviors/removable.js +3 -3
- package/dist/behaviors/resizable.cjs +19 -19
- package/dist/behaviors/resizable.cjs.map +1 -1
- package/dist/behaviors/resizable.d.cts +4 -4
- package/dist/behaviors/resizable.d.ts +4 -4
- package/dist/behaviors/resizable.js +3 -3
- package/dist/behaviors/scrollreveal.cjs +19 -22
- package/dist/behaviors/scrollreveal.cjs.map +1 -1
- package/dist/behaviors/scrollreveal.d.cts +4 -4
- package/dist/behaviors/scrollreveal.d.ts +4 -4
- package/dist/behaviors/scrollreveal.js +3 -3
- package/dist/behaviors/sortable.cjs +19 -20
- package/dist/behaviors/sortable.cjs.map +1 -1
- package/dist/behaviors/sortable.d.cts +4 -4
- package/dist/behaviors/sortable.d.ts +4 -4
- package/dist/behaviors/sortable.js +3 -3
- package/dist/behaviors/tabs.cjs +20 -25
- package/dist/behaviors/tabs.cjs.map +1 -1
- package/dist/behaviors/tabs.d.cts +4 -4
- package/dist/behaviors/tabs.d.ts +4 -4
- package/dist/behaviors/tabs.js +3 -3
- package/dist/behaviors/toggleable.cjs +25 -26
- package/dist/behaviors/toggleable.cjs.map +1 -1
- package/dist/behaviors/toggleable.d.cts +4 -4
- package/dist/behaviors/toggleable.d.ts +4 -4
- package/dist/behaviors/toggleable.js +3 -3
- package/dist/chunk-2GIS4DCY.js +30 -0
- package/dist/chunk-2GIS4DCY.js.map +1 -0
- package/dist/chunk-3BCBZR6R.js +30 -0
- package/dist/chunk-3BCBZR6R.js.map +1 -0
- package/dist/chunk-4YUIDZAO.js +30 -0
- package/dist/chunk-4YUIDZAO.js.map +1 -0
- package/dist/{chunk-XYZ7FOXJ.js → chunk-AQCOK5J5.js} +10 -14
- package/dist/chunk-AQCOK5J5.js.map +1 -0
- package/dist/chunk-ATGHQQC7.js +30 -0
- package/dist/chunk-ATGHQQC7.js.map +1 -0
- package/dist/{chunk-HQDYH45I.js → chunk-CWLAXLWR.js} +5 -13
- package/dist/chunk-CWLAXLWR.js.map +1 -0
- package/dist/chunk-DMWN52SV.js +30 -0
- package/dist/chunk-DMWN52SV.js.map +1 -0
- package/dist/{chunk-PDMOOWFW.js → chunk-EIVPVTUI.js} +4 -8
- package/dist/{chunk-PDMOOWFW.js.map → chunk-EIVPVTUI.js.map} +1 -1
- package/dist/chunk-HHTEMPIT.js +30 -0
- package/dist/chunk-HHTEMPIT.js.map +1 -0
- package/dist/{chunk-26IX7QU7.js → chunk-HJESSMKB.js} +4 -6
- package/dist/{chunk-26IX7QU7.js.map → chunk-HJESSMKB.js.map} +1 -1
- package/dist/{chunk-5S7WN3ZQ.js → chunk-IFAH652L.js} +4 -7
- package/dist/chunk-IFAH652L.js.map +1 -0
- package/dist/chunk-IUGJG4RA.js +30 -0
- package/dist/chunk-IUGJG4RA.js.map +1 -0
- package/dist/{chunk-ZR66E3T6.js → chunk-JNLVXPHR.js} +13 -13
- package/dist/chunk-KEZEGPEX.js +30 -0
- package/dist/chunk-KEZEGPEX.js.map +1 -0
- package/dist/{chunk-66U2RSYJ.js → chunk-KP4GNXYS.js} +4 -10
- package/dist/chunk-KP4GNXYS.js.map +1 -0
- package/dist/chunk-KXQMCN4O.js +30 -0
- package/dist/chunk-KXQMCN4O.js.map +1 -0
- package/dist/{chunk-YXS27XM6.js → chunk-KYBOZ7C4.js} +3 -7
- package/dist/chunk-KYBOZ7C4.js.map +1 -0
- package/dist/chunk-LBWM6TTU.js +30 -0
- package/dist/chunk-LBWM6TTU.js.map +1 -0
- package/dist/{chunk-3DVDS7CT.js → chunk-LCDONZBX.js} +5 -5
- package/dist/chunk-LCDONZBX.js.map +1 -0
- package/dist/{chunk-OJ3YWO3W.js → chunk-NE76IBV2.js} +3 -7
- package/dist/chunk-NE76IBV2.js.map +1 -0
- package/dist/{chunk-HMU342XE.js → chunk-PVO6TZCZ.js} +3 -9
- package/dist/{chunk-HMU342XE.js.map → chunk-PVO6TZCZ.js.map} +1 -1
- package/dist/{chunk-URZGNPTQ.js → chunk-RILYRQ72.js} +4 -10
- package/dist/chunk-RILYRQ72.js.map +1 -0
- package/dist/{chunk-FBQ3X3UY.js → chunk-U4QMFVAM.js} +5 -13
- package/dist/chunk-U4QMFVAM.js.map +1 -0
- package/dist/chunk-VWDTOSE3.js +26 -0
- package/dist/chunk-VWDTOSE3.js.map +1 -0
- package/dist/chunk-YRQBU2OW.js +30 -0
- package/dist/chunk-YRQBU2OW.js.map +1 -0
- package/dist/clickoutside.browser.global.js +5 -7
- package/dist/clickoutside.browser.global.js.map +1 -1
- package/dist/clipboard.browser.global.js +4 -8
- package/dist/clipboard.browser.global.js.map +1 -1
- package/dist/core.browser.global.js +13 -25
- package/dist/core.browser.global.js.map +1 -1
- package/dist/draggable.browser.global.js +5 -13
- package/dist/draggable.browser.global.js.map +1 -1
- package/dist/focustrap.browser.global.js +4 -8
- package/dist/focustrap.browser.global.js.map +1 -1
- package/dist/index.cjs +96 -271
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +42 -55
- package/dist/index.js.map +1 -1
- package/dist/loaders.cjs +75 -237
- package/dist/loaders.cjs.map +1 -1
- package/dist/loaders.js +13 -13
- package/dist/registry.cjs +4 -4
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.d.cts +5 -6
- package/dist/registry.d.ts +5 -6
- package/dist/registry.js +1 -1
- package/dist/removable.browser.global.js +3 -9
- package/dist/removable.browser.global.js.map +1 -1
- package/dist/resizable.browser.global.js +4 -7
- package/dist/resizable.browser.global.js.map +1 -1
- package/dist/resolver.browser.global.js +45 -100
- package/dist/resolver.browser.global.js.map +1 -1
- package/dist/schemas/index.cjs +38 -93
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +2 -2
- package/dist/schemas/index.d.ts +2 -2
- package/dist/schemas/index.js +11 -11
- package/dist/schemas/index.js.map +1 -1
- package/dist/scrollreveal.browser.global.js +5 -11
- package/dist/scrollreveal.browser.global.js.map +1 -1
- package/dist/sortable.browser.global.js +5 -9
- package/dist/sortable.browser.global.js.map +1 -1
- package/dist/tabs.browser.global.js +6 -14
- package/dist/tabs.browser.global.js.map +1 -1
- package/dist/toggleable.browser.global.js +9 -13
- package/dist/toggleable.browser.global.js.map +1 -1
- package/dist/{types-DCCtVQS5.d.cts → types-CbA4L8XJ.d.cts} +16 -23
- package/dist/{types-DCCtVQS5.d.ts → types-CbA4L8XJ.d.ts} +16 -23
- package/package.json +5 -5
- package/src/behavior-resolver.ts +24 -56
- package/src/behaviors/autodismiss.test.ts +12 -20
- package/src/behaviors/autodismiss.ts +8 -23
- package/src/behaviors/clickoutside.test.ts +13 -21
- package/src/behaviors/clickoutside.ts +8 -23
- package/src/behaviors/clipboard.test.ts +12 -34
- package/src/behaviors/clipboard.ts +8 -23
- package/src/behaviors/curated-runtime.test.ts +22 -24
- package/src/behaviors/draggable.ts +10 -24
- package/src/behaviors/experimental-runtime.test.ts +19 -18
- package/src/behaviors/focustrap.test.ts +12 -20
- package/src/behaviors/focustrap.ts +8 -23
- package/src/behaviors/integration.test.ts +19 -54
- package/src/behaviors/optional-runtime.test.ts +17 -22
- package/src/behaviors/removable.ts +6 -20
- package/src/behaviors/resizable.ts +9 -24
- package/src/behaviors/scrollreveal.test.ts +13 -21
- package/src/behaviors/scrollreveal.ts +8 -23
- package/src/behaviors/sortable.ts +7 -22
- package/src/behaviors/tabs.test.ts +12 -34
- package/src/behaviors/tabs.ts +8 -23
- package/src/behaviors/toggleable.ts +10 -26
- package/src/browser-resolver-bundle.ts +20 -30
- package/src/core-bundle.ts +11 -11
- package/src/index.test.ts +2 -2
- package/src/index.ts +30 -47
- package/src/registry.test.ts +9 -34
- package/src/registry.ts +7 -11
- package/src/schemas/autodismiss.schema.ts +3 -9
- package/src/schemas/clickoutside.schema.ts +3 -5
- package/src/schemas/clipboard.schema.ts +2 -6
- package/src/schemas/draggable.schema.ts +4 -12
- package/src/schemas/focustrap.schema.ts +2 -6
- package/src/schemas/index.ts +2 -1
- package/src/schemas/removable.schema.ts +2 -8
- package/src/schemas/resizable.schema.ts +3 -6
- package/src/schemas/scrollreveal.schema.ts +3 -9
- package/src/schemas/sortable.schema.ts +3 -7
- package/src/schemas/tabs.schema.ts +4 -12
- package/src/schemas/toggleable.schema.ts +10 -13
- package/src/schemas/types.ts +37 -30
- package/dist/chunk-2HUF6RMI.js +0 -40
- package/dist/chunk-2HUF6RMI.js.map +0 -1
- package/dist/chunk-3DVDS7CT.js.map +0 -1
- package/dist/chunk-5S7WN3ZQ.js.map +0 -1
- package/dist/chunk-66U2RSYJ.js.map +0 -1
- package/dist/chunk-6TJXDCAQ.js +0 -11
- package/dist/chunk-6TJXDCAQ.js.map +0 -1
- package/dist/chunk-7TPDLB7W.js +0 -40
- package/dist/chunk-7TPDLB7W.js.map +0 -1
- package/dist/chunk-CR3J7VV6.js +0 -40
- package/dist/chunk-CR3J7VV6.js.map +0 -1
- package/dist/chunk-EDBG2F57.js +0 -40
- package/dist/chunk-EDBG2F57.js.map +0 -1
- package/dist/chunk-FBQ3X3UY.js.map +0 -1
- package/dist/chunk-HQDYH45I.js.map +0 -1
- package/dist/chunk-HX663BJ4.js +0 -40
- package/dist/chunk-HX663BJ4.js.map +0 -1
- package/dist/chunk-KROW43CG.js +0 -40
- package/dist/chunk-KROW43CG.js.map +0 -1
- package/dist/chunk-L52AGCMP.js +0 -40
- package/dist/chunk-L52AGCMP.js.map +0 -1
- package/dist/chunk-MTI5PSRH.js +0 -40
- package/dist/chunk-MTI5PSRH.js.map +0 -1
- package/dist/chunk-OJ3YWO3W.js.map +0 -1
- package/dist/chunk-ORBJ7MRW.js +0 -40
- package/dist/chunk-ORBJ7MRW.js.map +0 -1
- package/dist/chunk-SLSCZV4A.js +0 -40
- package/dist/chunk-SLSCZV4A.js.map +0 -1
- package/dist/chunk-URZGNPTQ.js.map +0 -1
- package/dist/chunk-WKBRZGGL.js +0 -40
- package/dist/chunk-WKBRZGGL.js.map +0 -1
- package/dist/chunk-XYZ7FOXJ.js.map +0 -1
- package/dist/chunk-YXS27XM6.js.map +0 -1
- /package/dist/{chunk-ZR66E3T6.js.map → chunk-JNLVXPHR.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/behaviors/tabs.ts","../src/schemas/tabs.schema.ts","../src/schemas/types.ts"],"sourcesContent":["/**\n * Tabs Behavior\n *\n * WAI-ARIA compliant tabs with roving tabindex keyboard navigation.\n * Auto-wires role, aria-selected, aria-controls, and aria-labelledby. The ARIA\n * wiring / keyboard-navigation logic lives in the schema's hyperscript `source`\n * (an `init`-block `js()` body).\n *\n * Compiled from its hyperscript `source` — the single source of truth shared with\n * the CDN resolver bundle and patterns-reference. This collapses Tabs onto the one\n * runtime path (no more imperative-installer fork between CDN and npm);\n * see docs-internal/BEHAVIORS_CONSOLIDATION_PLAN.md §3d.\n */\n\nimport { tabsSchema } from '../schemas/tabs.schema';\nimport type { LokaScriptInstance } from '../schemas/types';\nimport { resolveRuntime } from '../schemas/types';\n\nexport const tabsSource = tabsSchema.source;\nexport const tabsMetadata = tabsSchema;\n\n/**\n * Register the Tabs behavior with LokaScript by compiling its\n * hyperscript source and executing the resulting behavior definition.\n */\nexport async function registerTabs(hyperfixi?: LokaScriptInstance): Promise<void> {\n const hf = hyperfixi || resolveRuntime();\n\n if (!hf) {\n throw new Error(\n 'LokaScript not found. Make sure @hyperfixi/core is loaded before registering behaviors.'\n );\n }\n\n const result = hf.compileSync(tabsSchema.source, { traditional: true });\n\n if (!result.ok) {\n throw new Error(`Failed to compile Tabs behavior: ${JSON.stringify(result.errors)}`);\n }\n\n const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };\n await hf.execute(result.ast, ctx);\n}\n\n// Auto-register when loaded as a script tag\nif (resolveRuntime()) {\n registerTabs().catch(console.error);\n}\n\nexport default {\n source: tabsSchema.source,\n metadata: tabsSchema,\n register: registerTabs,\n};\n","import type { BehaviorSchema } from './types';\n\nexport const tabsSchema: BehaviorSchema = {\n name: 'Tabs',\n category: 'ui',\n tier: 'core',\n version: '1.0.0',\n description:\n 'WAI-ARIA compliant tabs with roving tabindex keyboard navigation. ' +\n 'Auto-wires role, aria-selected, aria-controls, and aria-labelledby attributes.',\n parameters: [\n {\n name: 'orientation',\n type: 'string',\n optional: true,\n default: 'horizontal',\n enum: ['horizontal', 'vertical'],\n description: 'Arrow key direction: horizontal (Left/Right) or vertical (Up/Down)',\n },\n {\n name: 'activeTab',\n type: 'number',\n optional: true,\n default: 0,\n description: 'Initial active tab index (zero-based)',\n },\n {\n name: 'wrap',\n type: 'boolean',\n optional: true,\n default: true,\n description: 'Wrap to first/last tab when navigating past the end',\n },\n {\n name: 'activeClass',\n type: 'string',\n optional: true,\n default: 'active',\n description: 'CSS class applied to the active tab and its panel',\n },\n ],\n events: [\n {\n name: 'tabs:change',\n description: 'Fired before tab selection changes (cancelable via preventDefault)',\n },\n { name: 'tabs:changed', description: 'Fired after tab selection has changed' },\n ],\n requirements: [\n 'Sets role=\"tablist\" on the tab container',\n 'Sets role=\"tab\" on tab buttons and role=\"tabpanel\" on content panels',\n 'Manages aria-selected, aria-controls, aria-labelledby, and tabindex',\n 'Keyboard: Arrow keys navigate, Home/End jump to first/last, Tab exits tablist',\n ],\n source: `\nbehavior Tabs(orientation, activeTab, wrap, activeClass)\n init\n if orientation is undefined\n set orientation to \"horizontal\"\n end\n if activeTab is undefined\n set activeTab to 0\n end\n if wrap is undefined\n set wrap to true\n end\n if activeClass is undefined\n set activeClass to \"active\"\n end\n js(me, orientation, activeTab, wrap, activeClass)\n var tabs = Array.from(me.querySelectorAll('[role=\"tab\"]'));\n var panels = Array.from(me.querySelectorAll('[role=\"tabpanel\"]'));\n if (tabs.length === 0 || panels.length === 0) return;\n var tablist = me.querySelector('[role=\"tablist\"]') || tabs[0].parentElement;\n if (tablist) tablist.setAttribute('aria-orientation', orientation);\n var count = Math.min(tabs.length, panels.length);\n var idN = 0;\n for (var i = 0; i < count; i++) {\n if (!tabs[i].id) tabs[i].id = 'tab-' + (++idN);\n if (!panels[i].id) panels[i].id = 'tabpanel-' + idN;\n tabs[i].setAttribute('aria-controls', panels[i].id);\n panels[i].setAttribute('aria-labelledby', tabs[i].id);\n }\n var cur = Math.max(0, Math.min(activeTab, count - 1));\n function activate(idx, prev) {\n if (idx === prev || idx < 0 || idx >= count) return false;\n var ev = new CustomEvent('tabs:change', { bubbles: true, cancelable: true, detail: { index: idx, previousIndex: prev } });\n me.dispatchEvent(ev);\n if (ev.defaultPrevented) return false;\n if (prev >= 0 && prev < count) {\n tabs[prev].setAttribute('aria-selected', 'false');\n tabs[prev].setAttribute('tabindex', '-1');\n tabs[prev].classList.remove(activeClass);\n panels[prev].setAttribute('aria-hidden', 'true');\n panels[prev].classList.remove(activeClass);\n }\n tabs[idx].setAttribute('aria-selected', 'true');\n tabs[idx].setAttribute('tabindex', '0');\n tabs[idx].classList.add(activeClass);\n panels[idx].setAttribute('aria-hidden', 'false');\n panels[idx].classList.add(activeClass);\n cur = idx;\n me.dispatchEvent(new CustomEvent('tabs:changed', { bubbles: true, detail: { index: idx } }));\n return true;\n }\n for (var j = 0; j < count; j++) {\n tabs[j].setAttribute('aria-selected', 'false');\n tabs[j].setAttribute('tabindex', '-1');\n panels[j].setAttribute('aria-hidden', 'true');\n panels[j].classList.remove(activeClass);\n }\n tabs[cur].setAttribute('aria-selected', 'true');\n tabs[cur].setAttribute('tabindex', '0');\n tabs[cur].classList.add(activeClass);\n panels[cur].setAttribute('aria-hidden', 'false');\n panels[cur].classList.add(activeClass);\n for (var k = 0; k < count; k++) {\n (function(idx) {\n tabs[idx].addEventListener('click', function() { if (activate(idx, cur)) tabs[idx].focus(); });\n })(k);\n }\n (tablist || me).addEventListener('keydown', function(e) {\n var nk = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';\n var pk = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';\n var next = null;\n if (e.key === nk) { next = cur + 1; if (next >= count) next = wrap ? 0 : null; }\n else if (e.key === pk) { next = cur - 1; if (next < 0) next = wrap ? count - 1 : null; }\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = count - 1;\n if (next !== null) { e.preventDefault(); if (activate(next, cur)) tabs[next].focus(); }\n });\n end\n end\nend`.trim(),\n};\n","/**\n * Behavior Schema Types\n *\n * Defines the structure for behavior metadata used for:\n * - Code generation (TypeScript types, documentation)\n * - Runtime categorization and lazy loading\n * - Tooling and IDE integration\n */\n\n/**\n * Behavior categories for organization.\n * - ui: Visual interaction behaviors (drag, resize, sort)\n * - data: Data manipulation behaviors (remove, transform)\n * - animation: Animation and transition behaviors\n * - form: Form-related behaviors (validation, toggle states)\n * - layout: Layout behaviors (sticky, scrollspy)\n */\nexport type BehaviorCategory = 'ui' | 'data' | 'animation' | 'form' | 'layout';\n\n/**\n * Loading tiers for lazy loading optimization.\n * - core: Always loaded (essential behaviors)\n * - common: High usage (loaded for typical apps)\n * - optional: Low usage (loaded on demand)\n */\nexport type BehaviorTier = 'core' | 'common' | 'optional';\n\n/**\n * Schema for a behavior parameter.\n */\nexport interface ParameterSchema {\n name: string;\n type: 'string' | 'number' | 'boolean' | 'selector' | 'element';\n optional?: boolean;\n default?: string | number | boolean;\n /** Allowed values for constrained parameters */\n enum?: string[];\n description: string;\n}\n\n/**\n * Schema for a behavior event.\n */\nexport interface EventSchema {\n name: string;\n description: string;\n}\n\n/**\n * Complete schema for a behavior.\n * This is the source of truth for all behavior metadata.\n */\nexport interface BehaviorSchema {\n /** Behavior name (PascalCase) */\n name: string;\n /** Category for organization */\n category: BehaviorCategory;\n /** Loading tier for lazy loading */\n tier: BehaviorTier;\n /** Semantic version */\n version: string;\n /** Human-readable description */\n description: string;\n /** Parameter definitions */\n parameters: ParameterSchema[];\n /** Event definitions */\n events: EventSchema[];\n /** The hyperscript behavior source code */\n source: string;\n /** Optional requirements or notes */\n requirements?: string[];\n}\n\n/**\n * Runtime behavior module interface.\n * This is what gets registered with the hyperfixi runtime.\n */\nexport interface BehaviorModule {\n source: string;\n metadata: BehaviorSchema;\n register: (hyperfixi?: LokaScriptInstance) => Promise<void>;\n}\n\n/**\n * Minimal LokaScript instance interface for behavior registration.\n */\nexport interface LokaScriptInstance {\n compileSync: (\n code: string,\n options?: { traditional?: boolean; language?: string }\n ) => {\n ok: boolean;\n ast?: unknown;\n errors?: Array<{ message: string; line?: number; column?: number }>;\n };\n execute: (ast: unknown, ctx: unknown) => Promise<unknown>;\n createContext?: () => { locals: Map<string, unknown>; globals: Map<string, unknown> };\n}\n\n/**\n * Typed window interface for behavior auto-registration.\n * Avoids `(window as any)` casts throughout the codebase.\n * Checks both `window.lokascript` and `window.hyperfixi` for the runtime instance.\n */\nexport interface LokaScriptWindow {\n lokascript?: LokaScriptInstance;\n hyperfixi?: LokaScriptInstance;\n __hyperfixi_behaviors_ready?: Promise<void> | undefined;\n}\n\n/**\n * Resolve the LokaScript runtime from window globals.\n * Checks both `window.lokascript` and `window.hyperfixi`.\n */\nexport function resolveRuntime(): LokaScriptInstance | null {\n if (typeof window === 'undefined') return null;\n const win = window as unknown as LokaScriptWindow;\n return win.lokascript || win.hyperfixi || null;\n}\n"],"mappings":"scAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,iBAAAC,EAAA,iBAAAC,EAAA,eAAAC,ICEO,IAAMC,EAA6B,CACxC,KAAM,OACN,SAAU,KACV,KAAM,OACN,QAAS,QACT,YACE,mJAEF,WAAY,CACV,CACE,KAAM,cACN,KAAM,SACN,SAAU,GACV,QAAS,aACT,KAAM,CAAC,aAAc,UAAU,EAC/B,YAAa,oEACf,EACA,CACE,KAAM,YACN,KAAM,SACN,SAAU,GACV,QAAS,EACT,YAAa,uCACf,EACA,CACE,KAAM,OACN,KAAM,UACN,SAAU,GACV,QAAS,GACT,YAAa,qDACf,EACA,CACE,KAAM,cACN,KAAM,SACN,SAAU,GACV,QAAS,SACT,YAAa,mDACf,CACF,EACA,OAAQ,CACN,CACE,KAAM,cACN,YAAa,oEACf,EACA,CAAE,KAAM,eAAgB,YAAa,uCAAwC,CAC/E,EACA,aAAc,CACZ,2CACA,uEACA,sEACA,+EACF,EACA,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA+EL,KAAK,CACV,ECpBO,SAASC,GAA4C,CAC1D,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAMC,EAAM,OACZ,OAAOA,EAAI,YAAcA,EAAI,WAAa,IAC5C,CFpGO,IAAMC,EAAaC,EAAW,OACxBC,EAAeD,EAM5B,eAAsBE,EAAaC,EAA+C,CAChF,IAAMC,EAAKD,GAAaE,EAAe,EAEvC,GAAI,CAACD,EACH,MAAM,IAAI,MACR,yFACF,EAGF,IAAME,EAASF,EAAG,YAAYJ,EAAW,OAAQ,CAAE,YAAa,EAAK,CAAC,EAEtE,GAAI,CAACM,EAAO,GACV,MAAM,IAAI,MAAM,oCAAoC,KAAK,UAAUA,EAAO,MAAM,CAAC,EAAE,EAGrF,IAAMC,EAAMH,EAAG,cAAgBA,EAAG,cAAc,EAAI,CAAE,OAAQ,IAAI,IAAO,QAAS,IAAI,GAAM,EAC5F,MAAMA,EAAG,QAAQE,EAAO,IAAKC,CAAG,CAClC,CAGIF,EAAe,GACjBH,EAAa,EAAE,MAAM,QAAQ,KAAK,EAGpC,IAAOM,EAAQ,CACb,OAAQR,EAAW,OACnB,SAAUA,EACV,SAAUE,CACZ","names":["tabs_exports","__export","tabs_default","registerTabs","tabsMetadata","tabsSource","tabsSchema","resolveRuntime","win","tabsSource","tabsSchema","tabsMetadata","registerTabs","hyperfixi","hf","resolveRuntime","result","ctx","tabs_default"]}
|
|
1
|
+
{"version":3,"sources":["../src/behaviors/tabs.ts","../src/schemas/tabs.schema.ts","../src/schemas/types.ts"],"sourcesContent":["/**\n * Tabs Behavior\n *\n * WAI-ARIA compliant tabs with roving tabindex keyboard navigation.\n * Auto-wires role, aria-selected, aria-controls, and aria-labelledby. The ARIA\n * wiring / keyboard-navigation logic lives in the schema's hyperscript `source`\n * (an `init`-block `js()` body).\n *\n * Defined from its hyperscript `source` — the single source of truth shared with\n * the browser bundle and patterns-reference. This collapses Tabs onto the one\n * runtime path (no more imperative-installer fork between CDN and npm);\n * see docs-internal/BEHAVIORS_CONSOLIDATION_PLAN.md §3d.\n */\n\nimport { tabsSchema } from '../schemas/tabs.schema';\nimport { defineBehavior, resolveRuntime } from '../schemas/types';\nimport type { HyperscriptHost } from '../schemas/types';\n\nexport const tabsSource = tabsSchema.source;\nexport const tabsMetadata = tabsSchema;\n\n/**\n * Define the Tabs behavior on a hyperscript host (`window.hyperfixi` when none is given)\n * from its source, as a `<script type=\"text/hyperscript\">` would.\n */\nexport async function registerTabs(host?: HyperscriptHost): Promise<void> {\n defineBehavior(tabsSchema, host);\n}\n\n// Auto-register when loaded as a script tag\nif (resolveRuntime()) {\n registerTabs().catch(console.error);\n}\n\nexport default {\n source: tabsSchema.source,\n metadata: tabsSchema,\n register: registerTabs,\n};\n","import type { BehaviorSchema } from './types';\n\nexport const tabsSchema: BehaviorSchema = {\n name: 'Tabs',\n category: 'ui',\n tier: 'core',\n version: '1.0.0',\n description:\n 'WAI-ARIA compliant tabs with roving tabindex keyboard navigation. ' +\n 'Auto-wires role, aria-selected, aria-controls, and aria-labelledby attributes.',\n parameters: [\n {\n name: 'orientation',\n type: 'string',\n optional: true,\n default: 'horizontal',\n enum: ['horizontal', 'vertical'],\n description: 'Arrow key direction: horizontal (Left/Right) or vertical (Up/Down)',\n },\n {\n name: 'activeTab',\n type: 'number',\n optional: true,\n default: 0,\n description: 'Initial active tab index (zero-based)',\n },\n {\n name: 'wrap',\n type: 'boolean',\n optional: true,\n default: true,\n description: 'Wrap to first/last tab when navigating past the end',\n },\n {\n name: 'activeClass',\n type: 'string',\n optional: true,\n default: 'active',\n description: 'CSS class applied to the active tab and its panel',\n },\n ],\n events: [\n {\n name: 'tabs:change',\n description: 'Fired before tab selection changes (cancelable via preventDefault)',\n },\n { name: 'tabs:changed', description: 'Fired after tab selection has changed' },\n ],\n requirements: [\n 'Sets role=\"tablist\" on the tab container',\n 'Sets role=\"tab\" on tab buttons and role=\"tabpanel\" on content panels',\n 'Manages aria-selected, aria-controls, aria-labelledby, and tabindex',\n 'Keyboard: Arrow keys navigate, Home/End jump to first/last, Tab exits tablist',\n ],\n source: `\nbehavior Tabs(orientation, activeTab, wrap, activeClass)\n init\n if orientation is undefined set element's orientation to \"horizontal\" end\n if activeTab is undefined set element's activeTab to 0 end\n if wrap is undefined set element's wrap to true end\n if activeClass is undefined set element's activeClass to \"active\" end\n js(me, orientation, activeTab, wrap, activeClass)\n var tabs = Array.from(me.querySelectorAll('[role=\"tab\"]'));\n var panels = Array.from(me.querySelectorAll('[role=\"tabpanel\"]'));\n if (tabs.length === 0 || panels.length === 0) return;\n var tablist = me.querySelector('[role=\"tablist\"]') || tabs[0].parentElement;\n if (tablist) tablist.setAttribute('aria-orientation', orientation);\n var count = Math.min(tabs.length, panels.length);\n var idN = 0;\n for (var i = 0; i < count; i++) {\n if (!tabs[i].id) tabs[i].id = 'tab-' + (++idN);\n if (!panels[i].id) panels[i].id = 'tabpanel-' + idN;\n tabs[i].setAttribute('aria-controls', panels[i].id);\n panels[i].setAttribute('aria-labelledby', tabs[i].id);\n }\n var cur = Math.max(0, Math.min(activeTab, count - 1));\n function activate(idx, prev) {\n if (idx === prev || idx < 0 || idx >= count) return false;\n var ev = new CustomEvent('tabs:change', { bubbles: true, cancelable: true, detail: { index: idx, previousIndex: prev } });\n me.dispatchEvent(ev);\n if (ev.defaultPrevented) return false;\n if (prev >= 0 && prev < count) {\n tabs[prev].setAttribute('aria-selected', 'false');\n tabs[prev].setAttribute('tabindex', '-1');\n tabs[prev].classList.remove(activeClass);\n panels[prev].setAttribute('aria-hidden', 'true');\n panels[prev].classList.remove(activeClass);\n }\n tabs[idx].setAttribute('aria-selected', 'true');\n tabs[idx].setAttribute('tabindex', '0');\n tabs[idx].classList.add(activeClass);\n panels[idx].setAttribute('aria-hidden', 'false');\n panels[idx].classList.add(activeClass);\n cur = idx;\n me.dispatchEvent(new CustomEvent('tabs:changed', { bubbles: true, detail: { index: idx } }));\n return true;\n }\n for (var j = 0; j < count; j++) {\n tabs[j].setAttribute('aria-selected', 'false');\n tabs[j].setAttribute('tabindex', '-1');\n panels[j].setAttribute('aria-hidden', 'true');\n panels[j].classList.remove(activeClass);\n }\n tabs[cur].setAttribute('aria-selected', 'true');\n tabs[cur].setAttribute('tabindex', '0');\n tabs[cur].classList.add(activeClass);\n panels[cur].setAttribute('aria-hidden', 'false');\n panels[cur].classList.add(activeClass);\n for (var k = 0; k < count; k++) {\n (function(idx) {\n tabs[idx].addEventListener('click', function() { if (activate(idx, cur)) tabs[idx].focus(); });\n })(k);\n }\n (tablist || me).addEventListener('keydown', function(e) {\n var nk = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';\n var pk = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';\n var next = null;\n if (e.key === nk) { next = cur + 1; if (next >= count) next = wrap ? 0 : null; }\n else if (e.key === pk) { next = cur - 1; if (next < 0) next = wrap ? count - 1 : null; }\n else if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = count - 1;\n if (next !== null) { e.preventDefault(); if (activate(next, cur)) tabs[next].focus(); }\n });\n end\n end\nend`.trim(),\n};\n","/**\n * Behavior Schema Types\n *\n * Defines the structure for behavior metadata used for:\n * - Code generation (TypeScript types, documentation)\n * - Runtime categorization and lazy loading\n * - Tooling and IDE integration\n */\n\n/**\n * Behavior categories for organization.\n * - ui: Visual interaction behaviors (drag, resize, sort)\n * - data: Data manipulation behaviors (remove, transform)\n * - animation: Animation and transition behaviors\n * - form: Form-related behaviors (validation, toggle states)\n * - layout: Layout behaviors (sticky, scrollspy)\n */\nexport type BehaviorCategory = 'ui' | 'data' | 'animation' | 'form' | 'layout';\n\n/**\n * Loading tiers for lazy loading optimization.\n * - core: Always loaded (essential behaviors)\n * - common: High usage (loaded for typical apps)\n * - optional: Low usage (loaded on demand)\n */\nexport type BehaviorTier = 'core' | 'common' | 'optional';\n\n/**\n * Schema for a behavior parameter.\n */\nexport interface ParameterSchema {\n name: string;\n type: 'string' | 'number' | 'boolean' | 'selector' | 'element';\n optional?: boolean;\n default?: string | number | boolean;\n /** Allowed values for constrained parameters */\n enum?: string[];\n description: string;\n}\n\n/**\n * Schema for a behavior event.\n */\nexport interface EventSchema {\n name: string;\n description: string;\n}\n\n/**\n * Complete schema for a behavior.\n * This is the source of truth for all behavior metadata.\n */\nexport interface BehaviorSchema {\n /** Behavior name (PascalCase) */\n name: string;\n /** Category for organization */\n category: BehaviorCategory;\n /** Loading tier for lazy loading */\n tier: BehaviorTier;\n /** Semantic version */\n version: string;\n /** Human-readable description */\n description: string;\n /** Parameter definitions */\n parameters: ParameterSchema[];\n /** Event definitions */\n events: EventSchema[];\n /** The hyperscript behavior source code */\n source: string;\n /** Optional requirements or notes */\n requirements?: string[];\n}\n\n/**\n * Runtime behavior module interface: a behavior's source, its schema, and the function that\n * defines it on a hyperscript host.\n */\nexport interface BehaviorModule {\n source: string;\n metadata: BehaviorSchema;\n register: (host?: HyperscriptHost) => Promise<void>;\n}\n\n/**\n * A hyperscript host, in upstream `_hyperscript`'s shape: `@hyperfixi/engine` (the\n * `hyperfixi-hs.js` bundle, `window.hyperfixi` / `window._hyperscript`) or upstream itself.\n * `evaluate(source)` of a `behavior … end` program defines the behavior; `processNode`\n * initialises the scripted elements under a node.\n */\nexport interface HyperscriptHost {\n evaluate: (source: string) => unknown;\n processNode?: (node: Node) => void;\n}\n\n/** The window globals a host installs itself as. */\nexport interface HyperscriptWindow {\n hyperfixi?: HyperscriptHost;\n _hyperscript?: HyperscriptHost;\n}\n\n/** The host on `window`: `hyperfixi` (the engine's bundle) or `_hyperscript` (upstream's name). */\nexport function resolveRuntime(): HyperscriptHost | null {\n if (typeof window === 'undefined') return null;\n const win = window as unknown as HyperscriptWindow;\n const host = win.hyperfixi ?? win._hyperscript;\n return host && typeof host.evaluate === 'function' ? host : null;\n}\n\n/**\n * Define a behavior on a host from its schema's source, as a page's\n * `<script type=\"text/hyperscript\">` would. Throws when there is no host or the source\n * does not parse.\n */\nexport function defineBehavior(schema: BehaviorSchema, host?: HyperscriptHost): void {\n const hf = host ?? resolveRuntime();\n if (!hf) {\n throw new Error(\n `No hyperscript host found: load @hyperfixi/engine (hyperfixi-hs.js) before defining ${schema.name}.`\n );\n }\n try {\n hf.evaluate(schema.source);\n } catch (e) {\n throw new Error(`Failed to define ${schema.name} behavior: ${(e as Error).message}`);\n }\n}\n"],"mappings":"scAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,iBAAAC,EAAA,iBAAAC,EAAA,eAAAC,ICEO,IAAMC,EAA6B,CACxC,KAAM,OACN,SAAU,KACV,KAAM,OACN,QAAS,QACT,YACE,mJAEF,WAAY,CACV,CACE,KAAM,cACN,KAAM,SACN,SAAU,GACV,QAAS,aACT,KAAM,CAAC,aAAc,UAAU,EAC/B,YAAa,oEACf,EACA,CACE,KAAM,YACN,KAAM,SACN,SAAU,GACV,QAAS,EACT,YAAa,uCACf,EACA,CACE,KAAM,OACN,KAAM,UACN,SAAU,GACV,QAAS,GACT,YAAa,qDACf,EACA,CACE,KAAM,cACN,KAAM,SACN,SAAU,GACV,QAAS,SACT,YAAa,mDACf,CACF,EACA,OAAQ,CACN,CACE,KAAM,cACN,YAAa,oEACf,EACA,CAAE,KAAM,eAAgB,YAAa,uCAAwC,CAC/E,EACA,aAAc,CACZ,2CACA,uEACA,sEACA,+EACF,EACA,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAuEL,KAAK,CACV,ECzBO,SAASC,GAAyC,CACvD,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAMC,EAAM,OACNC,EAAOD,EAAI,WAAaA,EAAI,aAClC,OAAOC,GAAQ,OAAOA,EAAK,UAAa,WAAaA,EAAO,IAC9D,CAOO,SAASC,EAAeC,EAAwBF,EAA8B,CACnF,IAAMG,EAAKH,GAAQF,EAAe,EAClC,GAAI,CAACK,EACH,MAAM,IAAI,MACR,uFAAuFD,EAAO,IAAI,GACpG,EAEF,GAAI,CACFC,EAAG,SAASD,EAAO,MAAM,CAC3B,OAASE,EAAG,CACV,MAAM,IAAI,MAAM,oBAAoBF,EAAO,IAAI,cAAeE,EAAY,OAAO,EAAE,CACrF,CACF,CF3GO,IAAMC,EAAaC,EAAW,OACxBC,EAAeD,EAM5B,eAAsBE,EAAaC,EAAuC,CACxEC,EAAeJ,EAAYG,CAAI,CACjC,CAGIE,EAAe,GACjBH,EAAa,EAAE,MAAM,QAAQ,KAAK,EAGpC,IAAOI,EAAQ,CACb,OAAQN,EAAW,OACnB,SAAUA,EACV,SAAUE,CACZ","names":["tabs_exports","__export","tabs_default","registerTabs","tabsMetadata","tabsSource","tabsSchema","resolveRuntime","win","host","defineBehavior","schema","hf","e","tabsSource","tabsSchema","tabsMetadata","registerTabs","host","defineBehavior","resolveRuntime","tabs_default"]}
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
"use strict";var HyperFixiBehaviors=(()=>{var
|
|
2
|
-
behavior Toggleable(cls,
|
|
1
|
+
"use strict";var HyperFixiBehaviors=(()=>{var a=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var d=(e,t)=>{for(var r in t)a(e,r,{get:t[r],enumerable:!0})},h=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of g(t))!m.call(e,i)&&i!==r&&a(e,i,{get:()=>t[i],enumerable:!(n=p(t,i))||n.enumerable});return e};var f=e=>h(a({},"__esModule",{value:!0}),e);var b={};d(b,{default:()=>y,registerToggleable:()=>c,toggleableMetadata:()=>v,toggleableSource:()=>u});var o={name:"Toggleable",category:"form",tier:"core",version:"1.0.0",description:"Toggles a CSS class on click",parameters:[{name:"cls",type:"string",optional:!0,default:"active",description:"CSS class to toggle"},{name:"targetEl",type:"selector",optional:!0,default:"me",description:"Element to toggle the class on (not `target`: that is hyperscript's name for the event target)"}],events:[{name:"toggleable:on",description:"Fired when class is added"},{name:"toggleable:off",description:"Fired when class is removed"}],source:`
|
|
2
|
+
behavior Toggleable(cls, targetEl)
|
|
3
3
|
init
|
|
4
|
-
if cls is undefined
|
|
5
|
-
|
|
6
|
-
end
|
|
7
|
-
if target is undefined
|
|
8
|
-
set target to me
|
|
9
|
-
end
|
|
4
|
+
if cls is undefined set element's cls to "active" end
|
|
5
|
+
if targetEl is undefined set element's targetEl to me end
|
|
10
6
|
end
|
|
11
7
|
on click
|
|
12
|
-
toggle .{cls} on
|
|
13
|
-
js(
|
|
14
|
-
var eventName =
|
|
15
|
-
|
|
8
|
+
toggle .{cls} on targetEl
|
|
9
|
+
js(targetEl, cls)
|
|
10
|
+
var eventName = targetEl.classList.contains(cls) ? 'toggleable:on' : 'toggleable:off';
|
|
11
|
+
targetEl.dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
|
|
16
12
|
end
|
|
17
13
|
end
|
|
18
|
-
end`.trim()};function s(){if(typeof window>"u")return null;let t=
|
|
14
|
+
end`.trim()};function s(){if(typeof window>"u")return null;let e=window,t=e.hyperfixi??e._hyperscript;return t&&typeof t.evaluate=="function"?t:null}function l(e,t){let r=t??s();if(!r)throw new Error(`No hyperscript host found: load @hyperfixi/engine (hyperfixi-hs.js) before defining ${e.name}.`);try{r.evaluate(e.source)}catch(n){throw new Error(`Failed to define ${e.name} behavior: ${n.message}`)}}var u=o.source,v=o;async function c(e){l(o,e)}s()&&c().catch(console.error);var y={source:o.source,metadata:o,register:c};return f(b);})();
|
|
19
15
|
//# sourceMappingURL=toggleable.browser.global.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/behaviors/toggleable.ts","../src/schemas/toggleable.schema.ts","../src/schemas/types.ts"],"sourcesContent":["/**\n * Toggleable Behavior\n *\n * A behavior that toggles a CSS class on click.\n * Useful for accordions, dropdowns, and toggle buttons.\n *\n *
|
|
1
|
+
{"version":3,"sources":["../src/behaviors/toggleable.ts","../src/schemas/toggleable.schema.ts","../src/schemas/types.ts"],"sourcesContent":["/**\n * Toggleable Behavior\n *\n * A behavior that toggles a CSS class on click.\n * Useful for accordions, dropdowns, and toggle buttons.\n *\n * Defined from its hyperscript `source` (the single source of truth shared with\n * the browser bundle and patterns-reference). The `.{cls}` dynamic class\n * selector resolves the behavior's `cls` parameter at runtime.\n *\n * @example\n * ```html\n * <button _=\"install Toggleable\">Toggle</button>\n *\n * <button _=\"install Toggleable(cls: 'expanded', targetEl: #menu)\">Menu</button>\n * ```\n */\n\nimport { toggleableSchema } from '../schemas/toggleable.schema';\nimport { defineBehavior, resolveRuntime } from '../schemas/types';\nimport type { HyperscriptHost } from '../schemas/types';\n\n// Re-export schema-derived values for backwards compatibility\nexport const toggleableSource = toggleableSchema.source;\nexport const toggleableMetadata = toggleableSchema;\n\n/**\n * Define the Toggleable behavior on a hyperscript host (`window.hyperfixi` when none is given)\n * from its source, as a `<script type=\"text/hyperscript\">` would.\n */\nexport async function registerToggleable(host?: HyperscriptHost): Promise<void> {\n defineBehavior(toggleableSchema, host);\n}\n\n// Auto-register when loaded as a script tag\nif (resolveRuntime()) {\n registerToggleable().catch(console.error);\n}\n\nexport default {\n source: toggleableSchema.source,\n metadata: toggleableSchema,\n register: registerToggleable,\n};\n","import type { BehaviorSchema } from './types';\n\n/**\n * Toggleable Behavior Schema\n *\n * Toggles a CSS class on click.\n * Useful for accordions, dropdowns, and toggle buttons.\n */\nexport const toggleableSchema: BehaviorSchema = {\n name: 'Toggleable',\n category: 'form',\n tier: 'core',\n version: '1.0.0',\n description: 'Toggles a CSS class on click',\n parameters: [\n {\n name: 'cls',\n type: 'string',\n optional: true,\n default: 'active',\n description: 'CSS class to toggle',\n },\n {\n name: 'targetEl',\n type: 'selector',\n optional: true,\n default: 'me',\n description:\n \"Element to toggle the class on (not `target`: that is hyperscript's name for the event target)\",\n },\n ],\n events: [\n { name: 'toggleable:on', description: 'Fired when class is added' },\n { name: 'toggleable:off', description: 'Fired when class is removed' },\n ],\n source: `\nbehavior Toggleable(cls, targetEl)\n init\n if cls is undefined set element's cls to \"active\" end\n if targetEl is undefined set element's targetEl to me end\n end\n on click\n toggle .{cls} on targetEl\n js(targetEl, cls)\n var eventName = targetEl.classList.contains(cls) ? 'toggleable:on' : 'toggleable:off';\n targetEl.dispatchEvent(new CustomEvent(eventName, { bubbles: true }));\n end\n end\nend`.trim(),\n};\n","/**\n * Behavior Schema Types\n *\n * Defines the structure for behavior metadata used for:\n * - Code generation (TypeScript types, documentation)\n * - Runtime categorization and lazy loading\n * - Tooling and IDE integration\n */\n\n/**\n * Behavior categories for organization.\n * - ui: Visual interaction behaviors (drag, resize, sort)\n * - data: Data manipulation behaviors (remove, transform)\n * - animation: Animation and transition behaviors\n * - form: Form-related behaviors (validation, toggle states)\n * - layout: Layout behaviors (sticky, scrollspy)\n */\nexport type BehaviorCategory = 'ui' | 'data' | 'animation' | 'form' | 'layout';\n\n/**\n * Loading tiers for lazy loading optimization.\n * - core: Always loaded (essential behaviors)\n * - common: High usage (loaded for typical apps)\n * - optional: Low usage (loaded on demand)\n */\nexport type BehaviorTier = 'core' | 'common' | 'optional';\n\n/**\n * Schema for a behavior parameter.\n */\nexport interface ParameterSchema {\n name: string;\n type: 'string' | 'number' | 'boolean' | 'selector' | 'element';\n optional?: boolean;\n default?: string | number | boolean;\n /** Allowed values for constrained parameters */\n enum?: string[];\n description: string;\n}\n\n/**\n * Schema for a behavior event.\n */\nexport interface EventSchema {\n name: string;\n description: string;\n}\n\n/**\n * Complete schema for a behavior.\n * This is the source of truth for all behavior metadata.\n */\nexport interface BehaviorSchema {\n /** Behavior name (PascalCase) */\n name: string;\n /** Category for organization */\n category: BehaviorCategory;\n /** Loading tier for lazy loading */\n tier: BehaviorTier;\n /** Semantic version */\n version: string;\n /** Human-readable description */\n description: string;\n /** Parameter definitions */\n parameters: ParameterSchema[];\n /** Event definitions */\n events: EventSchema[];\n /** The hyperscript behavior source code */\n source: string;\n /** Optional requirements or notes */\n requirements?: string[];\n}\n\n/**\n * Runtime behavior module interface: a behavior's source, its schema, and the function that\n * defines it on a hyperscript host.\n */\nexport interface BehaviorModule {\n source: string;\n metadata: BehaviorSchema;\n register: (host?: HyperscriptHost) => Promise<void>;\n}\n\n/**\n * A hyperscript host, in upstream `_hyperscript`'s shape: `@hyperfixi/engine` (the\n * `hyperfixi-hs.js` bundle, `window.hyperfixi` / `window._hyperscript`) or upstream itself.\n * `evaluate(source)` of a `behavior … end` program defines the behavior; `processNode`\n * initialises the scripted elements under a node.\n */\nexport interface HyperscriptHost {\n evaluate: (source: string) => unknown;\n processNode?: (node: Node) => void;\n}\n\n/** The window globals a host installs itself as. */\nexport interface HyperscriptWindow {\n hyperfixi?: HyperscriptHost;\n _hyperscript?: HyperscriptHost;\n}\n\n/** The host on `window`: `hyperfixi` (the engine's bundle) or `_hyperscript` (upstream's name). */\nexport function resolveRuntime(): HyperscriptHost | null {\n if (typeof window === 'undefined') return null;\n const win = window as unknown as HyperscriptWindow;\n const host = win.hyperfixi ?? win._hyperscript;\n return host && typeof host.evaluate === 'function' ? host : null;\n}\n\n/**\n * Define a behavior on a host from its schema's source, as a page's\n * `<script type=\"text/hyperscript\">` would. Throws when there is no host or the source\n * does not parse.\n */\nexport function defineBehavior(schema: BehaviorSchema, host?: HyperscriptHost): void {\n const hf = host ?? resolveRuntime();\n if (!hf) {\n throw new Error(\n `No hyperscript host found: load @hyperfixi/engine (hyperfixi-hs.js) before defining ${schema.name}.`\n );\n }\n try {\n hf.evaluate(schema.source);\n } catch (e) {\n throw new Error(`Failed to define ${schema.name} behavior: ${(e as Error).message}`);\n }\n}\n"],"mappings":"scAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,uBAAAC,EAAA,uBAAAC,EAAA,qBAAAC,ICQO,IAAMC,EAAmC,CAC9C,KAAM,aACN,SAAU,OACV,KAAM,OACN,QAAS,QACT,YAAa,+BACb,WAAY,CACV,CACE,KAAM,MACN,KAAM,SACN,SAAU,GACV,QAAS,SACT,YAAa,qBACf,EACA,CACE,KAAM,WACN,KAAM,WACN,SAAU,GACV,QAAS,KACT,YACE,gGACJ,CACF,EACA,OAAQ,CACN,CAAE,KAAM,gBAAiB,YAAa,2BAA4B,EAClE,CAAE,KAAM,iBAAkB,YAAa,6BAA8B,CACvE,EACA,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAaL,KAAK,CACV,ECoDO,SAASC,GAAyC,CACvD,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAMC,EAAM,OACNC,EAAOD,EAAI,WAAaA,EAAI,aAClC,OAAOC,GAAQ,OAAOA,EAAK,UAAa,WAAaA,EAAO,IAC9D,CAOO,SAASC,EAAeC,EAAwBF,EAA8B,CACnF,IAAMG,EAAKH,GAAQF,EAAe,EAClC,GAAI,CAACK,EACH,MAAM,IAAI,MACR,uFAAuFD,EAAO,IAAI,GACpG,EAEF,GAAI,CACFC,EAAG,SAASD,EAAO,MAAM,CAC3B,OAASE,EAAG,CACV,MAAM,IAAI,MAAM,oBAAoBF,EAAO,IAAI,cAAeE,EAAY,OAAO,EAAE,CACrF,CACF,CFtGO,IAAMC,EAAmBC,EAAiB,OACpCC,EAAqBD,EAMlC,eAAsBE,EAAmBC,EAAuC,CAC9EC,EAAeJ,EAAkBG,CAAI,CACvC,CAGIE,EAAe,GACjBH,EAAmB,EAAE,MAAM,QAAQ,KAAK,EAG1C,IAAOI,EAAQ,CACb,OAAQN,EAAiB,OACzB,SAAUA,EACV,SAAUE,CACZ","names":["toggleable_exports","__export","toggleable_default","registerToggleable","toggleableMetadata","toggleableSource","toggleableSchema","resolveRuntime","win","host","defineBehavior","schema","hf","e","toggleableSource","toggleableSchema","toggleableMetadata","registerToggleable","host","defineBehavior","resolveRuntime","toggleable_default"]}
|
|
@@ -66,35 +66,28 @@ interface BehaviorSchema {
|
|
|
66
66
|
requirements?: string[];
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* Runtime behavior module interface
|
|
70
|
-
*
|
|
69
|
+
* Runtime behavior module interface: a behavior's source, its schema, and the function that
|
|
70
|
+
* defines it on a hyperscript host.
|
|
71
71
|
*/
|
|
72
72
|
interface BehaviorModule {
|
|
73
73
|
source: string;
|
|
74
74
|
metadata: BehaviorSchema;
|
|
75
|
-
register: (
|
|
75
|
+
register: (host?: HyperscriptHost) => Promise<void>;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* A hyperscript host, in upstream `_hyperscript`'s shape: `@hyperfixi/engine` (the
|
|
79
|
+
* `hyperfixi-hs.js` bundle, `window.hyperfixi` / `window._hyperscript`) or upstream itself.
|
|
80
|
+
* `evaluate(source)` of a `behavior … end` program defines the behavior; `processNode`
|
|
81
|
+
* initialises the scripted elements under a node.
|
|
79
82
|
*/
|
|
80
|
-
interface
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
message: string;
|
|
89
|
-
line?: number;
|
|
90
|
-
column?: number;
|
|
91
|
-
}>;
|
|
92
|
-
};
|
|
93
|
-
execute: (ast: unknown, ctx: unknown) => Promise<unknown>;
|
|
94
|
-
createContext?: () => {
|
|
95
|
-
locals: Map<string, unknown>;
|
|
96
|
-
globals: Map<string, unknown>;
|
|
97
|
-
};
|
|
83
|
+
interface HyperscriptHost {
|
|
84
|
+
evaluate: (source: string) => unknown;
|
|
85
|
+
processNode?: (node: Node) => void;
|
|
86
|
+
}
|
|
87
|
+
/** The window globals a host installs itself as. */
|
|
88
|
+
interface HyperscriptWindow {
|
|
89
|
+
hyperfixi?: HyperscriptHost;
|
|
90
|
+
_hyperscript?: HyperscriptHost;
|
|
98
91
|
}
|
|
99
92
|
|
|
100
|
-
export type { BehaviorSchema as B, EventSchema as E,
|
|
93
|
+
export type { BehaviorSchema as B, EventSchema as E, HyperscriptHost as H, ParameterSchema as P, BehaviorModule as a, BehaviorCategory as b, BehaviorTier as c, HyperscriptWindow as d };
|
|
@@ -66,35 +66,28 @@ interface BehaviorSchema {
|
|
|
66
66
|
requirements?: string[];
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* Runtime behavior module interface
|
|
70
|
-
*
|
|
69
|
+
* Runtime behavior module interface: a behavior's source, its schema, and the function that
|
|
70
|
+
* defines it on a hyperscript host.
|
|
71
71
|
*/
|
|
72
72
|
interface BehaviorModule {
|
|
73
73
|
source: string;
|
|
74
74
|
metadata: BehaviorSchema;
|
|
75
|
-
register: (
|
|
75
|
+
register: (host?: HyperscriptHost) => Promise<void>;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* A hyperscript host, in upstream `_hyperscript`'s shape: `@hyperfixi/engine` (the
|
|
79
|
+
* `hyperfixi-hs.js` bundle, `window.hyperfixi` / `window._hyperscript`) or upstream itself.
|
|
80
|
+
* `evaluate(source)` of a `behavior … end` program defines the behavior; `processNode`
|
|
81
|
+
* initialises the scripted elements under a node.
|
|
79
82
|
*/
|
|
80
|
-
interface
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
message: string;
|
|
89
|
-
line?: number;
|
|
90
|
-
column?: number;
|
|
91
|
-
}>;
|
|
92
|
-
};
|
|
93
|
-
execute: (ast: unknown, ctx: unknown) => Promise<unknown>;
|
|
94
|
-
createContext?: () => {
|
|
95
|
-
locals: Map<string, unknown>;
|
|
96
|
-
globals: Map<string, unknown>;
|
|
97
|
-
};
|
|
83
|
+
interface HyperscriptHost {
|
|
84
|
+
evaluate: (source: string) => unknown;
|
|
85
|
+
processNode?: (node: Node) => void;
|
|
86
|
+
}
|
|
87
|
+
/** The window globals a host installs itself as. */
|
|
88
|
+
interface HyperscriptWindow {
|
|
89
|
+
hyperfixi?: HyperscriptHost;
|
|
90
|
+
_hyperscript?: HyperscriptHost;
|
|
98
91
|
}
|
|
99
92
|
|
|
100
|
-
export type { BehaviorSchema as B, EventSchema as E,
|
|
93
|
+
export type { BehaviorSchema as B, EventSchema as E, HyperscriptHost as H, ParameterSchema as P, BehaviorModule as a, BehaviorCategory as b, BehaviorTier as c, HyperscriptWindow as d };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperfixi/behaviors",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Reusable hyperscript behaviors for
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Reusable hyperscript behaviors for @hyperfixi/engine (hyperfixi-hs.js) and upstream _hyperscript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -108,16 +108,16 @@
|
|
|
108
108
|
"build:watch": "tsup --watch",
|
|
109
109
|
"pretypecheck": "npm run generate",
|
|
110
110
|
"typecheck": "tsc --noEmit",
|
|
111
|
-
"pretest": "npm run generate",
|
|
111
|
+
"pretest": "npm run generate && bash ../../scripts/ensure-fresh.sh engine",
|
|
112
112
|
"test": "vitest run",
|
|
113
113
|
"test:watch": "vitest",
|
|
114
114
|
"test:check": "VITEST_QUIET=1 bash ../../scripts/vitest-run.sh --reporter=dot"
|
|
115
115
|
},
|
|
116
116
|
"peerDependencies": {
|
|
117
|
-
"@hyperfixi/
|
|
117
|
+
"@hyperfixi/engine": "^4.0.0"
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
|
-
"@hyperfixi/
|
|
120
|
+
"@hyperfixi/engine": "^4.0.0",
|
|
121
121
|
"tsup": "^8.0.0",
|
|
122
122
|
"typescript": "^5.0.0",
|
|
123
123
|
"vitest": "^5.0.2"
|
package/src/behavior-resolver.ts
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The behavior sources, by name, and the function that defines them on a host.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* import { createBehaviorResolver } from '@hyperfixi/behaviors/resolver';
|
|
12
|
-
*
|
|
13
|
-
* const resolve = createBehaviorResolver(window.hyperfixi, window._hyperscript.behaviors);
|
|
14
|
-
* window._hyperscript.behaviors.resolve = resolve;
|
|
15
|
-
*
|
|
16
|
-
* // Now `install Toggleable` will compile the behavior on first use
|
|
17
|
-
* ```
|
|
4
|
+
* Every consumer — the npm `register*()` functions, the browser bundle
|
|
5
|
+
* (`resolver.browser.global.js`) and `@hyperfixi/patterns-reference` — reads the same
|
|
6
|
+
* `source` string from each schema in `src/schemas/`. A host defines a behavior from its
|
|
7
|
+
* source exactly as it would from a page's `<script type="text/hyperscript">`.
|
|
18
8
|
*/
|
|
19
9
|
|
|
20
10
|
import { toggleableSchema } from './schemas/toggleable.schema';
|
|
@@ -28,6 +18,7 @@ import { tabsSchema } from './schemas/tabs.schema';
|
|
|
28
18
|
import { focusTrapSchema } from './schemas/focustrap.schema';
|
|
29
19
|
import { sortableSchema } from './schemas/sortable.schema';
|
|
30
20
|
import { resizableSchema } from './schemas/resizable.schema';
|
|
21
|
+
import type { HyperscriptHost } from './schemas/types';
|
|
31
22
|
|
|
32
23
|
export const BEHAVIOR_SOURCES: Record<string, string> = {
|
|
33
24
|
Toggleable: toggleableSchema.source,
|
|
@@ -43,49 +34,26 @@ export const BEHAVIOR_SOURCES: Record<string, string> = {
|
|
|
43
34
|
Resizable: resizableSchema.source,
|
|
44
35
|
};
|
|
45
36
|
|
|
46
|
-
interface CompileResult {
|
|
47
|
-
ok: boolean;
|
|
48
|
-
ast?: any;
|
|
49
|
-
errors?: unknown[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface BehaviorAPI {
|
|
53
|
-
set(name: string, definition: any): void;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface HyperFixiAPI {
|
|
57
|
-
compileSync(code: string, options?: { traditional?: boolean }): CompileResult;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
37
|
/**
|
|
61
|
-
*
|
|
38
|
+
* Define the named behaviors (every one by default) on a host. A source that does not
|
|
39
|
+
* parse is reported and skipped, so one bad behavior does not take the others with it.
|
|
62
40
|
*
|
|
63
|
-
*
|
|
64
|
-
* When called with a behavior name, it looks up the source, compiles it,
|
|
65
|
-
* and registers the resulting AST in the behavior registry.
|
|
66
|
-
*
|
|
67
|
-
* @param hyperfixi - The hyperfixi API (needs compileSync)
|
|
68
|
-
* @param behaviorAPI - The behavior registry API (needs set)
|
|
69
|
-
* @returns Resolver function: (name: string) => boolean
|
|
41
|
+
* @returns the names that were defined
|
|
70
42
|
*/
|
|
71
|
-
export function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
):
|
|
75
|
-
|
|
43
|
+
export function defineBehaviors(
|
|
44
|
+
host: HyperscriptHost,
|
|
45
|
+
names: readonly string[] = Object.keys(BEHAVIOR_SOURCES)
|
|
46
|
+
): string[] {
|
|
47
|
+
const defined: string[] = [];
|
|
48
|
+
for (const name of names) {
|
|
76
49
|
const source = BEHAVIOR_SOURCES[name];
|
|
77
|
-
if (!source)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
eventHandlers: ast.eventHandlers,
|
|
87
|
-
initBlock: ast.initBlock,
|
|
88
|
-
});
|
|
89
|
-
return true;
|
|
90
|
-
};
|
|
50
|
+
if (!source) continue;
|
|
51
|
+
try {
|
|
52
|
+
host.evaluate(source);
|
|
53
|
+
defined.push(name);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`[behaviors] ${name} did not parse: ${(e as Error).message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return defined;
|
|
91
59
|
}
|
|
@@ -2,36 +2,28 @@ import { describe, it, expect, vi } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
describe('AutoDismiss behavior', () => {
|
|
4
4
|
describe('registerAutoDismiss', () => {
|
|
5
|
-
it('
|
|
5
|
+
it('defines the behavior on the host from its hyperscript source', async () => {
|
|
6
6
|
const { registerAutoDismiss, autoDismissSource } = await import('./autodismiss');
|
|
7
|
-
const
|
|
8
|
-
compileSync: vi.fn().mockReturnValue({ ok: true, ast: { type: 'behavior' } }),
|
|
9
|
-
execute: vi.fn().mockResolvedValue(undefined),
|
|
10
|
-
createContext: vi.fn().mockReturnValue({ locals: new Map(), globals: new Map() }),
|
|
11
|
-
};
|
|
7
|
+
const host = { evaluate: vi.fn() };
|
|
12
8
|
|
|
13
|
-
await registerAutoDismiss(
|
|
9
|
+
await registerAutoDismiss(host);
|
|
14
10
|
|
|
15
|
-
expect(
|
|
16
|
-
expect(mock.execute).toHaveBeenCalledWith(
|
|
17
|
-
{ type: 'behavior' },
|
|
18
|
-
expect.objectContaining({ locals: expect.any(Map), globals: expect.any(Map) })
|
|
19
|
-
);
|
|
11
|
+
expect(host.evaluate).toHaveBeenCalledWith(autoDismissSource);
|
|
20
12
|
});
|
|
21
13
|
|
|
22
|
-
it('should throw
|
|
14
|
+
it('should throw when the host rejects the source', async () => {
|
|
23
15
|
const { registerAutoDismiss } = await import('./autodismiss');
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
const host = {
|
|
17
|
+
evaluate: vi.fn(() => {
|
|
18
|
+
throw new Error('boom');
|
|
19
|
+
}),
|
|
28
20
|
};
|
|
29
|
-
await expect(registerAutoDismiss(
|
|
21
|
+
await expect(registerAutoDismiss(host)).rejects.toThrowError(/Failed to define AutoDismiss/);
|
|
30
22
|
});
|
|
31
23
|
|
|
32
|
-
it('should throw when no
|
|
24
|
+
it('should throw when no host is available', async () => {
|
|
33
25
|
const { registerAutoDismiss } = await import('./autodismiss');
|
|
34
|
-
await expect(registerAutoDismiss(undefined)).rejects.toThrowError(/
|
|
26
|
+
await expect(registerAutoDismiss(undefined)).rejects.toThrowError(/No hyperscript host/);
|
|
35
27
|
});
|
|
36
28
|
});
|
|
37
29
|
|
|
@@ -5,39 +5,24 @@
|
|
|
5
5
|
* Supports pause-on-hover and fade effect using standard setTimeout/clearTimeout
|
|
6
6
|
* (in its hyperscript `source`'s js() body).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
8
|
+
* Defined from its hyperscript `source` (the single source of truth shared with
|
|
9
|
+
* the browser bundle and patterns-reference).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { autoDismissSchema } from '../schemas/autodismiss.schema';
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
13
|
+
import { defineBehavior, resolveRuntime } from '../schemas/types';
|
|
14
|
+
import type { HyperscriptHost } from '../schemas/types';
|
|
15
15
|
|
|
16
16
|
// Re-export schema-derived values
|
|
17
17
|
export const autoDismissSource = autoDismissSchema.source;
|
|
18
18
|
export const autoDismissMetadata = autoDismissSchema;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* Define the AutoDismiss behavior on a hyperscript host (`window.hyperfixi` when none is given)
|
|
22
|
+
* from its source, as a `<script type="text/hyperscript">` would.
|
|
23
23
|
*/
|
|
24
|
-
export async function registerAutoDismiss(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!hf) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
'LokaScript not found. Make sure @hyperfixi/core is loaded before registering behaviors.'
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const result = hf.compileSync(autoDismissSchema.source, { traditional: true });
|
|
34
|
-
|
|
35
|
-
if (!result.ok) {
|
|
36
|
-
throw new Error(`Failed to compile AutoDismiss behavior: ${JSON.stringify(result.errors)}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };
|
|
40
|
-
await hf.execute(result.ast, ctx);
|
|
24
|
+
export async function registerAutoDismiss(host?: HyperscriptHost): Promise<void> {
|
|
25
|
+
defineBehavior(autoDismissSchema, host);
|
|
41
26
|
}
|
|
42
27
|
|
|
43
28
|
// Auto-register when loaded as a script tag
|
|
@@ -2,38 +2,30 @@ import { describe, it, expect, vi } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
describe('ClickOutside behavior', () => {
|
|
4
4
|
describe('registerClickOutside', () => {
|
|
5
|
-
it('
|
|
5
|
+
it('defines the behavior on the host from its hyperscript source', async () => {
|
|
6
6
|
const { registerClickOutside, clickOutsideSource } = await import('./clickoutside');
|
|
7
|
-
const
|
|
8
|
-
compileSync: vi.fn().mockReturnValue({ ok: true, ast: { type: 'behavior' } }),
|
|
9
|
-
execute: vi.fn().mockResolvedValue(undefined),
|
|
10
|
-
createContext: vi.fn().mockReturnValue({ locals: new Map(), globals: new Map() }),
|
|
11
|
-
};
|
|
7
|
+
const host = { evaluate: vi.fn() };
|
|
12
8
|
|
|
13
|
-
await registerClickOutside(
|
|
9
|
+
await registerClickOutside(host);
|
|
14
10
|
|
|
15
|
-
expect(
|
|
16
|
-
expect(mock.execute).toHaveBeenCalledWith(
|
|
17
|
-
{ type: 'behavior' },
|
|
18
|
-
expect.objectContaining({ locals: expect.any(Map), globals: expect.any(Map) })
|
|
19
|
-
);
|
|
11
|
+
expect(host.evaluate).toHaveBeenCalledWith(clickOutsideSource);
|
|
20
12
|
});
|
|
21
13
|
|
|
22
|
-
it('should throw
|
|
14
|
+
it('should throw when the host rejects the source', async () => {
|
|
23
15
|
const { registerClickOutside } = await import('./clickoutside');
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
const host = {
|
|
17
|
+
evaluate: vi.fn(() => {
|
|
18
|
+
throw new Error('boom');
|
|
19
|
+
}),
|
|
28
20
|
};
|
|
29
|
-
await expect(registerClickOutside(
|
|
30
|
-
/Failed to
|
|
21
|
+
await expect(registerClickOutside(host)).rejects.toThrowError(
|
|
22
|
+
/Failed to define ClickOutside/
|
|
31
23
|
);
|
|
32
24
|
});
|
|
33
25
|
|
|
34
|
-
it('should throw when no
|
|
26
|
+
it('should throw when no host is available', async () => {
|
|
35
27
|
const { registerClickOutside } = await import('./clickoutside');
|
|
36
|
-
await expect(registerClickOutside(undefined)).rejects.toThrowError(/
|
|
28
|
+
await expect(registerClickOutside(undefined)).rejects.toThrowError(/No hyperscript host/);
|
|
37
29
|
});
|
|
38
30
|
});
|
|
39
31
|
|
|
@@ -4,39 +4,24 @@
|
|
|
4
4
|
* Fires a clickoutside event when the user clicks outside the element.
|
|
5
5
|
* Uses pointerdown instead of click to avoid timing issues.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
7
|
+
* Defined from its hyperscript `source` (the single source of truth shared with
|
|
8
|
+
* the browser bundle and patterns-reference).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { clickOutsideSchema } from '../schemas/clickoutside.schema';
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
12
|
+
import { defineBehavior, resolveRuntime } from '../schemas/types';
|
|
13
|
+
import type { HyperscriptHost } from '../schemas/types';
|
|
14
14
|
|
|
15
15
|
// Re-export schema-derived values
|
|
16
16
|
export const clickOutsideSource = clickOutsideSchema.source;
|
|
17
17
|
export const clickOutsideMetadata = clickOutsideSchema;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Define the ClickOutside behavior on a hyperscript host (`window.hyperfixi` when none is given)
|
|
21
|
+
* from its source, as a `<script type="text/hyperscript">` would.
|
|
22
22
|
*/
|
|
23
|
-
export async function registerClickOutside(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (!hf) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
'LokaScript not found. Make sure @hyperfixi/core is loaded before registering behaviors.'
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const result = hf.compileSync(clickOutsideSchema.source, { traditional: true });
|
|
33
|
-
|
|
34
|
-
if (!result.ok) {
|
|
35
|
-
throw new Error(`Failed to compile ClickOutside behavior: ${JSON.stringify(result.errors)}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };
|
|
39
|
-
await hf.execute(result.ast, ctx);
|
|
23
|
+
export async function registerClickOutside(host?: HyperscriptHost): Promise<void> {
|
|
24
|
+
defineBehavior(clickOutsideSchema, host);
|
|
40
25
|
}
|
|
41
26
|
|
|
42
27
|
// Auto-register when loaded as a script tag
|