@hyperfixi/behaviors 2.2.1 → 2.3.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.
Files changed (131) hide show
  1. package/dist/all.browser.global.js +316 -54
  2. package/dist/all.browser.global.js.map +1 -1
  3. package/dist/autodismiss.browser.global.js +54 -2
  4. package/dist/autodismiss.browser.global.js.map +1 -1
  5. package/dist/behaviors/autodismiss.cjs +54 -18
  6. package/dist/behaviors/autodismiss.cjs.map +1 -1
  7. package/dist/behaviors/autodismiss.js +2 -2
  8. package/dist/behaviors/clickoutside.cjs +23 -4
  9. package/dist/behaviors/clickoutside.cjs.map +1 -1
  10. package/dist/behaviors/clickoutside.js +2 -2
  11. package/dist/behaviors/clipboard.cjs +43 -21
  12. package/dist/behaviors/clipboard.cjs.map +1 -1
  13. package/dist/behaviors/clipboard.js +2 -2
  14. package/dist/behaviors/focustrap.cjs +44 -3
  15. package/dist/behaviors/focustrap.cjs.map +1 -1
  16. package/dist/behaviors/focustrap.js +2 -2
  17. package/dist/behaviors/removable.cjs +7 -4
  18. package/dist/behaviors/removable.cjs.map +1 -1
  19. package/dist/behaviors/removable.js +2 -2
  20. package/dist/behaviors/resizable.cjs +22 -14
  21. package/dist/behaviors/resizable.cjs.map +1 -1
  22. package/dist/behaviors/resizable.js +2 -2
  23. package/dist/behaviors/scrollreveal.cjs +25 -13
  24. package/dist/behaviors/scrollreveal.cjs.map +1 -1
  25. package/dist/behaviors/scrollreveal.js +2 -2
  26. package/dist/behaviors/sortable.cjs +18 -12
  27. package/dist/behaviors/sortable.cjs.map +1 -1
  28. package/dist/behaviors/sortable.js +2 -2
  29. package/dist/behaviors/tabs.cjs +80 -11
  30. package/dist/behaviors/tabs.cjs.map +1 -1
  31. package/dist/behaviors/tabs.js +2 -2
  32. package/dist/behaviors/toggleable.cjs +5 -2
  33. package/dist/behaviors/toggleable.cjs.map +1 -1
  34. package/dist/behaviors/toggleable.js +2 -2
  35. package/dist/chunk-66U2RSYJ.js +100 -0
  36. package/dist/chunk-66U2RSYJ.js.map +1 -0
  37. package/dist/{chunk-2NSKQMNM.js → chunk-7O7UOSBH.js} +2 -2
  38. package/dist/{chunk-244WSZOL.js → chunk-EKKJVXDZ.js} +2 -2
  39. package/dist/{chunk-AFGA54SA.js → chunk-F3CASJ7Q.js} +2 -2
  40. package/dist/{chunk-YHQRFUWV.js → chunk-GALKOMJ5.js} +2 -2
  41. package/dist/{chunk-272NKUJ3.js → chunk-HMU342XE.js} +8 -5
  42. package/dist/chunk-HMU342XE.js.map +1 -0
  43. package/dist/chunk-HQDYH45I.js +137 -0
  44. package/dist/chunk-HQDYH45I.js.map +1 -0
  45. package/dist/{chunk-WTUVPVSD.js → chunk-IQM32TEY.js} +2 -2
  46. package/dist/{chunk-H5Y5ARYG.js → chunk-L52AGCMP.js} +2 -2
  47. package/dist/{chunk-H2MCHR5A.js → chunk-LQJOQTBZ.js} +2 -2
  48. package/dist/{chunk-7JDV2THA.js → chunk-MVNHMYIK.js} +11 -11
  49. package/dist/{chunk-BWZ6EGII.js → chunk-NC6I6T7T.js} +19 -13
  50. package/dist/{chunk-BWZ6EGII.js.map → chunk-NC6I6T7T.js.map} +1 -1
  51. package/dist/chunk-OJ3YWO3W.js +92 -0
  52. package/dist/chunk-OJ3YWO3W.js.map +1 -0
  53. package/dist/{chunk-64DIQGQI.js → chunk-SLOXO6IY.js} +24 -5
  54. package/dist/chunk-SLOXO6IY.js.map +1 -0
  55. package/dist/{chunk-RPERQTFU.js → chunk-TLLGH5KX.js} +2 -2
  56. package/dist/{chunk-V5IWDPDP.js → chunk-U5GNXZUF.js} +23 -15
  57. package/dist/{chunk-V5IWDPDP.js.map → chunk-U5GNXZUF.js.map} +1 -1
  58. package/dist/{chunk-GPTHAUWN.js → chunk-URZGNPTQ.js} +26 -14
  59. package/dist/chunk-URZGNPTQ.js.map +1 -0
  60. package/dist/chunk-WOTZO7K4.js +92 -0
  61. package/dist/chunk-WOTZO7K4.js.map +1 -0
  62. package/dist/{chunk-JKBWIJFI.js → chunk-WPOQIMCC.js} +2 -2
  63. package/dist/{chunk-WUHHMDAA.js → chunk-XYZ7FOXJ.js} +6 -3
  64. package/dist/{chunk-WUHHMDAA.js.map → chunk-XYZ7FOXJ.js.map} +1 -1
  65. package/dist/{chunk-DYHBKDKB.js → chunk-ZZ2WSAX3.js} +2 -2
  66. package/dist/clickoutside.browser.global.js +16 -5
  67. package/dist/clickoutside.browser.global.js.map +1 -1
  68. package/dist/clipboard.browser.global.js +43 -2
  69. package/dist/clipboard.browser.global.js.map +1 -1
  70. package/dist/core.browser.global.js +5 -2
  71. package/dist/core.browser.global.js.map +1 -1
  72. package/dist/focustrap.browser.global.js +46 -5
  73. package/dist/focustrap.browser.global.js.map +1 -1
  74. package/dist/index.cjs +321 -102
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.js +21 -21
  77. package/dist/loaders.cjs +321 -102
  78. package/dist/loaders.cjs.map +1 -1
  79. package/dist/loaders.js +11 -11
  80. package/dist/removable.browser.global.js +8 -5
  81. package/dist/removable.browser.global.js.map +1 -1
  82. package/dist/resizable.browser.global.js +24 -16
  83. package/dist/resizable.browser.global.js.map +1 -1
  84. package/dist/resolver.browser.global.js +407 -0
  85. package/dist/resolver.browser.global.js.map +1 -0
  86. package/dist/schemas/index.cjs +321 -102
  87. package/dist/schemas/index.cjs.map +1 -1
  88. package/dist/schemas/index.js +10 -10
  89. package/dist/scrollreveal.browser.global.js +27 -8
  90. package/dist/scrollreveal.browser.global.js.map +1 -1
  91. package/dist/sortable.browser.global.js +19 -13
  92. package/dist/sortable.browser.global.js.map +1 -1
  93. package/dist/tabs.browser.global.js +80 -2
  94. package/dist/tabs.browser.global.js.map +1 -1
  95. package/dist/toggleable.browser.global.js +6 -3
  96. package/dist/toggleable.browser.global.js.map +1 -1
  97. package/package.json +1 -1
  98. package/src/behavior-resolver.ts +91 -0
  99. package/src/browser-resolver-bundle.ts +42 -0
  100. package/src/schemas/autodismiss.schema.ts +54 -18
  101. package/src/schemas/clickoutside.schema.ts +22 -3
  102. package/src/schemas/clipboard.schema.ts +43 -21
  103. package/src/schemas/focustrap.schema.ts +44 -3
  104. package/src/schemas/removable.schema.ts +7 -4
  105. package/src/schemas/resizable.schema.ts +22 -14
  106. package/src/schemas/scrollreveal.schema.ts +25 -13
  107. package/src/schemas/sortable.schema.ts +18 -12
  108. package/src/schemas/tabs.schema.ts +80 -11
  109. package/src/schemas/toggleable.schema.ts +5 -2
  110. package/dist/chunk-272NKUJ3.js.map +0 -1
  111. package/dist/chunk-64DIQGQI.js.map +0 -1
  112. package/dist/chunk-GPTHAUWN.js.map +0 -1
  113. package/dist/chunk-GVJPA2EK.js +0 -68
  114. package/dist/chunk-GVJPA2EK.js.map +0 -1
  115. package/dist/chunk-QKDYW4VM.js +0 -70
  116. package/dist/chunk-QKDYW4VM.js.map +0 -1
  117. package/dist/chunk-RB7OB6OD.js +0 -51
  118. package/dist/chunk-RB7OB6OD.js.map +0 -1
  119. package/dist/chunk-ZQQZTMCJ.js +0 -64
  120. package/dist/chunk-ZQQZTMCJ.js.map +0 -1
  121. /package/dist/{chunk-2NSKQMNM.js.map → chunk-7O7UOSBH.js.map} +0 -0
  122. /package/dist/{chunk-244WSZOL.js.map → chunk-EKKJVXDZ.js.map} +0 -0
  123. /package/dist/{chunk-AFGA54SA.js.map → chunk-F3CASJ7Q.js.map} +0 -0
  124. /package/dist/{chunk-YHQRFUWV.js.map → chunk-GALKOMJ5.js.map} +0 -0
  125. /package/dist/{chunk-WTUVPVSD.js.map → chunk-IQM32TEY.js.map} +0 -0
  126. /package/dist/{chunk-H5Y5ARYG.js.map → chunk-L52AGCMP.js.map} +0 -0
  127. /package/dist/{chunk-H2MCHR5A.js.map → chunk-LQJOQTBZ.js.map} +0 -0
  128. /package/dist/{chunk-7JDV2THA.js.map → chunk-MVNHMYIK.js.map} +0 -0
  129. /package/dist/{chunk-RPERQTFU.js.map → chunk-TLLGH5KX.js.map} +0 -0
  130. /package/dist/{chunk-JKBWIJFI.js.map → chunk-WPOQIMCC.js.map} +0 -0
  131. /package/dist/{chunk-DYHBKDKB.js.map → chunk-ZZ2WSAX3.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 — Imperative Implementation\n *\n * WAI-ARIA compliant tabs with roving tabindex keyboard navigation.\n * Auto-wires role, aria-selected, aria-controls, and aria-labelledby.\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\nlet idCounter = 0;\n\nfunction ensureId(el: HTMLElement, prefix: string): string {\n if (!el.id) {\n el.id = `${prefix}-${++idCounter}`;\n }\n return el.id;\n}\n\n/**\n * Imperative installer for Tabs behavior.\n */\nfunction installTabs(element: HTMLElement, params: Record<string, any>): void {\n const orientation = params.orientation === 'vertical' ? 'vertical' : 'horizontal';\n const wrap = params.wrap !== false;\n const activeClass = (params.activeClass as string) || 'active';\n const initialIndex = typeof params.activeTab === 'number' ? params.activeTab : 0;\n\n // Discover tabs and panels\n const tabs = Array.from(element.querySelectorAll<HTMLElement>('[role=\"tab\"]'));\n const panels = Array.from(element.querySelectorAll<HTMLElement>('[role=\"tabpanel\"]'));\n\n if (tabs.length === 0 || panels.length === 0) return;\n\n // Find or mark the tablist\n const tablist = element.querySelector<HTMLElement>('[role=\"tablist\"]') || tabs[0].parentElement;\n if (tablist && !tablist.getAttribute('role')) {\n tablist.setAttribute('role', 'tablist');\n }\n if (tablist) {\n tablist.setAttribute('aria-orientation', orientation);\n }\n\n // Wire IDs, aria-controls, aria-labelledby\n const count = Math.min(tabs.length, panels.length);\n for (let i = 0; i < count; i++) {\n const tabId = ensureId(tabs[i], 'tab');\n const panelId = ensureId(panels[i], 'tabpanel');\n tabs[i].setAttribute('aria-controls', panelId);\n panels[i].setAttribute('aria-labelledby', tabId);\n }\n\n let currentIndex = -1;\n\n function selectTab(index: number, previousIndex: number): boolean {\n if (index === previousIndex || index < 0 || index >= count) return false;\n\n const detail = {\n tab: tabs[index],\n panel: panels[index],\n index,\n previousIndex,\n };\n\n // Cancelable before-event\n const changeEvent = new CustomEvent('tabs:change', {\n bubbles: true,\n cancelable: true,\n detail,\n });\n element.dispatchEvent(changeEvent);\n if (changeEvent.defaultPrevented) return false;\n\n // Deactivate previous\n if (previousIndex >= 0 && previousIndex < count) {\n tabs[previousIndex].setAttribute('aria-selected', 'false');\n tabs[previousIndex].setAttribute('tabindex', '-1');\n tabs[previousIndex].classList.remove(activeClass);\n panels[previousIndex].setAttribute('aria-hidden', 'true');\n panels[previousIndex].classList.remove(activeClass);\n }\n\n // Activate new\n tabs[index].setAttribute('aria-selected', 'true');\n tabs[index].setAttribute('tabindex', '0');\n tabs[index].classList.add(activeClass);\n panels[index].setAttribute('aria-hidden', 'false');\n panels[index].classList.add(activeClass);\n\n currentIndex = index;\n\n element.dispatchEvent(new CustomEvent('tabs:changed', { bubbles: true, detail }));\n return true;\n }\n\n // Initialize all tabs as inactive, then activate initial\n for (let i = 0; i < count; i++) {\n tabs[i].setAttribute('aria-selected', 'false');\n tabs[i].setAttribute('tabindex', '-1');\n tabs[i].classList.remove(activeClass);\n panels[i].setAttribute('aria-hidden', 'true');\n panels[i].classList.remove(activeClass);\n }\n\n const clamped = Math.max(0, Math.min(initialIndex, count - 1));\n // Directly activate without firing change event on init\n tabs[clamped].setAttribute('aria-selected', 'true');\n tabs[clamped].setAttribute('tabindex', '0');\n tabs[clamped].classList.add(activeClass);\n panels[clamped].setAttribute('aria-hidden', 'false');\n panels[clamped].classList.add(activeClass);\n currentIndex = clamped;\n\n // Click handler\n for (let i = 0; i < count; i++) {\n tabs[i].addEventListener('click', () => {\n selectTab(i, currentIndex);\n tabs[i].focus();\n });\n }\n\n // Keyboard handler on tablist\n const keyTarget = tablist || element;\n keyTarget.addEventListener('keydown', (e: KeyboardEvent) => {\n const isHorizontal = orientation === 'horizontal';\n const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';\n const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';\n\n let nextIndex: number | null = null;\n\n if (e.key === nextKey) {\n nextIndex = currentIndex + 1;\n if (nextIndex >= count) nextIndex = wrap ? 0 : null;\n } else if (e.key === prevKey) {\n nextIndex = currentIndex - 1;\n if (nextIndex < 0) nextIndex = wrap ? count - 1 : null;\n } else if (e.key === 'Home') {\n nextIndex = 0;\n } else if (e.key === 'End') {\n nextIndex = count - 1;\n }\n\n if (nextIndex !== null) {\n e.preventDefault();\n if (selectTab(nextIndex, currentIndex)) {\n tabs[nextIndex].focus();\n }\n }\n });\n\n // Programmatic tab selection\n element.addEventListener('tabs:select', ((e: CustomEvent) => {\n const index = e.detail?.index;\n if (typeof index === 'number') {\n selectTab(index, currentIndex);\n }\n }) as EventListener);\n}\n\n/**\n * Register the Tabs behavior with LokaScript.\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 syntheticNode = {\n type: 'behavior',\n name: 'Tabs',\n parameters: ['orientation', 'activeTab', 'wrap', 'activeClass'],\n eventHandlers: [],\n imperativeInstaller: installTabs,\n };\n const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };\n await hf.execute(syntheticNode, 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: [\n 'behavior Tabs(orientation, activeTab, wrap, activeClass)',\n ' init',\n ' if orientation is undefined set orientation to \"horizontal\"',\n ' if activeTab is undefined set activeTab to 0',\n ' if wrap is undefined set wrap to true',\n ' if activeClass is undefined set activeClass to \"active\"',\n ' end',\n ' -- Imperative installer handles ARIA wiring and keyboard navigation',\n 'end',\n ].join('\\n'),\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":"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","names":["tabs_exports","__export","tabs_default","registerTabs","tabsMetadata","tabsSource","tabsSchema","resolveRuntime","win","tabsSource","tabsSchema","tabsMetadata","idCounter","ensureId","el","prefix","installTabs","element","params","orientation","wrap","activeClass","initialIndex","tabs","panels","tablist","count","i","tabId","panelId","currentIndex","selectTab","index","previousIndex","detail","changeEvent","clamped","isHorizontal","nextKey","prevKey","nextIndex","registerTabs","hyperfixi","hf","resolveRuntime","syntheticNode","ctx","tabs_default"]}
1
+ {"version":3,"sources":["../src/behaviors/tabs.ts","../src/schemas/tabs.schema.ts","../src/schemas/types.ts"],"sourcesContent":["/**\n * Tabs Behavior — Imperative Implementation\n *\n * WAI-ARIA compliant tabs with roving tabindex keyboard navigation.\n * Auto-wires role, aria-selected, aria-controls, and aria-labelledby.\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\nlet idCounter = 0;\n\nfunction ensureId(el: HTMLElement, prefix: string): string {\n if (!el.id) {\n el.id = `${prefix}-${++idCounter}`;\n }\n return el.id;\n}\n\n/**\n * Imperative installer for Tabs behavior.\n */\nfunction installTabs(element: HTMLElement, params: Record<string, any>): void {\n const orientation = params.orientation === 'vertical' ? 'vertical' : 'horizontal';\n const wrap = params.wrap !== false;\n const activeClass = (params.activeClass as string) || 'active';\n const initialIndex = typeof params.activeTab === 'number' ? params.activeTab : 0;\n\n // Discover tabs and panels\n const tabs = Array.from(element.querySelectorAll<HTMLElement>('[role=\"tab\"]'));\n const panels = Array.from(element.querySelectorAll<HTMLElement>('[role=\"tabpanel\"]'));\n\n if (tabs.length === 0 || panels.length === 0) return;\n\n // Find or mark the tablist\n const tablist = element.querySelector<HTMLElement>('[role=\"tablist\"]') || tabs[0].parentElement;\n if (tablist && !tablist.getAttribute('role')) {\n tablist.setAttribute('role', 'tablist');\n }\n if (tablist) {\n tablist.setAttribute('aria-orientation', orientation);\n }\n\n // Wire IDs, aria-controls, aria-labelledby\n const count = Math.min(tabs.length, panels.length);\n for (let i = 0; i < count; i++) {\n const tabId = ensureId(tabs[i], 'tab');\n const panelId = ensureId(panels[i], 'tabpanel');\n tabs[i].setAttribute('aria-controls', panelId);\n panels[i].setAttribute('aria-labelledby', tabId);\n }\n\n let currentIndex = -1;\n\n function selectTab(index: number, previousIndex: number): boolean {\n if (index === previousIndex || index < 0 || index >= count) return false;\n\n const detail = {\n tab: tabs[index],\n panel: panels[index],\n index,\n previousIndex,\n };\n\n // Cancelable before-event\n const changeEvent = new CustomEvent('tabs:change', {\n bubbles: true,\n cancelable: true,\n detail,\n });\n element.dispatchEvent(changeEvent);\n if (changeEvent.defaultPrevented) return false;\n\n // Deactivate previous\n if (previousIndex >= 0 && previousIndex < count) {\n tabs[previousIndex].setAttribute('aria-selected', 'false');\n tabs[previousIndex].setAttribute('tabindex', '-1');\n tabs[previousIndex].classList.remove(activeClass);\n panels[previousIndex].setAttribute('aria-hidden', 'true');\n panels[previousIndex].classList.remove(activeClass);\n }\n\n // Activate new\n tabs[index].setAttribute('aria-selected', 'true');\n tabs[index].setAttribute('tabindex', '0');\n tabs[index].classList.add(activeClass);\n panels[index].setAttribute('aria-hidden', 'false');\n panels[index].classList.add(activeClass);\n\n currentIndex = index;\n\n element.dispatchEvent(new CustomEvent('tabs:changed', { bubbles: true, detail }));\n return true;\n }\n\n // Initialize all tabs as inactive, then activate initial\n for (let i = 0; i < count; i++) {\n tabs[i].setAttribute('aria-selected', 'false');\n tabs[i].setAttribute('tabindex', '-1');\n tabs[i].classList.remove(activeClass);\n panels[i].setAttribute('aria-hidden', 'true');\n panels[i].classList.remove(activeClass);\n }\n\n const clamped = Math.max(0, Math.min(initialIndex, count - 1));\n // Directly activate without firing change event on init\n tabs[clamped].setAttribute('aria-selected', 'true');\n tabs[clamped].setAttribute('tabindex', '0');\n tabs[clamped].classList.add(activeClass);\n panels[clamped].setAttribute('aria-hidden', 'false');\n panels[clamped].classList.add(activeClass);\n currentIndex = clamped;\n\n // Click handler\n for (let i = 0; i < count; i++) {\n tabs[i].addEventListener('click', () => {\n selectTab(i, currentIndex);\n tabs[i].focus();\n });\n }\n\n // Keyboard handler on tablist\n const keyTarget = tablist || element;\n keyTarget.addEventListener('keydown', (e: KeyboardEvent) => {\n const isHorizontal = orientation === 'horizontal';\n const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';\n const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';\n\n let nextIndex: number | null = null;\n\n if (e.key === nextKey) {\n nextIndex = currentIndex + 1;\n if (nextIndex >= count) nextIndex = wrap ? 0 : null;\n } else if (e.key === prevKey) {\n nextIndex = currentIndex - 1;\n if (nextIndex < 0) nextIndex = wrap ? count - 1 : null;\n } else if (e.key === 'Home') {\n nextIndex = 0;\n } else if (e.key === 'End') {\n nextIndex = count - 1;\n }\n\n if (nextIndex !== null) {\n e.preventDefault();\n if (selectTab(nextIndex, currentIndex)) {\n tabs[nextIndex].focus();\n }\n }\n });\n\n // Programmatic tab selection\n element.addEventListener('tabs:select', ((e: CustomEvent) => {\n const index = e.detail?.index;\n if (typeof index === 'number') {\n selectTab(index, currentIndex);\n }\n }) as EventListener);\n}\n\n/**\n * Register the Tabs behavior with LokaScript.\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 syntheticNode = {\n type: 'behavior',\n name: 'Tabs',\n parameters: ['orientation', 'activeTab', 'wrap', 'activeClass'],\n eventHandlers: [],\n imperativeInstaller: installTabs,\n };\n const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };\n await hf.execute(syntheticNode, 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,CF3GO,IAAMC,EAAaC,EAAW,OACxBC,EAAeD,EAExBE,EAAY,EAEhB,SAASC,EAASC,EAAiBC,EAAwB,CACzD,OAAKD,EAAG,KACNA,EAAG,GAAK,GAAGC,CAAM,IAAI,EAAEH,CAAS,IAE3BE,EAAG,EACZ,CAKA,SAASE,EAAYC,EAAsBC,EAAmC,CAC5E,IAAMC,EAAcD,EAAO,cAAgB,WAAa,WAAa,aAC/DE,EAAOF,EAAO,OAAS,GACvBG,EAAeH,EAAO,aAA0B,SAChDI,EAAe,OAAOJ,EAAO,WAAc,SAAWA,EAAO,UAAY,EAGzEK,EAAO,MAAM,KAAKN,EAAQ,iBAA8B,cAAc,CAAC,EACvEO,EAAS,MAAM,KAAKP,EAAQ,iBAA8B,mBAAmB,CAAC,EAEpF,GAAIM,EAAK,SAAW,GAAKC,EAAO,SAAW,EAAG,OAG9C,IAAMC,EAAUR,EAAQ,cAA2B,kBAAkB,GAAKM,EAAK,CAAC,EAAE,cAC9EE,GAAW,CAACA,EAAQ,aAAa,MAAM,GACzCA,EAAQ,aAAa,OAAQ,SAAS,EAEpCA,GACFA,EAAQ,aAAa,mBAAoBN,CAAW,EAItD,IAAMO,EAAQ,KAAK,IAAIH,EAAK,OAAQC,EAAO,MAAM,EACjD,QAASG,EAAI,EAAGA,EAAID,EAAOC,IAAK,CAC9B,IAAMC,EAAQf,EAASU,EAAKI,CAAC,EAAG,KAAK,EAC/BE,EAAUhB,EAASW,EAAOG,CAAC,EAAG,UAAU,EAC9CJ,EAAKI,CAAC,EAAE,aAAa,gBAAiBE,CAAO,EAC7CL,EAAOG,CAAC,EAAE,aAAa,kBAAmBC,CAAK,CACjD,CAEA,IAAIE,EAAe,GAEnB,SAASC,EAAUC,EAAeC,EAAgC,CAChE,GAAID,IAAUC,GAAiBD,EAAQ,GAAKA,GAASN,EAAO,MAAO,GAEnE,IAAMQ,EAAS,CACb,IAAKX,EAAKS,CAAK,EACf,MAAOR,EAAOQ,CAAK,EACnB,MAAAA,EACA,cAAAC,CACF,EAGME,EAAc,IAAI,YAAY,cAAe,CACjD,QAAS,GACT,WAAY,GACZ,OAAAD,CACF,CAAC,EAED,OADAjB,EAAQ,cAAckB,CAAW,EAC7BA,EAAY,iBAAyB,IAGrCF,GAAiB,GAAKA,EAAgBP,IACxCH,EAAKU,CAAa,EAAE,aAAa,gBAAiB,OAAO,EACzDV,EAAKU,CAAa,EAAE,aAAa,WAAY,IAAI,EACjDV,EAAKU,CAAa,EAAE,UAAU,OAAOZ,CAAW,EAChDG,EAAOS,CAAa,EAAE,aAAa,cAAe,MAAM,EACxDT,EAAOS,CAAa,EAAE,UAAU,OAAOZ,CAAW,GAIpDE,EAAKS,CAAK,EAAE,aAAa,gBAAiB,MAAM,EAChDT,EAAKS,CAAK,EAAE,aAAa,WAAY,GAAG,EACxCT,EAAKS,CAAK,EAAE,UAAU,IAAIX,CAAW,EACrCG,EAAOQ,CAAK,EAAE,aAAa,cAAe,OAAO,EACjDR,EAAOQ,CAAK,EAAE,UAAU,IAAIX,CAAW,EAEvCS,EAAeE,EAEff,EAAQ,cAAc,IAAI,YAAY,eAAgB,CAAE,QAAS,GAAM,OAAAiB,CAAO,CAAC,CAAC,EACzE,GACT,CAGA,QAASP,EAAI,EAAGA,EAAID,EAAOC,IACzBJ,EAAKI,CAAC,EAAE,aAAa,gBAAiB,OAAO,EAC7CJ,EAAKI,CAAC,EAAE,aAAa,WAAY,IAAI,EACrCJ,EAAKI,CAAC,EAAE,UAAU,OAAON,CAAW,EACpCG,EAAOG,CAAC,EAAE,aAAa,cAAe,MAAM,EAC5CH,EAAOG,CAAC,EAAE,UAAU,OAAON,CAAW,EAGxC,IAAMe,EAAU,KAAK,IAAI,EAAG,KAAK,IAAId,EAAcI,EAAQ,CAAC,CAAC,EAE7DH,EAAKa,CAAO,EAAE,aAAa,gBAAiB,MAAM,EAClDb,EAAKa,CAAO,EAAE,aAAa,WAAY,GAAG,EAC1Cb,EAAKa,CAAO,EAAE,UAAU,IAAIf,CAAW,EACvCG,EAAOY,CAAO,EAAE,aAAa,cAAe,OAAO,EACnDZ,EAAOY,CAAO,EAAE,UAAU,IAAIf,CAAW,EACzCS,EAAeM,EAGf,QAAST,EAAI,EAAGA,EAAID,EAAOC,IACzBJ,EAAKI,CAAC,EAAE,iBAAiB,QAAS,IAAM,CACtCI,EAAUJ,EAAGG,CAAY,EACzBP,EAAKI,CAAC,EAAE,MAAM,CAChB,CAAC,GAIeF,GAAWR,GACnB,iBAAiB,UAAY,GAAqB,CAC1D,IAAMoB,EAAelB,IAAgB,aAC/BmB,EAAUD,EAAe,aAAe,YACxCE,EAAUF,EAAe,YAAc,UAEzCG,EAA2B,KAE3B,EAAE,MAAQF,GACZE,EAAYV,EAAe,EACvBU,GAAad,IAAOc,EAAYpB,EAAO,EAAI,OACtC,EAAE,MAAQmB,GACnBC,EAAYV,EAAe,EACvBU,EAAY,IAAGA,EAAYpB,EAAOM,EAAQ,EAAI,OACzC,EAAE,MAAQ,OACnBc,EAAY,EACH,EAAE,MAAQ,QACnBA,EAAYd,EAAQ,GAGlBc,IAAc,OAChB,EAAE,eAAe,EACbT,EAAUS,EAAWV,CAAY,GACnCP,EAAKiB,CAAS,EAAE,MAAM,EAG5B,CAAC,EAGDvB,EAAQ,iBAAiB,eAAiB,GAAmB,CAC3D,IAAMe,EAAQ,EAAE,QAAQ,MACpB,OAAOA,GAAU,UACnBD,EAAUC,EAAOF,CAAY,CAEjC,EAAmB,CACrB,CAKA,eAAsBW,EAAaC,EAA+C,CAChF,IAAMC,EAAKD,GAAaE,EAAe,EAEvC,GAAI,CAACD,EACH,MAAM,IAAI,MACR,yFACF,EAGF,IAAME,EAAgB,CACpB,KAAM,WACN,KAAM,OACN,WAAY,CAAC,cAAe,YAAa,OAAQ,aAAa,EAC9D,cAAe,CAAC,EAChB,oBAAqB7B,CACvB,EACM8B,EAAMH,EAAG,cAAgBA,EAAG,cAAc,EAAI,CAAE,OAAQ,IAAI,IAAO,QAAS,IAAI,GAAM,EAC5F,MAAMA,EAAG,QAAQE,EAAeC,CAAG,CACrC,CAGIF,EAAe,GACjBH,EAAa,EAAE,MAAM,QAAQ,KAAK,EAGpC,IAAOM,EAAQ,CACb,OAAQrC,EAAW,OACnB,SAAUA,EACV,SAAU+B,CACZ","names":["tabs_exports","__export","tabs_default","registerTabs","tabsMetadata","tabsSource","tabsSchema","resolveRuntime","win","tabsSource","tabsSchema","tabsMetadata","idCounter","ensureId","el","prefix","installTabs","element","params","orientation","wrap","activeClass","initialIndex","tabs","panels","tablist","count","i","tabId","panelId","currentIndex","selectTab","index","previousIndex","detail","changeEvent","clamped","isHorizontal","nextKey","prevKey","nextIndex","registerTabs","hyperfixi","hf","resolveRuntime","syntheticNode","ctx","tabs_default"]}
@@ -1,4 +1,4 @@
1
- "use strict";var HyperFixiBehaviors=(()=>{var a=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var m=(t,e)=>{for(var r in e)a(t,r,{get:e[r],enumerable:!0})},d=(t,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of g(e))!p.call(t,n)&&n!==r&&a(t,n,{get:()=>e[n],enumerable:!(o=l(e,n))||o.enumerable});return t};var u=t=>d(a({},"__esModule",{value:!0}),t);var y={};m(y,{default:()=>b,registerToggleable:()=>c,toggleableMetadata:()=>h,toggleableSource:()=>f});var i={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:"target",type:"selector",optional:!0,default:"me",description:"Element to toggle the class on"}],events:[{name:"toggleable:on",description:"Fired when class is added"},{name:"toggleable:off",description:"Fired when class is removed"}],source:`
1
+ "use strict";var HyperFixiBehaviors=(()=>{var i=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var p=(t,e)=>{for(var r in e)i(t,r,{get:e[r],enumerable:!0})},d=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of g(e))!m.call(t,o)&&o!==r&&i(t,o,{get:()=>e[o],enumerable:!(n=l(e,o))||n.enumerable});return t};var u=t=>d(i({},"__esModule",{value:!0}),t);var y={};p(y,{default:()=>b,registerToggleable:()=>c,toggleableMetadata:()=>v,toggleableSource:()=>f});var a={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:"target",type:"selector",optional:!0,default:"me",description:"Element to toggle the class on"}],events:[{name:"toggleable:on",description:"Fired when class is added"},{name:"toggleable:off",description:"Fired when class is removed"}],source:`
2
2
  behavior Toggleable(cls, target)
3
3
  init
4
4
  if cls is undefined
@@ -10,7 +10,10 @@ behavior Toggleable(cls, target)
10
10
  end
11
11
  on click
12
12
  toggle .{cls} on target
13
+ js(target, cls)
14
+ var eventName = target.classList.contains(cls) ? 'toggleable:on' : 'toggleable:off';
15
+ target.dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
16
+ end
13
17
  end
14
- end
15
- `.trim()};function s(){if(typeof window>"u")return null;let t=window;return t.lokascript||t.hyperfixi||null}var f=i.source,h=i;function v(t,e){let r=e.cls||"active",o=t;if(e.target&&e.target!=="me")if(typeof e.target=="string"){let n=document.querySelector(e.target);n instanceof HTMLElement&&(o=n)}else e.target instanceof HTMLElement&&(o=e.target);t.addEventListener("click",()=>{let n=o.classList.contains(r);o.classList.toggle(r),o.dispatchEvent(new CustomEvent(n?"toggleable:off":"toggleable:on",{bubbles:!0}))})}async function c(t){let e=t||s();if(!e)throw new Error("LokaScript not found. Make sure @hyperfixi/core is loaded before registering behaviors.");let r={type:"behavior",name:"Toggleable",parameters:["cls","target"],eventHandlers:[],imperativeInstaller:v},o=e.createContext?e.createContext():{locals:new Map,globals:new Map};await e.execute(r,o)}s()&&c().catch(console.error);var b={source:i.source,metadata:i,register:c};return u(y);})();
18
+ end`.trim()};function s(){if(typeof window>"u")return null;let t=window;return t.lokascript||t.hyperfixi||null}var f=a.source,v=a;function h(t,e){let r=e.cls||"active",n=t;if(e.target&&e.target!=="me")if(typeof e.target=="string"){let o=document.querySelector(e.target);o instanceof HTMLElement&&(n=o)}else e.target instanceof HTMLElement&&(n=e.target);t.addEventListener("click",()=>{let o=n.classList.contains(r);n.classList.toggle(r),n.dispatchEvent(new CustomEvent(o?"toggleable:off":"toggleable:on",{bubbles:!0}))})}async function c(t){let e=t||s();if(!e)throw new Error("LokaScript not found. Make sure @hyperfixi/core is loaded before registering behaviors.");let r={type:"behavior",name:"Toggleable",parameters:["cls","target"],eventHandlers:[],imperativeInstaller:h},n=e.createContext?e.createContext():{locals:new Map,globals:new Map};await e.execute(r,n)}s()&&c().catch(console.error);var b={source:a.source,metadata:a,register:c};return u(y);})();
16
19
  //# 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 — Imperative Implementation\n *\n * A behavior that toggles a CSS class on click.\n * Useful for accordions, dropdowns, and toggle buttons.\n *\n * Uses direct DOM APIs instead of compiling hyperscript source,\n * because `toggle .{cls}` template interpolation doesn't resolve\n * behavior parameters at runtime.\n *\n * @example\n * ```html\n * <button _=\"install Toggleable\">Toggle</button>\n *\n * <button _=\"install Toggleable(cls: expanded, target: #menu)\">Menu</button>\n * ```\n */\n\nimport { toggleableSchema } from '../schemas/toggleable.schema';\nimport type { LokaScriptInstance } from '../schemas/types';\nimport { resolveRuntime } 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 * Imperative installer for Toggleable behavior.\n * Called directly by the runtime when `install Toggleable` is executed.\n */\nfunction installToggleable(element: HTMLElement, params: Record<string, any>): void {\n const cls = params.cls || 'active';\n\n // Resolve target element\n let targetEl: HTMLElement = element;\n if (params.target && params.target !== 'me') {\n if (typeof params.target === 'string') {\n const found = document.querySelector(params.target);\n if (found instanceof HTMLElement) targetEl = found;\n } else if (params.target instanceof HTMLElement) {\n targetEl = params.target;\n }\n }\n\n element.addEventListener('click', () => {\n const wasOn = targetEl.classList.contains(cls);\n targetEl.classList.toggle(cls);\n targetEl.dispatchEvent(\n new CustomEvent(wasOn ? 'toggleable:off' : 'toggleable:on', { bubbles: true })\n );\n });\n}\n\n/**\n * Register the Toggleable behavior with LokaScript.\n * Uses imperative installer via synthetic behavior node.\n */\nexport async function registerToggleable(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 syntheticNode = {\n type: 'behavior',\n name: 'Toggleable',\n parameters: ['cls', 'target'],\n eventHandlers: [],\n imperativeInstaller: installToggleable,\n };\n const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };\n await hf.execute(syntheticNode, ctx);\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: 'target',\n type: 'selector',\n optional: true,\n default: 'me',\n description: 'Element to toggle the class on',\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, target)\n init\n if cls is undefined\n set cls to \"active\"\n end\n if target is undefined\n set target to me\n end\n end\n on click\n toggle .{cls} on target\n end\nend\n`.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,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,SACN,KAAM,WACN,SAAU,GACV,QAAS,KACT,YAAa,gCACf,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;AAAA,EAcR,KAAK,CACP,ECiEO,SAASC,GAA4C,CAC1D,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAMC,EAAM,OACZ,OAAOA,EAAI,YAAcA,EAAI,WAAa,IAC5C,CF/FO,IAAMC,EAAmBC,EAAiB,OACpCC,EAAqBD,EAMlC,SAASE,EAAkBC,EAAsBC,EAAmC,CAClF,IAAMC,EAAMD,EAAO,KAAO,SAGtBE,EAAwBH,EAC5B,GAAIC,EAAO,QAAUA,EAAO,SAAW,KACrC,GAAI,OAAOA,EAAO,QAAW,SAAU,CACrC,IAAMG,EAAQ,SAAS,cAAcH,EAAO,MAAM,EAC9CG,aAAiB,cAAaD,EAAWC,EAC/C,MAAWH,EAAO,kBAAkB,cAClCE,EAAWF,EAAO,QAItBD,EAAQ,iBAAiB,QAAS,IAAM,CACtC,IAAMK,EAAQF,EAAS,UAAU,SAASD,CAAG,EAC7CC,EAAS,UAAU,OAAOD,CAAG,EAC7BC,EAAS,cACP,IAAI,YAAYE,EAAQ,iBAAmB,gBAAiB,CAAE,QAAS,EAAK,CAAC,CAC/E,CACF,CAAC,CACH,CAMA,eAAsBC,EAAmBC,EAA+C,CACtF,IAAMC,EAAKD,GAAaE,EAAe,EAEvC,GAAI,CAACD,EACH,MAAM,IAAI,MACR,yFACF,EAGF,IAAME,EAAgB,CACpB,KAAM,WACN,KAAM,aACN,WAAY,CAAC,MAAO,QAAQ,EAC5B,cAAe,CAAC,EAChB,oBAAqBX,CACvB,EACMY,EAAMH,EAAG,cAAgBA,EAAG,cAAc,EAAI,CAAE,OAAQ,IAAI,IAAO,QAAS,IAAI,GAAM,EAC5F,MAAMA,EAAG,QAAQE,EAAeC,CAAG,CACrC,CAGIF,EAAe,GACjBH,EAAmB,EAAE,MAAM,QAAQ,KAAK,EAG1C,IAAOM,EAAQ,CACb,OAAQf,EAAiB,OACzB,SAAUA,EACV,SAAUS,CACZ","names":["toggleable_exports","__export","toggleable_default","registerToggleable","toggleableMetadata","toggleableSource","toggleableSchema","resolveRuntime","win","toggleableSource","toggleableSchema","toggleableMetadata","installToggleable","element","params","cls","targetEl","found","wasOn","registerToggleable","hyperfixi","hf","resolveRuntime","syntheticNode","ctx","toggleable_default"]}
