@hyperfixi/behaviors 2.2.0 → 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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schemas/toggleable.schema.ts","../src/schemas/removable.schema.ts","../src/schemas/autodismiss.schema.ts","../src/schemas/clipboard.schema.ts","../src/schemas/draggable.schema.ts","../src/schemas/clickoutside.schema.ts","../src/schemas/scrollreveal.schema.ts","../src/schemas/tabs.schema.ts","../src/schemas/focustrap.schema.ts","../src/schemas/sortable.schema.ts","../src/schemas/resizable.schema.ts","../src/behavior-resolver.ts","../src/browser-resolver-bundle.ts"],"sourcesContent":["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","import type { BehaviorSchema } from './types';\n\n/**\n * Removable Behavior Schema\n *\n * Removes an element when a trigger is clicked.\n * Supports optional confirmation and transition effects.\n *\n * @since 1.2.0 — `trigger` parameter renamed to `triggerEl` to avoid\n * shadowing the hyperscript `trigger` command keyword. Users migrating\n * from v1.x should update: `install Removable(triggerEl: .close-btn)`.\n */\nexport const removableSchema: BehaviorSchema = {\n name: 'Removable',\n category: 'data',\n tier: 'common',\n version: '1.2.0',\n description: 'Makes elements removable on click',\n parameters: [\n {\n name: 'triggerEl',\n type: 'selector',\n optional: true,\n default: 'me',\n description: 'Element that triggers removal (e.g., a close button)',\n },\n {\n name: 'confirmRemoval',\n type: 'boolean',\n optional: true,\n default: false,\n description: 'Show confirmation dialog before removal',\n },\n {\n name: 'effect',\n type: 'string',\n optional: true,\n default: 'none',\n enum: ['fade', 'none'],\n description: 'Transition effect: \"fade\" or \"none\"',\n },\n ],\n events: [\n { name: 'removable:before', description: 'Fired before removal (cancelable)' },\n { name: 'removable:removed', description: 'Fired after removal' },\n ],\n source: `\nbehavior Removable(triggerEl, confirmRemoval, effect)\n init\n if triggerEl is undefined\n set triggerEl to me\n end\n end\n on click from triggerEl\n if confirmRemoval\n js(me)\n if (!window.confirm(\"Are you sure?\")) return \"cancel\";\n end\n if it is \"cancel\"\n halt\n end\n end\n trigger removable:before\n if effect is \"fade\"\n transition opacity to 0 over 300ms\n end\n trigger removable:removed\n remove me\n end\nend\n`.trim(),\n};\n","import type { BehaviorSchema } from './types';\n\n/**\n * AutoDismiss Behavior Schema\n *\n * Auto-removes flash messages and toasts after a configurable delay.\n * Supports pause-on-hover and fade effect.\n */\nexport const autoDismissSchema: BehaviorSchema = {\n name: 'AutoDismiss',\n category: 'ui',\n tier: 'core',\n version: '1.0.0',\n description: 'Auto-removes elements after a delay with optional pause-on-hover',\n parameters: [\n {\n name: 'delay',\n type: 'number',\n optional: true,\n default: 5000,\n description: 'Time in ms before the element is removed',\n },\n {\n name: 'pauseOnHover',\n type: 'boolean',\n optional: true,\n default: true,\n description: 'Pause the countdown when the mouse hovers over the element',\n },\n {\n name: 'effect',\n type: 'string',\n optional: true,\n default: 'none',\n enum: ['fade', 'none'],\n description: 'Transition effect before removal: \"fade\" or \"none\"',\n },\n ],\n events: [\n { name: 'autodismiss:start', description: 'Fired when the countdown timer starts' },\n {\n name: 'autodismiss:dismissed',\n description: 'Fired before removal (cancelable via preventDefault)',\n },\n { name: 'autodismiss:paused', description: 'Fired when the timer is paused on hover' },\n { name: 'autodismiss:resumed', description: 'Fired when the timer resumes on mouseleave' },\n ],\n source: `\nbehavior AutoDismiss(delay, pauseOnHover, effect)\n init\n if delay is undefined\n set delay to 5000\n end\n if pauseOnHover is undefined\n set pauseOnHover to true\n end\n if effect is undefined\n set effect to \"none\"\n end\n js(me, delay, effect, pauseOnHover)\n me.dispatchEvent(new CustomEvent('autodismiss:start', { bubbles: true }));\n var remaining = delay;\n var startedAt = Date.now();\n var timerId = null;\n function dismiss() {\n var ev = new CustomEvent('autodismiss:dismissed', { bubbles: true, cancelable: true });\n me.dispatchEvent(ev);\n if (ev.defaultPrevented) return;\n if (effect === 'fade') {\n me.style.transition = 'opacity 300ms';\n me.style.opacity = '0';\n setTimeout(function() { me.remove(); }, 300);\n } else {\n me.remove();\n }\n }\n function start() {\n startedAt = Date.now();\n timerId = setTimeout(dismiss, remaining);\n }\n start();\n if (pauseOnHover) {\n me.addEventListener('mouseenter', function() {\n if (timerId != null) {\n clearTimeout(timerId);\n timerId = null;\n remaining -= Date.now() - startedAt;\n if (remaining < 0) remaining = 0;\n me.dispatchEvent(new CustomEvent('autodismiss:paused', { bubbles: true }));\n }\n });\n me.addEventListener('mouseleave', function() {\n if (timerId == null && me.isConnected) {\n me.dispatchEvent(new CustomEvent('autodismiss:resumed', { bubbles: true }));\n start();\n }\n });\n }\n end\n end\nend`.trim(),\n};\n","import type { BehaviorSchema } from './types';\n\n/**\n * Clipboard Behavior Schema\n *\n * Copies text to clipboard on click with visual feedback.\n * Uses the Clipboard API with execCommand fallback.\n */\nexport const clipboardSchema: BehaviorSchema = {\n name: 'Clipboard',\n category: 'ui',\n tier: 'core',\n version: '1.0.0',\n description: 'Copies text to clipboard on click with visual feedback',\n parameters: [\n {\n name: 'text',\n type: 'string',\n optional: true,\n description: 'Literal text to copy (takes priority over source)',\n },\n {\n name: 'source',\n type: 'selector',\n optional: true,\n default: 'me',\n description: 'Element whose textContent or value is copied',\n },\n {\n name: 'feedback',\n type: 'selector',\n optional: true,\n description: 'Element to show .copied feedback on (defaults to the installed element)',\n },\n {\n name: 'feedbackDuration',\n type: 'number',\n optional: true,\n default: 2000,\n description: 'Duration in ms to show .copied class feedback',\n },\n ],\n events: [\n { name: 'clipboard:copied', description: 'Fired after text is copied successfully' },\n { name: 'clipboard:error', description: 'Fired when clipboard write fails' },\n ],\n requirements: [\n 'navigator.clipboard.writeText() requires HTTPS or localhost',\n 'Falls back to document.execCommand(\"copy\") for older browsers',\n ],\n source: `\nbehavior Clipboard(text, source, feedback, feedbackDuration)\n init\n if feedbackDuration is undefined\n set feedbackDuration to 2000\n end\n if source is undefined\n set source to me\n end\n end\n on click\n if text is not undefined\n set copyText to text\n else\n set copyText to source's textContent\n end\n js(me, copyText, feedback, feedbackDuration)\n async function copyToClipboard(t) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n await navigator.clipboard.writeText(t);\n return;\n }\n var ta = document.createElement('textarea');\n ta.value = t;\n ta.style.position = 'fixed';\n ta.style.opacity = '0';\n document.body.appendChild(ta);\n ta.select();\n try { document.execCommand('copy'); } finally { document.body.removeChild(ta); }\n }\n try {\n await copyToClipboard(copyText);\n var feedbackEl = feedback ? (typeof feedback === 'string' ? document.querySelector(feedback) : feedback) : me;\n if (!feedbackEl) feedbackEl = me;\n feedbackEl.classList.add('copied');\n me.dispatchEvent(new CustomEvent('clipboard:copied', { bubbles: true, detail: { text: copyText } }));\n setTimeout(function() { feedbackEl.classList.remove('copied'); }, feedbackDuration);\n } catch(err) {\n me.dispatchEvent(new CustomEvent('clipboard:error', { bubbles: true, detail: { error: err } }));\n }\n end\n end\nend`.trim(),\n};\n","import type { BehaviorSchema } from './types';\n\n/**\n * Draggable Behavior Schema\n *\n * Makes elements draggable with pointer events.\n * Supports custom drag handles and lifecycle events.\n */\nexport const draggableSchema: BehaviorSchema = {\n name: 'Draggable',\n category: 'ui',\n tier: 'core',\n version: '1.0.0',\n description: 'Makes elements draggable with pointer events',\n parameters: [\n {\n name: 'dragHandle',\n type: 'selector',\n optional: true,\n default: 'me',\n description: 'CSS selector for the drag handle element',\n },\n ],\n events: [\n { name: 'draggable:start', description: 'Fired when drag begins' },\n { name: 'draggable:move', description: 'Fired during each movement' },\n { name: 'draggable:end', description: 'Fired when drag completes' },\n ],\n requirements: ['position: absolute or position: fixed on the element'],\n source: `\nbehavior Draggable(dragHandle)\n init\n if no dragHandle set dragHandle to me\n end\n on pointerdown(clientX, clientY) from dragHandle\n halt the event\n trigger draggable:start\n measure x\n set startX to it\n measure y\n set startY to it\n set xoff to clientX - startX\n set yoff to clientY - startY\n repeat until event pointerup from document\n wait for pointermove(clientX, clientY) or\n pointerup(clientX, clientY) from document\n add { left: \\${clientX - xoff}px; top: \\${clientY - yoff}px; }\n trigger draggable:move\n end\n trigger draggable:end\n end\nend\n`.trim(),\n};\n","import type { BehaviorSchema } from './types';\n\n/**\n * ClickOutside Behavior Schema\n *\n * Fires an event when the user clicks outside the element.\n * Uses pointerdown instead of click to avoid timing issues.\n */\nexport const clickOutsideSchema: BehaviorSchema = {\n name: 'ClickOutside',\n category: 'ui',\n tier: 'core',\n version: '1.0.0',\n description: 'Fires an event when the user clicks outside the element',\n parameters: [\n {\n name: 'active',\n type: 'boolean',\n optional: true,\n default: true,\n description: 'Whether the listener is active (can be toggled programmatically)',\n },\n ],\n events: [\n { name: 'clickoutside', description: 'Fired when a pointer press occurs outside the element' },\n {\n name: 'clickoutside:activate',\n description: 'Listen for this event to programmatically activate detection',\n },\n {\n name: 'clickoutside:deactivate',\n description: 'Listen for this event to programmatically deactivate detection',\n },\n ],\n requirements: ['Uses pointerdown instead of click to avoid timing issues with removed elements'],\n source: `\nbehavior ClickOutside(active)\n init\n if active is undefined\n set active to true\n end\n end\n on pointerdown from document\n js(me, active)\n if (!me.isConnected) return;\n if (active && !me.contains(event.target)) {\n me.dispatchEvent(new CustomEvent('clickoutside', { bubbles: true }));\n }\n end\n end\n on clickoutside:activate\n set active to true\n end\n on clickoutside:deactivate\n set active to false\n end\nend`.trim(),\n};\n","import type { BehaviorSchema } from './types';\n\n/**\n * ScrollReveal Behavior Schema\n *\n * Adds a class or fires events when the element enters or exits the viewport.\n * Uses the IntersectionObserver web standard API.\n */\nexport const scrollRevealSchema: BehaviorSchema = {\n name: 'ScrollReveal',\n category: 'layout',\n tier: 'common',\n version: '1.0.0',\n description: 'Adds a class when the element enters the viewport via IntersectionObserver',\n parameters: [\n {\n name: 'cls',\n type: 'string',\n optional: true,\n default: 'revealed',\n description: 'CSS class added when element enters viewport',\n },\n {\n name: 'threshold',\n type: 'number',\n optional: true,\n default: 0.1,\n description: 'Intersection ratio threshold (0-1) for triggering',\n },\n {\n name: 'once',\n type: 'boolean',\n optional: true,\n default: true,\n description: 'If true, disconnect observer after first intersection',\n },\n ],\n events: [\n { name: 'scrollreveal:enter', description: 'Fired when the element enters the viewport' },\n { name: 'scrollreveal:exit', description: 'Fired when the element exits the viewport' },\n ],\n requirements: ['Uses IntersectionObserver API (supported in all modern browsers)'],\n source: `\nbehavior ScrollReveal(cls, threshold, once)\n init\n if cls is undefined\n set cls to \"revealed\"\n end\n if threshold is undefined\n set threshold to 0.1\n end\n if once is undefined\n set once to true\n end\n js(me, cls, threshold, once)\n var obs = new IntersectionObserver(function(entries) {\n for (var i = 0; i < entries.length; i++) {\n if (entries[i].isIntersecting) {\n me.classList.add(cls);\n me.dispatchEvent(new CustomEvent('scrollreveal:enter', { bubbles: true }));\n if (once) obs.disconnect();\n } else if (!once) {\n me.classList.remove(cls);\n me.dispatchEvent(new CustomEvent('scrollreveal:exit', { bubbles: true }));\n }\n }\n }, { threshold: threshold });\n obs.observe(me);\n end\n end\nend`.trim(),\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","import type { BehaviorSchema } from './types';\n\n/**\n * FocusTrap Behavior Schema\n *\n * Confines Tab navigation inside an element and restores focus on release.\n * Manages aria-modal for accessibility compliance.\n */\nexport const focusTrapSchema: BehaviorSchema = {\n name: 'FocusTrap',\n category: 'ui',\n tier: 'core',\n version: '1.0.0',\n description: 'Confines Tab navigation inside an element with ARIA support',\n parameters: [\n {\n name: 'active',\n type: 'boolean',\n optional: true,\n default: true,\n description: 'Whether the trap is active on install',\n },\n {\n name: 'initialFocus',\n type: 'selector',\n optional: true,\n description: 'Element to focus when the trap activates',\n },\n {\n name: 'returnFocus',\n type: 'boolean',\n optional: true,\n default: true,\n description: 'Restore focus to the previously focused element on deactivation',\n },\n ],\n events: [\n { name: 'focustrap:activated', description: 'Fired when the trap is activated' },\n { name: 'focustrap:deactivated', description: 'Fired when the trap is deactivated' },\n ],\n requirements: [\n 'Sets aria-modal=\"true\" on the element when active',\n 'Listens for focustrap:activate and focustrap:deactivate custom events for programmatic control',\n ],\n source: `\nbehavior FocusTrap(active, initialFocus, returnFocus)\n init\n if active is undefined\n set active to true\n end\n if returnFocus is undefined\n set returnFocus to true\n end\n js(me, active, initialFocus, returnFocus)\n var FOCUSABLE = 'a[href],button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex=\"-1\"])';\n var isActive = false;\n var previouslyFocused = null;\n function getFocusable() { return Array.from(me.querySelectorAll(FOCUSABLE)); }\n function activate() {\n if (isActive) return;\n isActive = true;\n previouslyFocused = document.activeElement;\n me.setAttribute('aria-modal', 'true');\n var initEl = initialFocus ? (typeof initialFocus === 'string' ? me.querySelector(initialFocus) : initialFocus) : null;\n if (initEl) { initEl.focus(); } else { var f = getFocusable(); if (f.length) f[0].focus(); }\n me.dispatchEvent(new CustomEvent('focustrap:activated', { bubbles: true }));\n }\n function deactivate() {\n if (!isActive) return;\n isActive = false;\n me.removeAttribute('aria-modal');\n if (returnFocus && previouslyFocused instanceof HTMLElement) previouslyFocused.focus();\n previouslyFocused = null;\n me.dispatchEvent(new CustomEvent('focustrap:deactivated', { bubbles: true }));\n }\n me.addEventListener('keydown', function(e) {\n if (!isActive || e.key !== 'Tab') return;\n var focusable = getFocusable();\n if (focusable.length === 0) { e.preventDefault(); return; }\n e.preventDefault();\n var idx = focusable.indexOf(document.activeElement);\n if (e.shiftKey) {\n focusable[idx <= 0 ? focusable.length - 1 : idx - 1].focus();\n } else {\n focusable[idx >= focusable.length - 1 ? 0 : idx + 1].focus();\n }\n });\n me.addEventListener('focustrap:activate', function() { activate(); });\n me.addEventListener('focustrap:deactivate', function() { deactivate(); });\n if (active) activate();\n end\n end\nend`.trim(),\n};\n","import type { BehaviorSchema } from './types';\n\n/**\n * Sortable Behavior Schema\n *\n * Drag-and-drop reordering of list items.\n * Apply to a container element; children become sortable.\n *\n * Note: This behavior fires lifecycle events but does NOT automatically\n * reorder DOM elements. Users must handle actual reordering in their\n * `sortable:move` event handlers.\n */\nexport const sortableSchema: BehaviorSchema = {\n name: 'Sortable',\n category: 'ui',\n tier: 'common',\n version: '1.0.0',\n description: 'Makes child elements reorderable via drag-and-drop',\n parameters: [\n {\n name: 'handle',\n type: 'selector',\n optional: true,\n default: 'none',\n description: 'CSS selector for drag handles within items',\n },\n {\n name: 'dragClass',\n type: 'string',\n optional: true,\n default: 'sorting',\n description: 'CSS class added to item during drag',\n },\n ],\n events: [\n { name: 'sortable:start', description: 'Fired when drag begins' },\n {\n name: 'sortable:move',\n description: 'Fired during drag — handle this event to reorder DOM elements',\n },\n { name: 'sortable:end', description: 'Fired when drag completes' },\n ],\n requirements: ['Users must handle DOM reordering in sortable:move event handlers'],\n source: `\nbehavior Sortable(handle, dragClass)\n init\n if dragClass is undefined\n set dragClass to \"sorting\"\n end\n end\n on pointerdown(clientY) from me\n js(me, handle, event)\n var target = event.target;\n var item = target.closest(\"li\");\n if (!item || !me.contains(item)) return false;\n if (handle && !target.closest(handle)) return false;\n event.preventDefault();\n event.stopPropagation();\n return true;\n end\n if it is false\n exit\n end\n add .{dragClass} to me\n trigger sortable:start on me\n repeat until event pointerup from document\n wait for pointermove(clientY) from document\n trigger sortable:move on me\n end\n remove .{dragClass} from me\n trigger sortable:end on me\n end\nend`.trim(),\n};\n","import type { BehaviorSchema } from './types';\n\n/**\n * Resizable Behavior Schema\n *\n * Makes elements resizable by dragging edges or corners.\n * Supports minimum/maximum dimensions and resize handles.\n */\nexport const resizableSchema: BehaviorSchema = {\n name: 'Resizable',\n category: 'ui',\n tier: 'optional',\n version: '1.0.0',\n description: 'Makes elements resizable by dragging',\n parameters: [\n {\n name: 'handle',\n type: 'selector',\n optional: true,\n default: 'me',\n description: 'CSS selector for the resize handle (defaults to the element itself)',\n },\n {\n name: 'minWidth',\n type: 'number',\n optional: true,\n default: 50,\n description: 'Minimum width in pixels',\n },\n {\n name: 'minHeight',\n type: 'number',\n optional: true,\n default: 50,\n description: 'Minimum height in pixels',\n },\n {\n name: 'maxWidth',\n type: 'number',\n optional: true,\n default: 9999,\n description: 'Maximum width in pixels',\n },\n {\n name: 'maxHeight',\n type: 'number',\n optional: true,\n default: 9999,\n description: 'Maximum height in pixels',\n },\n ],\n events: [\n { name: 'resizable:start', description: 'Fired when resize begins' },\n { name: 'resizable:resize', description: 'Fired during resize' },\n { name: 'resizable:end', description: 'Fired when resize completes' },\n ],\n source: `\nbehavior Resizable(handle, minWidth, minHeight, maxWidth, maxHeight)\n init\n if handle is undefined\n set handle to me\n end\n if minWidth is undefined\n set minWidth to 50\n end\n if minHeight is undefined\n set minHeight to 50\n end\n if maxWidth is undefined\n set maxWidth to 9999\n end\n if maxHeight is undefined\n set maxHeight to 9999\n end\n end\n on pointerdown(clientX, clientY) from handle\n halt the event\n trigger resizable:start\n measure width\n set startWidth to it\n measure height\n set startHeight to it\n set startX to clientX\n set startY to clientY\n repeat until event pointerup from document\n wait for pointermove(clientX, clientY) or pointerup from document\n js(startWidth, startHeight, clientX, clientY, startX, startY, minWidth, maxWidth, minHeight, maxHeight, me)\n var nw = Math.max(minWidth, Math.min(maxWidth, startWidth + (clientX - startX)));\n var nh = Math.max(minHeight, Math.min(maxHeight, startHeight + (clientY - startY)));\n me.style.width = nw + 'px';\n me.style.height = nh + 'px';\n end\n trigger resizable:resize\n end\n trigger resizable:end\n end\nend\n`.trim(),\n};\n","/**\n * Behavior Resolver — Lazy compilation of hyperscript behaviors\n *\n * Provides a resolver function that compiles behavior source strings on demand.\n * When the runtime encounters `install X` and X isn't registered, it calls\n * the resolver. If the resolver has source for X, it compiles it, registers\n * the behavior, and installation proceeds normally.\n *\n * @example\n * ```typescript\n * import { createBehaviorResolver } from '@hyperfixi/behaviors/resolver';\n *\n * const resolve = createBehaviorResolver(window.hyperfixi, window._hyperscript.behaviors);\n * window._hyperscript.behaviors.resolve = resolve;\n *\n * // Now `install Toggleable` will compile the behavior on first use\n * ```\n */\n\nimport { toggleableSchema } from './schemas/toggleable.schema';\nimport { removableSchema } from './schemas/removable.schema';\nimport { autoDismissSchema } from './schemas/autodismiss.schema';\nimport { clipboardSchema } from './schemas/clipboard.schema';\nimport { draggableSchema } from './schemas/draggable.schema';\nimport { clickOutsideSchema } from './schemas/clickoutside.schema';\nimport { scrollRevealSchema } from './schemas/scrollreveal.schema';\nimport { tabsSchema } from './schemas/tabs.schema';\nimport { focusTrapSchema } from './schemas/focustrap.schema';\nimport { sortableSchema } from './schemas/sortable.schema';\nimport { resizableSchema } from './schemas/resizable.schema';\n\nexport const BEHAVIOR_SOURCES: Record<string, string> = {\n Toggleable: toggleableSchema.source,\n Removable: removableSchema.source,\n AutoDismiss: autoDismissSchema.source,\n Clipboard: clipboardSchema.source,\n Draggable: draggableSchema.source,\n ClickOutside: clickOutsideSchema.source,\n ScrollReveal: scrollRevealSchema.source,\n Tabs: tabsSchema.source,\n FocusTrap: focusTrapSchema.source,\n Sortable: sortableSchema.source,\n Resizable: resizableSchema.source,\n};\n\ninterface CompileResult {\n ok: boolean;\n ast?: any;\n errors?: unknown[];\n}\n\ninterface BehaviorAPI {\n set(name: string, definition: any): void;\n}\n\ninterface HyperFixiAPI {\n compileSync(code: string, options?: { traditional?: boolean }): CompileResult;\n}\n\n/**\n * Create a behavior resolver that compiles hyperscript source on demand.\n *\n * The returned function is meant to be assigned to `_hyperscript.behaviors.resolve`.\n * When called with a behavior name, it looks up the source, compiles it,\n * and registers the resulting AST in the behavior registry.\n *\n * @param hyperfixi - The hyperfixi API (needs compileSync)\n * @param behaviorAPI - The behavior registry API (needs set)\n * @returns Resolver function: (name: string) => boolean\n */\nexport function createBehaviorResolver(\n hyperfixi: HyperFixiAPI,\n behaviorAPI: BehaviorAPI\n): (name: string) => boolean {\n return (name: string): boolean => {\n const source = BEHAVIOR_SOURCES[name];\n if (!source) return false;\n\n const result = hyperfixi.compileSync(source, { traditional: true });\n if (!result.ok || !result.ast) return false;\n\n const ast = result.ast as any;\n behaviorAPI.set(name, {\n name: ast.name,\n parameters: ast.parameters,\n eventHandlers: ast.eventHandlers,\n initBlock: ast.initBlock,\n });\n return true;\n };\n}\n","/**\n * Browser Behavior Resolver Bundle\n *\n * Pre-registers all standard behavior definitions with the hyperfixi runtime.\n * Include after hyperfixi.js — all standard behaviors become available.\n *\n * Usage:\n * <script src=\"hyperfixi.js\"></script>\n * <script src=\"hyperfixi-behaviors.js\"></script>\n * <!-- install Toggleable, Draggable, etc. just work -->\n */\n\nimport { BEHAVIOR_SOURCES } from './behavior-resolver';\n\nfunction init() {\n const hf = (window as any).hyperfixi;\n if (!hf?.compileSync || !hf?.execute) return;\n\n // Compile and register each behavior definition with the runtime.\n // This is equivalent to having them in a <script type=\"text/hyperscript\"> block.\n for (const [name, source] of Object.entries(BEHAVIOR_SOURCES)) {\n const result = hf.compileSync(source, { traditional: true });\n if (result?.ok && result.ast) {\n // execute() registers the behavior in the runtime's behaviorRegistry\n hf.execute(result.ast).catch(() => {\n // Async — errors handled silently\n });\n }\n }\n\n // Re-scan: the attribute processor may have already processed elements\n // and failed (behaviors weren't registered). Reset and re-init.\n const ap = hf.attributeProcessor;\n if (ap) {\n ap.reset();\n ap.init();\n }\n}\n\nif (typeof window !== 'undefined') {\n init();\n}\n"],"mappings":"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","names":["toggleableSchema","removableSchema","autoDismissSchema","clipboardSchema","draggableSchema","clickOutsideSchema","scrollRevealSchema","tabsSchema","focusTrapSchema","sortableSchema","resizableSchema","BEHAVIOR_SOURCES","toggleableSchema","removableSchema","autoDismissSchema","clipboardSchema","draggableSchema","clickOutsideSchema","scrollRevealSchema","tabsSchema","focusTrapSchema","sortableSchema","resizableSchema","init","hf","name","source","BEHAVIOR_SOURCES","result","ap"]}
@@ -120,27 +120,33 @@ var sortableSchema = {
120
120
  source: `
121
121
  behavior Sortable(handle, dragClass)
122
122
  init
123
- if no dragClass set dragClass to "sorting"
123
+ if dragClass is undefined
124
+ set dragClass to "sorting"
125
+ end
124
126
  end
125
- on pointerdown(target, clientY) from me
126
- set item to closest <li/> in target
127
- if no item exit
128
- if handle
129
- set handleEl to target.closest(handle)
130
- if no handleEl exit
127
+ on pointerdown(clientY) from me
128
+ js(me, handle, event)
129
+ var target = event.target;
130
+ var item = target.closest("li");
131
+ if (!item || !me.contains(item)) return false;
132
+ if (handle && !target.closest(handle)) return false;
133
+ event.preventDefault();
134
+ event.stopPropagation();
135
+ return true;
131
136
  end
132
- halt the event
133
- add dragClass to item
137
+ if it is false
138
+ exit
139
+ end
140
+ add .{dragClass} to me
134
141
  trigger sortable:start on me
135
142
  repeat until event pointerup from document
136
143
  wait for pointermove(clientY) from document
137
144
  trigger sortable:move on me
138
145
  end
139
- remove dragClass from item
146
+ remove .{dragClass} from me
140
147
  trigger sortable:end on me
141
148
  end
142
- end
143
- `.trim()
149
+ end`.trim()
144
150
  };
145
151
 
146
152
  // src/schemas/resizable.schema.ts
@@ -195,11 +201,21 @@ var resizableSchema = {
195
201
  source: `
196
202
  behavior Resizable(handle, minWidth, minHeight, maxWidth, maxHeight)
197
203
  init
198
- if no handle set handle to me
199
- if no minWidth set minWidth to 50
200
- if no minHeight set minHeight to 50
201
- if no maxWidth set maxWidth to 9999
202
- if no maxHeight set maxHeight to 9999
204
+ if handle is undefined
205
+ set handle to me
206
+ end
207
+ if minWidth is undefined
208
+ set minWidth to 50
209
+ end
210
+ if minHeight is undefined
211
+ set minHeight to 50
212
+ end
213
+ if maxWidth is undefined
214
+ set maxWidth to 9999
215
+ end
216
+ if maxHeight is undefined
217
+ set maxHeight to 9999
218
+ end
203
219
  end
204
220
  on pointerdown(clientX, clientY) from handle
205
221
  halt the event
@@ -211,15 +227,13 @@ behavior Resizable(handle, minWidth, minHeight, maxWidth, maxHeight)
211
227
  set startX to clientX
212
228
  set startY to clientY
213
229
  repeat until event pointerup from document
214
- wait for pointermove(clientX, clientY) or
215
- pointerup from document
216
- set newWidth to startWidth + (clientX - startX)
217
- set newHeight to startHeight + (clientY - startY)
218
- if newWidth < minWidth set newWidth to minWidth
219
- if newWidth > maxWidth set newWidth to maxWidth
220
- if newHeight < minHeight set newHeight to minHeight
221
- if newHeight > maxHeight set newHeight to maxHeight
222
- add { width: \${newWidth}px; height: \${newHeight}px; }
230
+ wait for pointermove(clientX, clientY) or pointerup from document
231
+ js(startWidth, startHeight, clientX, clientY, startX, startY, minWidth, maxWidth, minHeight, maxHeight, me)
232
+ var nw = Math.max(minWidth, Math.min(maxWidth, startWidth + (clientX - startX)));
233
+ var nh = Math.max(minHeight, Math.min(maxHeight, startHeight + (clientY - startY)));
234
+ me.style.width = nw + 'px';
235
+ me.style.height = nh + 'px';
236
+ end
223
237
  trigger resizable:resize
224
238
  end
225
239
  trigger resizable:end
@@ -244,7 +258,7 @@ var removableSchema = {
244
258
  description: "Element that triggers removal (e.g., a close button)"
245
259
  },
246
260
  {
247
- name: "confirm",
261
+ name: "confirmRemoval",
248
262
  type: "boolean",
249
263
  optional: true,
250
264
  default: false,
@@ -264,15 +278,18 @@ var removableSchema = {
264
278
  { name: "removable:removed", description: "Fired after removal" }
265
279
  ],
266
280
  source: `
267
- behavior Removable(triggerEl, confirm, effect)
281
+ behavior Removable(triggerEl, confirmRemoval, effect)
268
282
  init
269
283
  if triggerEl is undefined
270
284
  set triggerEl to me
271
285
  end
272
286
  end
273
287
  on click from triggerEl
274
- if confirm
275
- if not window.confirm("Are you sure?")
288
+ if confirmRemoval
289
+ js(me)
290
+ if (!window.confirm("Are you sure?")) return "cancel";
291
+ end
292
+ if it is "cancel"
276
293
  halt
277
294
  end
278
295
  end
@@ -326,9 +343,12 @@ behavior Toggleable(cls, target)
326
343
  end
327
344
  on click
328
345
  toggle .{cls} on target
346
+ js(target, cls)
347
+ var eventName = target.classList.contains(cls) ? 'toggleable:on' : 'toggleable:off';
348
+ target.dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
349
+ end
329
350
  end
330
- end
331
- `.trim()
351
+ end`.trim()
332
352
  };
333
353
 
334
354
  // src/schemas/clipboard.schema.ts
@@ -374,27 +394,49 @@ var clipboardSchema = {
374
394
  "navigator.clipboard.writeText() requires HTTPS or localhost",
375
395
  'Falls back to document.execCommand("copy") for older browsers'
376
396
  ],
377
- source: [
378
- "behavior Clipboard(text, source, feedback, feedbackDuration)",
379
- " init",
380
- " if feedbackDuration is undefined set feedbackDuration to 2000",
381
- " if source is undefined set source to me",
382
- " end",
383
- " on click",
384
- " if text is not undefined",
385
- " set copyText to text",
386
- " else",
387
- " set copyText to source's value or source's textContent",
388
- " end",
389
- " js(copyText) navigator.clipboard.writeText(copyText) end",
390
- " set feedbackEl to feedback or me",
391
- " add .copied to feedbackEl",
392
- " wait feedbackDuration ms",
393
- " remove .copied from feedbackEl",
394
- " trigger clipboard:copied",
395
- " end",
396
- "end"
397
- ].join("\n")
397
+ source: `
398
+ behavior Clipboard(text, source, feedback, feedbackDuration)
399
+ init
400
+ if feedbackDuration is undefined
401
+ set feedbackDuration to 2000
402
+ end
403
+ if source is undefined
404
+ set source to me
405
+ end
406
+ end
407
+ on click
408
+ if text is not undefined
409
+ set copyText to text
410
+ else
411
+ set copyText to source's textContent
412
+ end
413
+ js(me, copyText, feedback, feedbackDuration)
414
+ async function copyToClipboard(t) {
415
+ if (navigator.clipboard && navigator.clipboard.writeText) {
416
+ await navigator.clipboard.writeText(t);
417
+ return;
418
+ }
419
+ var ta = document.createElement('textarea');
420
+ ta.value = t;
421
+ ta.style.position = 'fixed';
422
+ ta.style.opacity = '0';
423
+ document.body.appendChild(ta);
424
+ ta.select();
425
+ try { document.execCommand('copy'); } finally { document.body.removeChild(ta); }
426
+ }
427
+ try {
428
+ await copyToClipboard(copyText);
429
+ var feedbackEl = feedback ? (typeof feedback === 'string' ? document.querySelector(feedback) : feedback) : me;
430
+ if (!feedbackEl) feedbackEl = me;
431
+ feedbackEl.classList.add('copied');
432
+ me.dispatchEvent(new CustomEvent('clipboard:copied', { bubbles: true, detail: { text: copyText } }));
433
+ setTimeout(function() { feedbackEl.classList.remove('copied'); }, feedbackDuration);
434
+ } catch(err) {
435
+ me.dispatchEvent(new CustomEvent('clipboard:error', { bubbles: true, detail: { error: err } }));
436
+ }
437
+ end
438
+ end
439
+ end`.trim()
398
440
  };
399
441
 
400
442
  // src/schemas/autodismiss.schema.ts
@@ -437,24 +479,60 @@ var autoDismissSchema = {
437
479
  { name: "autodismiss:paused", description: "Fired when the timer is paused on hover" },
438
480
  { name: "autodismiss:resumed", description: "Fired when the timer resumes on mouseleave" }
439
481
  ],
440
- source: [
441
- "behavior AutoDismiss(delay, pauseOnHover, effect)",
442
- " init",
443
- " if delay is undefined set delay to 5000",
444
- " if pauseOnHover is undefined set pauseOnHover to true",
445
- ' if effect is undefined set effect to "none"',
446
- " end",
447
- " on load",
448
- " trigger autodismiss:start",
449
- " wait delay ms",
450
- " trigger autodismiss:dismissed",
451
- ' if effect is "fade"',
452
- " transition opacity to 0 over 300ms",
453
- " end",
454
- " remove me",
455
- " end",
456
- "end"
457
- ].join("\n")
482
+ source: `
483
+ behavior AutoDismiss(delay, pauseOnHover, effect)
484
+ init
485
+ if delay is undefined
486
+ set delay to 5000
487
+ end
488
+ if pauseOnHover is undefined
489
+ set pauseOnHover to true
490
+ end
491
+ if effect is undefined
492
+ set effect to "none"
493
+ end
494
+ js(me, delay, effect, pauseOnHover)
495
+ me.dispatchEvent(new CustomEvent('autodismiss:start', { bubbles: true }));
496
+ var remaining = delay;
497
+ var startedAt = Date.now();
498
+ var timerId = null;
499
+ function dismiss() {
500
+ var ev = new CustomEvent('autodismiss:dismissed', { bubbles: true, cancelable: true });
501
+ me.dispatchEvent(ev);
502
+ if (ev.defaultPrevented) return;
503
+ if (effect === 'fade') {
504
+ me.style.transition = 'opacity 300ms';
505
+ me.style.opacity = '0';
506
+ setTimeout(function() { me.remove(); }, 300);
507
+ } else {
508
+ me.remove();
509
+ }
510
+ }
511
+ function start() {
512
+ startedAt = Date.now();
513
+ timerId = setTimeout(dismiss, remaining);
514
+ }
515
+ start();
516
+ if (pauseOnHover) {
517
+ me.addEventListener('mouseenter', function() {
518
+ if (timerId != null) {
519
+ clearTimeout(timerId);
520
+ timerId = null;
521
+ remaining -= Date.now() - startedAt;
522
+ if (remaining < 0) remaining = 0;
523
+ me.dispatchEvent(new CustomEvent('autodismiss:paused', { bubbles: true }));
524
+ }
525
+ });
526
+ me.addEventListener('mouseleave', function() {
527
+ if (timerId == null && me.isConnected) {
528
+ me.dispatchEvent(new CustomEvent('autodismiss:resumed', { bubbles: true }));
529
+ start();
530
+ }
531
+ });
532
+ }
533
+ end
534
+ end
535
+ end`.trim()
458
536
  };
459
537
 
460
538
  // src/schemas/clickoutside.schema.ts
@@ -474,19 +552,38 @@ var clickOutsideSchema = {
474
552
  }
475
553
  ],
476
554
  events: [
477
- { name: "clickoutside", description: "Fired when a pointer press occurs outside the element" }
555
+ { name: "clickoutside", description: "Fired when a pointer press occurs outside the element" },
556
+ {
557
+ name: "clickoutside:activate",
558
+ description: "Listen for this event to programmatically activate detection"
559
+ },
560
+ {
561
+ name: "clickoutside:deactivate",
562
+ description: "Listen for this event to programmatically deactivate detection"
563
+ }
478
564
  ],
479
565
  requirements: ["Uses pointerdown instead of click to avoid timing issues with removed elements"],
480
566
  source: `
481
567
  behavior ClickOutside(active)
482
568
  init
483
- if active is undefined set active to true
569
+ if active is undefined
570
+ set active to true
571
+ end
484
572
  end
485
573
  on pointerdown from document
486
- if active and not me.contains(event.target)
487
- trigger clickoutside on me
574
+ js(me, active)
575
+ if (!me.isConnected) return;
576
+ if (active && !me.contains(event.target)) {
577
+ me.dispatchEvent(new CustomEvent('clickoutside', { bubbles: true }));
578
+ }
488
579
  end
489
580
  end
581
+ on clickoutside:activate
582
+ set active to true
583
+ end
584
+ on clickoutside:deactivate
585
+ set active to false
586
+ end
490
587
  end`.trim()
491
588
  };
492
589
 
@@ -530,10 +627,51 @@ var focusTrapSchema = {
530
627
  source: `
531
628
  behavior FocusTrap(active, initialFocus, returnFocus)
532
629
  init
533
- if active is undefined set active to true
534
- if returnFocus is undefined set returnFocus to true
630
+ if active is undefined
631
+ set active to true
632
+ end
633
+ if returnFocus is undefined
634
+ set returnFocus to true
635
+ end
636
+ js(me, active, initialFocus, returnFocus)
637
+ var FOCUSABLE = 'a[href],button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex="-1"])';
638
+ var isActive = false;
639
+ var previouslyFocused = null;
640
+ function getFocusable() { return Array.from(me.querySelectorAll(FOCUSABLE)); }
641
+ function activate() {
642
+ if (isActive) return;
643
+ isActive = true;
644
+ previouslyFocused = document.activeElement;
645
+ me.setAttribute('aria-modal', 'true');
646
+ var initEl = initialFocus ? (typeof initialFocus === 'string' ? me.querySelector(initialFocus) : initialFocus) : null;
647
+ if (initEl) { initEl.focus(); } else { var f = getFocusable(); if (f.length) f[0].focus(); }
648
+ me.dispatchEvent(new CustomEvent('focustrap:activated', { bubbles: true }));
649
+ }
650
+ function deactivate() {
651
+ if (!isActive) return;
652
+ isActive = false;
653
+ me.removeAttribute('aria-modal');
654
+ if (returnFocus && previouslyFocused instanceof HTMLElement) previouslyFocused.focus();
655
+ previouslyFocused = null;
656
+ me.dispatchEvent(new CustomEvent('focustrap:deactivated', { bubbles: true }));
657
+ }
658
+ me.addEventListener('keydown', function(e) {
659
+ if (!isActive || e.key !== 'Tab') return;
660
+ var focusable = getFocusable();
661
+ if (focusable.length === 0) { e.preventDefault(); return; }
662
+ e.preventDefault();
663
+ var idx = focusable.indexOf(document.activeElement);
664
+ if (e.shiftKey) {
665
+ focusable[idx <= 0 ? focusable.length - 1 : idx - 1].focus();
666
+ } else {
667
+ focusable[idx >= focusable.length - 1 ? 0 : idx + 1].focus();
668
+ }
669
+ });
670
+ me.addEventListener('focustrap:activate', function() { activate(); });
671
+ me.addEventListener('focustrap:deactivate', function() { deactivate(); });
672
+ if (active) activate();
673
+ end
535
674
  end
536
- -- Imperative installer handles Tab key trapping and focus management
537
675
  end`.trim()
538
676
  };
539
677
 
@@ -565,13 +703,6 @@ var scrollRevealSchema = {
565
703
  optional: true,
566
704
  default: true,
567
705
  description: "If true, disconnect observer after first intersection"
568
- },
569
- {
570
- name: "rootMargin",
571
- type: "string",
572
- optional: true,
573
- default: "0px",
574
- description: "CSS margin around the viewport for triggering earlier or later"
575
706
  }
576
707
  ],
577
708
  events: [
@@ -580,14 +711,33 @@ var scrollRevealSchema = {
580
711
  ],
581
712
  requirements: ["Uses IntersectionObserver API (supported in all modern browsers)"],
582
713
  source: `
583
- behavior ScrollReveal(cls, threshold, once, rootMargin)
714
+ behavior ScrollReveal(cls, threshold, once)
584
715
  init
585
- if cls is undefined set cls to "revealed"
586
- if threshold is undefined set threshold to 0.1
587
- if once is undefined set once to true
588
- if rootMargin is undefined set rootMargin to "0px"
716
+ if cls is undefined
717
+ set cls to "revealed"
718
+ end
719
+ if threshold is undefined
720
+ set threshold to 0.1
721
+ end
722
+ if once is undefined
723
+ set once to true
724
+ end
725
+ js(me, cls, threshold, once)
726
+ var obs = new IntersectionObserver(function(entries) {
727
+ for (var i = 0; i < entries.length; i++) {
728
+ if (entries[i].isIntersecting) {
729
+ me.classList.add(cls);
730
+ me.dispatchEvent(new CustomEvent('scrollreveal:enter', { bubbles: true }));
731
+ if (once) obs.disconnect();
732
+ } else if (!once) {
733
+ me.classList.remove(cls);
734
+ me.dispatchEvent(new CustomEvent('scrollreveal:exit', { bubbles: true }));
735
+ }
736
+ }
737
+ }, { threshold: threshold });
738
+ obs.observe(me);
739
+ end
589
740
  end
590
- -- Imperative installer handles IntersectionObserver setup
591
741
  end`.trim()
592
742
  };
593
743
 
@@ -642,17 +792,86 @@ var tabsSchema = {
642
792
  "Manages aria-selected, aria-controls, aria-labelledby, and tabindex",
643
793
  "Keyboard: Arrow keys navigate, Home/End jump to first/last, Tab exits tablist"
644
794
  ],
645
- source: [
646
- "behavior Tabs(orientation, activeTab, wrap, activeClass)",
647
- " init",
648
- ' if orientation is undefined set orientation to "horizontal"',
649
- " if activeTab is undefined set activeTab to 0",
650
- " if wrap is undefined set wrap to true",
651
- ' if activeClass is undefined set activeClass to "active"',
652
- " end",
653
- " -- Imperative installer handles ARIA wiring and keyboard navigation",
654
- "end"
655
- ].join("\n")
795
+ source: `
796
+ behavior Tabs(orientation, activeTab, wrap, activeClass)
797
+ init
798
+ if orientation is undefined
799
+ set orientation to "horizontal"
800
+ end
801
+ if activeTab is undefined
802
+ set activeTab to 0
803
+ end
804
+ if wrap is undefined
805
+ set wrap to true
806
+ end
807
+ if activeClass is undefined
808
+ set activeClass to "active"
809
+ end
810
+ js(me, orientation, activeTab, wrap, activeClass)
811
+ var tabs = Array.from(me.querySelectorAll('[role="tab"]'));
812
+ var panels = Array.from(me.querySelectorAll('[role="tabpanel"]'));
813
+ if (tabs.length === 0 || panels.length === 0) return;
814
+ var tablist = me.querySelector('[role="tablist"]') || tabs[0].parentElement;
815
+ if (tablist) tablist.setAttribute('aria-orientation', orientation);
816
+ var count = Math.min(tabs.length, panels.length);
817
+ var idN = 0;
818
+ for (var i = 0; i < count; i++) {
819
+ if (!tabs[i].id) tabs[i].id = 'tab-' + (++idN);
820
+ if (!panels[i].id) panels[i].id = 'tabpanel-' + idN;
821
+ tabs[i].setAttribute('aria-controls', panels[i].id);
822
+ panels[i].setAttribute('aria-labelledby', tabs[i].id);
823
+ }
824
+ var cur = Math.max(0, Math.min(activeTab, count - 1));
825
+ function activate(idx, prev) {
826
+ if (idx === prev || idx < 0 || idx >= count) return false;
827
+ var ev = new CustomEvent('tabs:change', { bubbles: true, cancelable: true, detail: { index: idx, previousIndex: prev } });
828
+ me.dispatchEvent(ev);
829
+ if (ev.defaultPrevented) return false;
830
+ if (prev >= 0 && prev < count) {
831
+ tabs[prev].setAttribute('aria-selected', 'false');
832
+ tabs[prev].setAttribute('tabindex', '-1');
833
+ tabs[prev].classList.remove(activeClass);
834
+ panels[prev].setAttribute('aria-hidden', 'true');
835
+ panels[prev].classList.remove(activeClass);
836
+ }
837
+ tabs[idx].setAttribute('aria-selected', 'true');
838
+ tabs[idx].setAttribute('tabindex', '0');
839
+ tabs[idx].classList.add(activeClass);
840
+ panels[idx].setAttribute('aria-hidden', 'false');
841
+ panels[idx].classList.add(activeClass);
842
+ cur = idx;
843
+ me.dispatchEvent(new CustomEvent('tabs:changed', { bubbles: true, detail: { index: idx } }));
844
+ return true;
845
+ }
846
+ for (var j = 0; j < count; j++) {
847
+ tabs[j].setAttribute('aria-selected', 'false');
848
+ tabs[j].setAttribute('tabindex', '-1');
849
+ panels[j].setAttribute('aria-hidden', 'true');
850
+ panels[j].classList.remove(activeClass);
851
+ }
852
+ tabs[cur].setAttribute('aria-selected', 'true');
853
+ tabs[cur].setAttribute('tabindex', '0');
854
+ tabs[cur].classList.add(activeClass);
855
+ panels[cur].setAttribute('aria-hidden', 'false');
856
+ panels[cur].classList.add(activeClass);
857
+ for (var k = 0; k < count; k++) {
858
+ (function(idx) {
859
+ tabs[idx].addEventListener('click', function() { if (activate(idx, cur)) tabs[idx].focus(); });
860
+ })(k);
861
+ }
862
+ (tablist || me).addEventListener('keydown', function(e) {
863
+ var nk = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';
864
+ var pk = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';
865
+ var next = null;
866
+ if (e.key === nk) { next = cur + 1; if (next >= count) next = wrap ? 0 : null; }
867
+ else if (e.key === pk) { next = cur - 1; if (next < 0) next = wrap ? count - 1 : null; }
868
+ else if (e.key === 'Home') next = 0;
869
+ else if (e.key === 'End') next = count - 1;
870
+ if (next !== null) { e.preventDefault(); if (activate(next, cur)) tabs[next].focus(); }
871
+ });
872
+ end
873
+ end
874
+ end`.trim()
656
875
  };
657
876
 
658
877
  // src/schemas/index.ts