1
+ {"version":3,"sources":["../src/behaviors/toggleable.ts","../src/schemas/toggleable.schema.ts","../src/schemas/types.ts"],"sourcesContent":["/**\n * Toggleable Behavior — Imperative Implementation\n *\n * A behavior that toggles a CSS class on click.\n * Useful for accordions, dropdowns, and toggle buttons.\n *\n * Uses direct DOM APIs instead of compiling hyperscript source,\n * because `toggle .{cls}` template interpolation doesn't resolve\n * behavior parameters at runtime.\n *\n * @example\n * ```html\n * <button _=\"install Toggleable\">Toggle</button>\n *\n * <button _=\"install Toggleable(cls: expanded, target: #menu)\">Menu</button>\n * ```\n */\n\nimport { toggleableSchema } from '../schemas/toggleable.schema';\nimport type { LokaScriptInstance } from '../schemas/types';\nimport { resolveRuntime } 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 * Imperative installer for Toggleable behavior.\n * Called directly by the runtime when `install Toggleable` is executed.\n */\nfunction installToggleable(element: HTMLElement, params: Record<string, any>): void {\n const cls = params.cls || 'active';\n\n // Resolve target element\n let targetEl: HTMLElement = element;\n if (params.target && params.target !== 'me') {\n if (typeof params.target === 'string') {\n const found = document.querySelector(params.target);\n if (found instanceof HTMLElement) targetEl = found;\n } else if (params.target instanceof HTMLElement) {\n targetEl = params.target;\n }\n }\n\n element.addEventListener('click', () => {\n const wasOn = targetEl.classList.contains(cls);\n targetEl.classList.toggle(cls);\n targetEl.dispatchEvent(\n new CustomEvent(wasOn ? 'toggleable:off' : 'toggleable:on', { bubbles: true })\n );\n });\n}\n\n/**\n * Register the Toggleable behavior with LokaScript.\n * Uses imperative installer via synthetic behavior node.\n */\nexport async function registerToggleable(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 syntheticNode = {\n type: 'behavior',\n name: 'Toggleable',\n parameters: ['cls', 'target'],\n eventHandlers: [],\n imperativeInstaller: installToggleable,\n };\n const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };\n await hf.execute(syntheticNode, ctx);\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: 'target',\n type: 'selector',\n optional: true,\n default: 'me',\n description: 'Element to toggle the class on',\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, target)\n init\n if cls is undefined\n set cls to \"active\"\n end\n if target is undefined\n set target to me\n end\n end\n on click\n toggle .{cls} on target\n js(target, cls)\n var eventName = target.classList.contains(cls) ? 'toggleable:on' : 'toggleable:off';\n target.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.\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,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,SACN,KAAM,WACN,SAAU,GACV,QAAS,KACT,YAAa,gCACf,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;AAAA;AAAA;AAAA;AAAA,KAiBL,KAAK,CACV,EC8DO,SAASC,GAA4C,CAC1D,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAMC,EAAM,OACZ,OAAOA,EAAI,YAAcA,EAAI,WAAa,IAC5C,CF/FO,IAAMC,EAAmBC,EAAiB,OACpCC,EAAqBD,EAMlC,SAASE,EAAkBC,EAAsBC,EAAmC,CAClF,IAAMC,EAAMD,EAAO,KAAO,SAGtBE,EAAwBH,EAC5B,GAAIC,EAAO,QAAUA,EAAO,SAAW,KACrC,GAAI,OAAOA,EAAO,QAAW,SAAU,CACrC,IAAMG,EAAQ,SAAS,cAAcH,EAAO,MAAM,EAC9CG,aAAiB,cAAaD,EAAWC,EAC/C,MAAWH,EAAO,kBAAkB,cAClCE,EAAWF,EAAO,QAItBD,EAAQ,iBAAiB,QAAS,IAAM,CACtC,IAAMK,EAAQF,EAAS,UAAU,SAASD,CAAG,EAC7CC,EAAS,UAAU,OAAOD,CAAG,EAC7BC,EAAS,cACP,IAAI,YAAYE,EAAQ,iBAAmB,gBAAiB,CAAE,QAAS,EAAK,CAAC,CAC/E,CACF,CAAC,CACH,CAMA,eAAsBC,EAAmBC,EAA+C,CACtF,IAAMC,EAAKD,GAAaE,EAAe,EAEvC,GAAI,CAACD,EACH,MAAM,IAAI,MACR,yFACF,EAGF,IAAME,EAAgB,CACpB,KAAM,WACN,KAAM,aACN,WAAY,CAAC,MAAO,QAAQ,EAC5B,cAAe,CAAC,EAChB,oBAAqBX,CACvB,EACMY,EAAMH,EAAG,cAAgBA,EAAG,cAAc,EAAI,CAAE,OAAQ,IAAI,IAAO,QAAS,IAAI,GAAM,EAC5F,MAAMA,EAAG,QAAQE,EAAeC,CAAG,CACrC,CAGIF,EAAe,GACjBH,EAAmB,EAAE,MAAM,QAAQ,KAAK,EAG1C,IAAOM,EAAQ,CACb,OAAQf,EAAiB,OACzB,SAAUA,EACV,SAAUS,CACZ","names":["toggleable_exports","__export","toggleable_default","registerToggleable","toggleableMetadata","toggleableSource","toggleableSchema","resolveRuntime","win","toggleableSource","toggleableSchema","toggleableMetadata","installToggleable","element","params","cls","targetEl","found","wasOn","registerToggleable","hyperfixi","hf","resolveRuntime","syntheticNode","ctx","toggleable_default"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperfixi/behaviors",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Reusable hyperscript behaviors for LokaScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Behavior Resolver — Lazy compilation of hyperscript behaviors
3
+ *
4
+ * Provides a resolver function that compiles behavior source strings on demand.
5
+ * When the runtime encounters `install X` and X isn't registered, it calls
6
+ * the resolver. If the resolver has source for X, it compiles it, registers
7
+ * the behavior, and installation proceeds normally.
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
+ * ```
18
+ */
19
+
20
+ import { toggleableSchema } from './schemas/toggleable.schema';
21
+ import { removableSchema } from './schemas/removable.schema';
22
+ import { autoDismissSchema } from './schemas/autodismiss.schema';
23
+ import { clipboardSchema } from './schemas/clipboard.schema';
24
+ import { draggableSchema } from './schemas/draggable.schema';
25
+ import { clickOutsideSchema } from './schemas/clickoutside.schema';
26
+ import { scrollRevealSchema } from './schemas/scrollreveal.schema';
27
+ import { tabsSchema } from './schemas/tabs.schema';
28
+ import { focusTrapSchema } from './schemas/focustrap.schema';
29
+ import { sortableSchema } from './schemas/sortable.schema';
30
+ import { resizableSchema } from './schemas/resizable.schema';
31
+
32
+ export const BEHAVIOR_SOURCES: Record<string, string> = {
33
+ Toggleable: toggleableSchema.source,
34
+ Removable: removableSchema.source,
35
+ AutoDismiss: autoDismissSchema.source,
36
+ Clipboard: clipboardSchema.source,
37
+ Draggable: draggableSchema.source,
38
+ ClickOutside: clickOutsideSchema.source,
39
+ ScrollReveal: scrollRevealSchema.source,
40
+ Tabs: tabsSchema.source,
41
+ FocusTrap: focusTrapSchema.source,
42
+ Sortable: sortableSchema.source,
43
+ Resizable: resizableSchema.source,
44
+ };
45
+
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
+ /**
61
+ * Create a behavior resolver that compiles hyperscript source on demand.
62
+ *
63
+ * The returned function is meant to be assigned to `_hyperscript.behaviors.resolve`.
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
70
+ */
71
+ export function createBehaviorResolver(
72
+ hyperfixi: HyperFixiAPI,
73
+ behaviorAPI: BehaviorAPI
74
+ ): (name: string) => boolean {
75
+ return (name: string): boolean => {
76
+ const source = BEHAVIOR_SOURCES[name];
77
+ if (!source) return false;
78
+
79
+ const result = hyperfixi.compileSync(source, { traditional: true });
80
+ if (!result.ok || !result.ast) return false;
81
+
82
+ const ast = result.ast as any;
83
+ behaviorAPI.set(name, {
84
+ name: ast.name,
85
+ parameters: ast.parameters,
86
+ eventHandlers: ast.eventHandlers,
87
+ initBlock: ast.initBlock,
88
+ });
89
+ return true;
90
+ };
91
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Browser Behavior Resolver Bundle
3
+ *
4
+ * Pre-registers all standard behavior definitions with the hyperfixi runtime.
5
+ * Include after hyperfixi.js — all standard behaviors become available.
6
+ *
7
+ * Usage:
8
+ * <script src="hyperfixi.js"></script>
9
+ * <script src="hyperfixi-behaviors.js"></script>
10
+ * <!-- install Toggleable, Draggable, etc. just work -->
11
+ */
12
+
13
+ import { BEHAVIOR_SOURCES } from './behavior-resolver';
14
+
15
+ function init() {
16
+ const hf = (window as any).hyperfixi;
17
+ if (!hf?.compileSync || !hf?.execute) return;
18
+
19
+ // Compile and register each behavior definition with the runtime.
20
+ // This is equivalent to having them in a <script type="text/hyperscript"> block.
21
+ for (const [name, source] of Object.entries(BEHAVIOR_SOURCES)) {
22
+ const result = hf.compileSync(source, { traditional: true });
23
+ if (result?.ok && result.ast) {
24
+ // execute() registers the behavior in the runtime's behaviorRegistry
25
+ hf.execute(result.ast).catch(() => {
26
+ // Async — errors handled silently
27
+ });
28
+ }
29
+ }
30
+
31
+ // Re-scan: the attribute processor may have already processed elements
32
+ // and failed (behaviors weren't registered). Reset and re-init.
33
+ const ap = hf.attributeProcessor;
34
+ if (ap) {
35
+ ap.reset();
36
+ ap.init();
37
+ }
38
+ }
39
+
40
+ if (typeof window !== 'undefined') {
41
+ init();
42
+ }
@@ -45,22 +45,58 @@ export const autoDismissSchema: BehaviorSchema = {
45
45
  { name: 'autodismiss:paused', description: 'Fired when the timer is paused on hover' },
46
46
  { name: 'autodismiss:resumed', description: 'Fired when the timer resumes on mouseleave' },
47
47
  ],
48
- source: [
49
- 'behavior AutoDismiss(delay, pauseOnHover, effect)',
50
- ' init',
51
- ' if delay is undefined set delay to 5000',
52
- ' if pauseOnHover is undefined set pauseOnHover to true',
53
- ' if effect is undefined set effect to "none"',
54
- ' end',
55
- ' on load',
56
- ' trigger autodismiss:start',
57
- ' wait delay ms',
58
- ' trigger autodismiss:dismissed',
59
- ' if effect is "fade"',
60
- ' transition opacity to 0 over 300ms',
61
- ' end',
62
- ' remove me',
63
- ' end',
64
- 'end',
65
- ].join('\n'),
48
+ source: `
49
+ behavior AutoDismiss(delay, pauseOnHover, effect)
50
+ init
51
+ if delay is undefined
52
+ set delay to 5000
53
+ end
54
+ if pauseOnHover is undefined
55
+ set pauseOnHover to true
56
+ end
57
+ if effect is undefined
58
+ set effect to "none"
59
+ end
60
+ js(me, delay, effect, pauseOnHover)
61
+ me.dispatchEvent(new CustomEvent('autodismiss:start', { bubbles: true }));
62
+ var remaining = delay;
63
+ var startedAt = Date.now();
64
+ var timerId = null;
65
+ function dismiss() {
66
+ var ev = new CustomEvent('autodismiss:dismissed', { bubbles: true, cancelable: true });
67
+ me.dispatchEvent(ev);
68
+ if (ev.defaultPrevented) return;
69
+ if (effect === 'fade') {
70
+ me.style.transition = 'opacity 300ms';
71
+ me.style.opacity = '0';
72
+ setTimeout(function() { me.remove(); }, 300);
73
+ } else {
74
+ me.remove();
75
+ }
76
+ }
77
+ function start() {
78
+ startedAt = Date.now();
79
+ timerId = setTimeout(dismiss, remaining);
80
+ }
81
+ start();
82
+ if (pauseOnHover) {
83
+ me.addEventListener('mouseenter', function() {
84
+ if (timerId != null) {
85
+ clearTimeout(timerId);
86
+ timerId = null;
87
+ remaining -= Date.now() - startedAt;
88
+ if (remaining < 0) remaining = 0;
89
+ me.dispatchEvent(new CustomEvent('autodismiss:paused', { bubbles: true }));
90
+ }
91
+ });
92
+ me.addEventListener('mouseleave', function() {
93
+ if (timerId == null && me.isConnected) {
94
+ me.dispatchEvent(new CustomEvent('autodismiss:resumed', { bubbles: true }));
95
+ start();
96
+ }
97
+ });
98
+ }
99
+ end
100
+ end
101
+ end`.trim(),
66
102
  };
@@ -23,17 +23,36 @@ export const clickOutsideSchema: BehaviorSchema = {
23
23
  ],
24
24
  events: [
25
25
  { name: 'clickoutside', description: 'Fired when a pointer press occurs outside the element' },
26
+ {
27
+ name: 'clickoutside:activate',
28
+ description: 'Listen for this event to programmatically activate detection',
29
+ },
30
+ {
31
+ name: 'clickoutside:deactivate',
32
+ description: 'Listen for this event to programmatically deactivate detection',
33
+ },
26
34
  ],
27
35
  requirements: ['Uses pointerdown instead of click to avoid timing issues with removed elements'],
28
36
  source: `
29
37
  behavior ClickOutside(active)
30
38
  init
31
- if active is undefined set active to true
39
+ if active is undefined
40
+ set active to true
41
+ end
32
42
  end
33
43
  on pointerdown from document
34
- if active and not me.contains(event.target)
35
- trigger clickoutside on me
44
+ js(me, active)
45
+ if (!me.isConnected) return;
46
+ if (active && !me.contains(event.target)) {
47
+ me.dispatchEvent(new CustomEvent('clickoutside', { bubbles: true }));
48
+ }
36
49
  end
37
50
  end
51
+ on clickoutside:activate
52
+ set active to true
53
+ end
54
+ on clickoutside:deactivate
55
+ set active to false
56
+ end
38
57
  end`.trim(),
39
58
  };
@@ -48,25 +48,47 @@ export const clipboardSchema: BehaviorSchema = {
48
48
  'navigator.clipboard.writeText() requires HTTPS or localhost',
49
49
  'Falls back to document.execCommand("copy") for older browsers',
50
50
  ],
51
- source: [
52
- 'behavior Clipboard(text, source, feedback, feedbackDuration)',
53
- ' init',
54
- ' if feedbackDuration is undefined set feedbackDuration to 2000',
55
- ' if source is undefined set source to me',
56
- ' end',
57
- ' on click',
58
- ' if text is not undefined',
59
- ' set copyText to text',
60
- ' else',
61
- " set copyText to source's value or source's textContent",
62
- ' end',
63
- ' js(copyText) navigator.clipboard.writeText(copyText) end',
64
- ' set feedbackEl to feedback or me',
65
- ' add .copied to feedbackEl',
66
- ' wait feedbackDuration ms',
67
- ' remove .copied from feedbackEl',
68
- ' trigger clipboard:copied',
69
- ' end',
70
- 'end',
71
- ].join('\n'),
51
+ source: `
52
+ behavior Clipboard(text, source, feedback, feedbackDuration)
53
+ init
54
+ if feedbackDuration is undefined
55
+ set feedbackDuration to 2000
56
+ end
57
+ if source is undefined
58
+ set source to me
59
+ end
60
+ end
61
+ on click
62
+ if text is not undefined
63
+ set copyText to text
64
+ else
65
+ set copyText to source's textContent
66
+ end
67
+ js(me, copyText, feedback, feedbackDuration)
68
+ async function copyToClipboard(t) {
69
+ if (navigator.clipboard && navigator.clipboard.writeText) {
70
+ await navigator.clipboard.writeText(t);
71
+ return;
72
+ }
73
+ var ta = document.createElement('textarea');
74
+ ta.value = t;
75
+ ta.style.position = 'fixed';
76
+ ta.style.opacity = '0';
77
+ document.body.appendChild(ta);
78
+ ta.select();
79
+ try { document.execCommand('copy'); } finally { document.body.removeChild(ta); }
80
+ }
81
+ try {
82
+ await copyToClipboard(copyText);
83
+ var feedbackEl = feedback ? (typeof feedback === 'string' ? document.querySelector(feedback) : feedback) : me;
84
+ if (!feedbackEl) feedbackEl = me;
85
+ feedbackEl.classList.add('copied');
86
+ me.dispatchEvent(new CustomEvent('clipboard:copied', { bubbles: true, detail: { text: copyText } }));
87
+ setTimeout(function() { feedbackEl.classList.remove('copied'); }, feedbackDuration);
88
+ } catch(err) {
89
+ me.dispatchEvent(new CustomEvent('clipboard:error', { bubbles: true, detail: { error: err } }));
90
+ }
91
+ end
92
+ end
93
+ end`.trim(),
72
94
  };
@@ -45,9 +45,50 @@ export const focusTrapSchema: BehaviorSchema = {
45
45
  source: `
46
46
  behavior FocusTrap(active, initialFocus, returnFocus)
47
47
  init
48
- if active is undefined set active to true
49
- if returnFocus is undefined set returnFocus to true
48
+ if active is undefined
49
+ set active to true
50
+ end
51
+ if returnFocus is undefined
52
+ set returnFocus to true
53
+ end
54
+ js(me, active, initialFocus, returnFocus)
55
+ var FOCUSABLE = 'a[href],button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex="-1"])';
56
+ var isActive = false;
57
+ var previouslyFocused = null;
58
+ function getFocusable() { return Array.from(me.querySelectorAll(FOCUSABLE)); }
59
+ function activate() {
60
+ if (isActive) return;
61
+ isActive = true;
62
+ previouslyFocused = document.activeElement;
63
+ me.setAttribute('aria-modal', 'true');
64
+ var initEl = initialFocus ? (typeof initialFocus === 'string' ? me.querySelector(initialFocus) : initialFocus) : null;
65
+ if (initEl) { initEl.focus(); } else { var f = getFocusable(); if (f.length) f[0].focus(); }
66
+ me.dispatchEvent(new CustomEvent('focustrap:activated', { bubbles: true }));
67
+ }
68
+ function deactivate() {
69
+ if (!isActive) return;
70
+ isActive = false;
71
+ me.removeAttribute('aria-modal');
72
+ if (returnFocus && previouslyFocused instanceof HTMLElement) previouslyFocused.focus();
73
+ previouslyFocused = null;
74
+ me.dispatchEvent(new CustomEvent('focustrap:deactivated', { bubbles: true }));
75
+ }
76
+ me.addEventListener('keydown', function(e) {
77
+ if (!isActive || e.key !== 'Tab') return;
78
+ var focusable = getFocusable();
79
+ if (focusable.length === 0) { e.preventDefault(); return; }
80
+ e.preventDefault();
81
+ var idx = focusable.indexOf(document.activeElement);
82
+ if (e.shiftKey) {
83
+ focusable[idx <= 0 ? focusable.length - 1 : idx - 1].focus();
84
+ } else {
85
+ focusable[idx >= focusable.length - 1 ? 0 : idx + 1].focus();
86
+ }
87
+ });
88
+ me.addEventListener('focustrap:activate', function() { activate(); });
89
+ me.addEventListener('focustrap:deactivate', function() { deactivate(); });
90
+ if (active) activate();
91
+ end
50
92
  end
51
- -- Imperative installer handles Tab key trapping and focus management
52
93
  end`.trim(),
53
94
  };
@@ -25,7 +25,7 @@ export const removableSchema: BehaviorSchema = {
25
25
  description: 'Element that triggers removal (e.g., a close button)',
26
26
  },
27
27
  {
28
- name: 'confirm',
28
+ name: 'confirmRemoval',
29
29
  type: 'boolean',
30
30
  optional: true,
31
31
  default: false,
@@ -45,15 +45,18 @@ export const removableSchema: BehaviorSchema = {
45
45
  { name: 'removable:removed', description: 'Fired after removal' },
46
46
  ],
47
47
  source: `
48
- behavior Removable(triggerEl, confirm, effect)
48
+ behavior Removable(triggerEl, confirmRemoval, effect)
49
49
  init
50
50
  if triggerEl is undefined
51
51
  set triggerEl to me
52
52
  end
53
53
  end
54
54
  on click from triggerEl
55
- if confirm
56
- if not window.confirm("Are you sure?")
55
+ if confirmRemoval
56
+ js(me)
57
+ if (!window.confirm("Are you sure?")) return "cancel";
58
+ end
59
+ if it is "cancel"
57
60
  halt
58
61
  end
59
62
  end