@hyperfixi/behaviors 2.0.0 → 2.2.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 (189) hide show
  1. package/dist/all.browser.global.js +35 -6
  2. package/dist/all.browser.global.js.map +1 -1
  3. package/dist/autodismiss.browser.global.js +3 -0
  4. package/dist/autodismiss.browser.global.js.map +1 -0
  5. package/dist/behaviors/autodismiss.cjs +177 -0
  6. package/dist/behaviors/autodismiss.cjs.map +1 -0
  7. package/dist/behaviors/autodismiss.d.cts +15 -0
  8. package/dist/behaviors/autodismiss.d.ts +15 -0
  9. package/dist/behaviors/autodismiss.js +15 -0
  10. package/dist/behaviors/autodismiss.js.map +1 -0
  11. package/dist/behaviors/clickoutside.cjs +123 -0
  12. package/dist/behaviors/clickoutside.cjs.map +1 -0
  13. package/dist/behaviors/clickoutside.d.cts +15 -0
  14. package/dist/behaviors/clickoutside.d.ts +15 -0
  15. package/dist/behaviors/clickoutside.js +15 -0
  16. package/dist/behaviors/clickoutside.js.map +1 -0
  17. package/dist/behaviors/clipboard.cjs +193 -0
  18. package/dist/behaviors/clipboard.cjs.map +1 -0
  19. package/dist/behaviors/clipboard.d.cts +15 -0
  20. package/dist/behaviors/clipboard.d.ts +15 -0
  21. package/dist/behaviors/clipboard.js +15 -0
  22. package/dist/behaviors/clipboard.js.map +1 -0
  23. package/dist/behaviors/draggable.cjs +49 -7
  24. package/dist/behaviors/draggable.cjs.map +1 -1
  25. package/dist/behaviors/draggable.d.cts +2 -1
  26. package/dist/behaviors/draggable.d.ts +2 -1
  27. package/dist/behaviors/draggable.js +2 -1
  28. package/dist/behaviors/focustrap.cjs +191 -0
  29. package/dist/behaviors/focustrap.cjs.map +1 -0
  30. package/dist/behaviors/focustrap.d.cts +15 -0
  31. package/dist/behaviors/focustrap.d.ts +15 -0
  32. package/dist/behaviors/focustrap.js +15 -0
  33. package/dist/behaviors/focustrap.js.map +1 -0
  34. package/dist/behaviors/removable.cjs +9 -2
  35. package/dist/behaviors/removable.cjs.map +1 -1
  36. package/dist/behaviors/removable.d.cts +1 -1
  37. package/dist/behaviors/removable.d.ts +1 -1
  38. package/dist/behaviors/removable.js +2 -1
  39. package/dist/behaviors/resizable.cjs +54 -7
  40. package/dist/behaviors/resizable.cjs.map +1 -1
  41. package/dist/behaviors/resizable.d.cts +1 -1
  42. package/dist/behaviors/resizable.d.ts +1 -1
  43. package/dist/behaviors/resizable.js +2 -1
  44. package/dist/behaviors/scrollreveal.cjs +149 -0
  45. package/dist/behaviors/scrollreveal.cjs.map +1 -0
  46. package/dist/behaviors/scrollreveal.d.cts +15 -0
  47. package/dist/behaviors/scrollreveal.d.ts +15 -0
  48. package/dist/behaviors/scrollreveal.js +15 -0
  49. package/dist/behaviors/scrollreveal.js.map +1 -0
  50. package/dist/behaviors/sortable.cjs +49 -7
  51. package/dist/behaviors/sortable.cjs.map +1 -1
  52. package/dist/behaviors/sortable.d.cts +1 -1
  53. package/dist/behaviors/sortable.d.ts +1 -1
  54. package/dist/behaviors/sortable.js +2 -1
  55. package/dist/behaviors/tabs.cjs +247 -0
  56. package/dist/behaviors/tabs.cjs.map +1 -0
  57. package/dist/behaviors/tabs.d.cts +15 -0
  58. package/dist/behaviors/tabs.d.ts +15 -0
  59. package/dist/behaviors/tabs.js +15 -0
  60. package/dist/behaviors/tabs.js.map +1 -0
  61. package/dist/behaviors/toggleable.cjs +36 -7
  62. package/dist/behaviors/toggleable.cjs.map +1 -1
  63. package/dist/behaviors/toggleable.d.cts +2 -1
  64. package/dist/behaviors/toggleable.d.ts +2 -1
  65. package/dist/behaviors/toggleable.js +2 -1
  66. package/dist/chunk-244WSZOL.js +67 -0
  67. package/dist/chunk-244WSZOL.js.map +1 -0
  68. package/dist/chunk-2NSKQMNM.js +99 -0
  69. package/dist/chunk-2NSKQMNM.js.map +1 -0
  70. package/dist/chunk-64DIQGQI.js +37 -0
  71. package/dist/chunk-64DIQGQI.js.map +1 -0
  72. package/dist/chunk-6TJXDCAQ.js +11 -0
  73. package/dist/chunk-6TJXDCAQ.js.map +1 -0
  74. package/dist/chunk-7JDV2THA.js +62 -0
  75. package/dist/chunk-7JDV2THA.js.map +1 -0
  76. package/dist/chunk-AFGA54SA.js +116 -0
  77. package/dist/chunk-AFGA54SA.js.map +1 -0
  78. package/dist/chunk-DYHBKDKB.js +62 -0
  79. package/dist/chunk-DYHBKDKB.js.map +1 -0
  80. package/dist/chunk-GPTHAUWN.js +58 -0
  81. package/dist/chunk-GPTHAUWN.js.map +1 -0
  82. package/dist/chunk-GVJPA2EK.js +68 -0
  83. package/dist/chunk-GVJPA2EK.js.map +1 -0
  84. package/dist/chunk-H2MCHR5A.js +89 -0
  85. package/dist/chunk-H2MCHR5A.js.map +1 -0
  86. package/dist/{chunk-2NQHIHD5.js → chunk-H5Y5ARYG.js} +6 -3
  87. package/dist/chunk-H5Y5ARYG.js.map +1 -0
  88. package/dist/chunk-JKBWIJFI.js +155 -0
  89. package/dist/chunk-JKBWIJFI.js.map +1 -0
  90. package/dist/chunk-QKDYW4VM.js +70 -0
  91. package/dist/chunk-QKDYW4VM.js.map +1 -0
  92. package/dist/chunk-RB7OB6OD.js +51 -0
  93. package/dist/chunk-RB7OB6OD.js.map +1 -0
  94. package/dist/chunk-RPERQTFU.js +75 -0
  95. package/dist/chunk-RPERQTFU.js.map +1 -0
  96. package/dist/chunk-S4QDV2MH.js +75 -0
  97. package/dist/chunk-S4QDV2MH.js.map +1 -0
  98. package/dist/chunk-WTUVPVSD.js +80 -0
  99. package/dist/chunk-WTUVPVSD.js.map +1 -0
  100. package/dist/chunk-YHQRFUWV.js +62 -0
  101. package/dist/chunk-YHQRFUWV.js.map +1 -0
  102. package/dist/chunk-ZQQZTMCJ.js +64 -0
  103. package/dist/chunk-ZQQZTMCJ.js.map +1 -0
  104. package/dist/clickoutside.browser.global.js +12 -0
  105. package/dist/clickoutside.browser.global.js.map +1 -0
  106. package/dist/clipboard.browser.global.js +3 -0
  107. package/dist/clipboard.browser.global.js.map +1 -0
  108. package/dist/core.browser.global.js +3 -3
  109. package/dist/core.browser.global.js.map +1 -1
  110. package/dist/draggable.browser.global.js +2 -2
  111. package/dist/draggable.browser.global.js.map +1 -1
  112. package/dist/focustrap.browser.global.js +9 -0
  113. package/dist/focustrap.browser.global.js.map +1 -0
  114. package/dist/index.cjs +1270 -50
  115. package/dist/index.cjs.map +1 -1
  116. package/dist/index.d.cts +13 -7
  117. package/dist/index.d.ts +13 -7
  118. package/dist/index.js +155 -48
  119. package/dist/index.js.map +1 -1
  120. package/dist/loaders.cjs +1151 -30
  121. package/dist/loaders.cjs.map +1 -1
  122. package/dist/loaders.js +9 -3
  123. package/dist/registry.d.cts +1 -1
  124. package/dist/registry.d.ts +1 -1
  125. package/dist/removable.browser.global.js +2 -2
  126. package/dist/removable.browser.global.js.map +1 -1
  127. package/dist/resizable.browser.global.js +2 -2
  128. package/dist/resizable.browser.global.js.map +1 -1
  129. package/dist/schemas/index.cjs +343 -1
  130. package/dist/schemas/index.cjs.map +1 -1
  131. package/dist/schemas/index.d.cts +45 -3
  132. package/dist/schemas/index.d.ts +45 -3
  133. package/dist/schemas/index.js +34 -4
  134. package/dist/schemas/index.js.map +1 -1
  135. package/dist/scrollreveal.browser.global.js +11 -0
  136. package/dist/scrollreveal.browser.global.js.map +1 -0
  137. package/dist/sortable.browser.global.js +2 -2
  138. package/dist/sortable.browser.global.js.map +1 -1
  139. package/dist/tabs.browser.global.js +3 -0
  140. package/dist/tabs.browser.global.js.map +1 -0
  141. package/dist/toggleable.browser.global.js +2 -2
  142. package/dist/toggleable.browser.global.js.map +1 -1
  143. package/dist/{types-CCc_2wAb.d.cts → types-DCCtVQS5.d.cts} +1 -1
  144. package/dist/{types-CCc_2wAb.d.ts → types-DCCtVQS5.d.ts} +1 -1
  145. package/package.json +45 -2
  146. package/src/behaviors/autodismiss.test.ts +61 -0
  147. package/src/behaviors/autodismiss.ts +107 -0
  148. package/src/behaviors/clickoutside.test.ts +52 -0
  149. package/src/behaviors/clickoutside.ts +77 -0
  150. package/src/behaviors/clipboard.test.ts +67 -0
  151. package/src/behaviors/clipboard.ts +130 -0
  152. package/src/behaviors/draggable.ts +62 -15
  153. package/src/behaviors/focustrap.test.ts +59 -0
  154. package/src/behaviors/focustrap.ts +158 -0
  155. package/src/behaviors/index.ts +37 -0
  156. package/src/behaviors/integration.test.ts +120 -45
  157. package/src/behaviors/removable.ts +4 -5
  158. package/src/behaviors/resizable.ts +63 -14
  159. package/src/behaviors/scrollreveal.test.ts +59 -0
  160. package/src/behaviors/scrollreveal.ts +78 -0
  161. package/src/behaviors/sortable.ts +61 -14
  162. package/src/behaviors/tabs.test.ts +81 -0
  163. package/src/behaviors/tabs.ts +196 -0
  164. package/src/behaviors/toggleable.ts +45 -13
  165. package/src/generated/metadata.ts +38 -7
  166. package/src/generated/types.ts +5 -5
  167. package/src/index.test.ts +143 -5
  168. package/src/index.ts +65 -13
  169. package/src/loaders.ts +18 -0
  170. package/src/registry.test.ts +26 -5
  171. package/src/schemas/autodismiss.schema.ts +66 -0
  172. package/src/schemas/clickoutside.schema.ts +39 -0
  173. package/src/schemas/clipboard.schema.ts +72 -0
  174. package/src/schemas/focustrap.schema.ts +53 -0
  175. package/src/schemas/index.ts +18 -0
  176. package/src/schemas/scrollreveal.schema.ts +60 -0
  177. package/src/schemas/tabs.schema.ts +66 -0
  178. package/src/schemas/types.ts +13 -1
  179. package/dist/chunk-2NQHIHD5.js.map +0 -1
  180. package/dist/chunk-3Q652JQB.js +0 -37
  181. package/dist/chunk-3Q652JQB.js.map +0 -1
  182. package/dist/chunk-HMRPQWQU.js +0 -37
  183. package/dist/chunk-HMRPQWQU.js.map +0 -1
  184. package/dist/chunk-MEP6B7XW.js +0 -37
  185. package/dist/chunk-MEP6B7XW.js.map +0 -1
  186. package/dist/chunk-URBOGGW6.js +0 -32
  187. package/dist/chunk-URBOGGW6.js.map +0 -1
  188. package/dist/chunk-WU37DFAL.js +0 -37
  189. package/dist/chunk-WU37DFAL.js.map +0 -1
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Tabs Behavior — Imperative Implementation
3
+ *
4
+ * WAI-ARIA compliant tabs with roving tabindex keyboard navigation.
5
+ * Auto-wires role, aria-selected, aria-controls, and aria-labelledby.
6
+ */
7
+
8
+ import { tabsSchema } from '../schemas/tabs.schema';
9
+ import type { LokaScriptInstance } from '../schemas/types';
10
+ import { resolveRuntime } from '../schemas/types';
11
+
12
+ export const tabsSource = tabsSchema.source;
13
+ export const tabsMetadata = tabsSchema;
14
+
15
+ let idCounter = 0;
16
+
17
+ function ensureId(el: HTMLElement, prefix: string): string {
18
+ if (!el.id) {
19
+ el.id = `${prefix}-${++idCounter}`;
20
+ }
21
+ return el.id;
22
+ }
23
+
24
+ /**
25
+ * Imperative installer for Tabs behavior.
26
+ */
27
+ function installTabs(element: HTMLElement, params: Record<string, any>): void {
28
+ const orientation = params.orientation === 'vertical' ? 'vertical' : 'horizontal';
29
+ const wrap = params.wrap !== false;
30
+ const activeClass = (params.activeClass as string) || 'active';
31
+ const initialIndex = typeof params.activeTab === 'number' ? params.activeTab : 0;
32
+
33
+ // Discover tabs and panels
34
+ const tabs = Array.from(element.querySelectorAll<HTMLElement>('[role="tab"]'));
35
+ const panels = Array.from(element.querySelectorAll<HTMLElement>('[role="tabpanel"]'));
36
+
37
+ if (tabs.length === 0 || panels.length === 0) return;
38
+
39
+ // Find or mark the tablist
40
+ const tablist = element.querySelector<HTMLElement>('[role="tablist"]') || tabs[0].parentElement;
41
+ if (tablist && !tablist.getAttribute('role')) {
42
+ tablist.setAttribute('role', 'tablist');
43
+ }
44
+ if (tablist) {
45
+ tablist.setAttribute('aria-orientation', orientation);
46
+ }
47
+
48
+ // Wire IDs, aria-controls, aria-labelledby
49
+ const count = Math.min(tabs.length, panels.length);
50
+ for (let i = 0; i < count; i++) {
51
+ const tabId = ensureId(tabs[i], 'tab');
52
+ const panelId = ensureId(panels[i], 'tabpanel');
53
+ tabs[i].setAttribute('aria-controls', panelId);
54
+ panels[i].setAttribute('aria-labelledby', tabId);
55
+ }
56
+
57
+ let currentIndex = -1;
58
+
59
+ function selectTab(index: number, previousIndex: number): boolean {
60
+ if (index === previousIndex || index < 0 || index >= count) return false;
61
+
62
+ const detail = {
63
+ tab: tabs[index],
64
+ panel: panels[index],
65
+ index,
66
+ previousIndex,
67
+ };
68
+
69
+ // Cancelable before-event
70
+ const changeEvent = new CustomEvent('tabs:change', {
71
+ bubbles: true,
72
+ cancelable: true,
73
+ detail,
74
+ });
75
+ element.dispatchEvent(changeEvent);
76
+ if (changeEvent.defaultPrevented) return false;
77
+
78
+ // Deactivate previous
79
+ if (previousIndex >= 0 && previousIndex < count) {
80
+ tabs[previousIndex].setAttribute('aria-selected', 'false');
81
+ tabs[previousIndex].setAttribute('tabindex', '-1');
82
+ tabs[previousIndex].classList.remove(activeClass);
83
+ panels[previousIndex].setAttribute('aria-hidden', 'true');
84
+ panels[previousIndex].classList.remove(activeClass);
85
+ }
86
+
87
+ // Activate new
88
+ tabs[index].setAttribute('aria-selected', 'true');
89
+ tabs[index].setAttribute('tabindex', '0');
90
+ tabs[index].classList.add(activeClass);
91
+ panels[index].setAttribute('aria-hidden', 'false');
92
+ panels[index].classList.add(activeClass);
93
+
94
+ currentIndex = index;
95
+
96
+ element.dispatchEvent(new CustomEvent('tabs:changed', { bubbles: true, detail }));
97
+ return true;
98
+ }
99
+
100
+ // Initialize all tabs as inactive, then activate initial
101
+ for (let i = 0; i < count; i++) {
102
+ tabs[i].setAttribute('aria-selected', 'false');
103
+ tabs[i].setAttribute('tabindex', '-1');
104
+ tabs[i].classList.remove(activeClass);
105
+ panels[i].setAttribute('aria-hidden', 'true');
106
+ panels[i].classList.remove(activeClass);
107
+ }
108
+
109
+ const clamped = Math.max(0, Math.min(initialIndex, count - 1));
110
+ // Directly activate without firing change event on init
111
+ tabs[clamped].setAttribute('aria-selected', 'true');
112
+ tabs[clamped].setAttribute('tabindex', '0');
113
+ tabs[clamped].classList.add(activeClass);
114
+ panels[clamped].setAttribute('aria-hidden', 'false');
115
+ panels[clamped].classList.add(activeClass);
116
+ currentIndex = clamped;
117
+
118
+ // Click handler
119
+ for (let i = 0; i < count; i++) {
120
+ tabs[i].addEventListener('click', () => {
121
+ selectTab(i, currentIndex);
122
+ tabs[i].focus();
123
+ });
124
+ }
125
+
126
+ // Keyboard handler on tablist
127
+ const keyTarget = tablist || element;
128
+ keyTarget.addEventListener('keydown', (e: KeyboardEvent) => {
129
+ const isHorizontal = orientation === 'horizontal';
130
+ const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';
131
+ const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';
132
+
133
+ let nextIndex: number | null = null;
134
+
135
+ if (e.key === nextKey) {
136
+ nextIndex = currentIndex + 1;
137
+ if (nextIndex >= count) nextIndex = wrap ? 0 : null;
138
+ } else if (e.key === prevKey) {
139
+ nextIndex = currentIndex - 1;
140
+ if (nextIndex < 0) nextIndex = wrap ? count - 1 : null;
141
+ } else if (e.key === 'Home') {
142
+ nextIndex = 0;
143
+ } else if (e.key === 'End') {
144
+ nextIndex = count - 1;
145
+ }
146
+
147
+ if (nextIndex !== null) {
148
+ e.preventDefault();
149
+ if (selectTab(nextIndex, currentIndex)) {
150
+ tabs[nextIndex].focus();
151
+ }
152
+ }
153
+ });
154
+
155
+ // Programmatic tab selection
156
+ element.addEventListener('tabs:select', ((e: CustomEvent) => {
157
+ const index = e.detail?.index;
158
+ if (typeof index === 'number') {
159
+ selectTab(index, currentIndex);
160
+ }
161
+ }) as EventListener);
162
+ }
163
+
164
+ /**
165
+ * Register the Tabs behavior with LokaScript.
166
+ */
167
+ export async function registerTabs(hyperfixi?: LokaScriptInstance): Promise<void> {
168
+ const hf = hyperfixi || resolveRuntime();
169
+
170
+ if (!hf) {
171
+ throw new Error(
172
+ 'LokaScript not found. Make sure @hyperfixi/core is loaded before registering behaviors.'
173
+ );
174
+ }
175
+
176
+ const syntheticNode = {
177
+ type: 'behavior',
178
+ name: 'Tabs',
179
+ parameters: ['orientation', 'activeTab', 'wrap', 'activeClass'],
180
+ eventHandlers: [],
181
+ imperativeInstaller: installTabs,
182
+ };
183
+ const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };
184
+ await hf.execute(syntheticNode, ctx);
185
+ }
186
+
187
+ // Auto-register when loaded as a script tag
188
+ if (resolveRuntime()) {
189
+ registerTabs().catch(console.error);
190
+ }
191
+
192
+ export default {
193
+ source: tabsSchema.source,
194
+ metadata: tabsSchema,
195
+ register: registerTabs,
196
+ };
@@ -1,9 +1,13 @@
1
1
  /**
2
- * Toggleable Behavior
2
+ * Toggleable Behavior — Imperative Implementation
3
3
  *
4
4
  * A behavior that toggles a CSS class on click.
5
5
  * Useful for accordions, dropdowns, and toggle buttons.
6
6
  *
7
+ * Uses direct DOM APIs instead of compiling hyperscript source,
8
+ * because `toggle .{cls}` template interpolation doesn't resolve
9
+ * behavior parameters at runtime.
10
+ *
7
11
  * @example
8
12
  * ```html
9
13
  * <button _="install Toggleable">Toggle</button>
@@ -13,19 +17,46 @@
13
17
  */
14
18
 
15
19
  import { toggleableSchema } from '../schemas/toggleable.schema';
16
- import type { LokaScriptInstance, LokaScriptWindow } from '../schemas/types';
20
+ import type { LokaScriptInstance } from '../schemas/types';
21
+ import { resolveRuntime } from '../schemas/types';
17
22
 
18
23
  // Re-export schema-derived values for backwards compatibility
19
24
  export const toggleableSource = toggleableSchema.source;
20
25
  export const toggleableMetadata = toggleableSchema;
21
26
 
27
+ /**
28
+ * Imperative installer for Toggleable behavior.
29
+ * Called directly by the runtime when `install Toggleable` is executed.
30
+ */
31
+ function installToggleable(element: HTMLElement, params: Record<string, any>): void {
32
+ const cls = params.cls || 'active';
33
+
34
+ // Resolve target element
35
+ let targetEl: HTMLElement = element;
36
+ if (params.target && params.target !== 'me') {
37
+ if (typeof params.target === 'string') {
38
+ const found = document.querySelector(params.target);
39
+ if (found instanceof HTMLElement) targetEl = found;
40
+ } else if (params.target instanceof HTMLElement) {
41
+ targetEl = params.target;
42
+ }
43
+ }
44
+
45
+ element.addEventListener('click', () => {
46
+ const wasOn = targetEl.classList.contains(cls);
47
+ targetEl.classList.toggle(cls);
48
+ targetEl.dispatchEvent(
49
+ new CustomEvent(wasOn ? 'toggleable:off' : 'toggleable:on', { bubbles: true })
50
+ );
51
+ });
52
+ }
53
+
22
54
  /**
23
55
  * Register the Toggleable behavior with LokaScript.
56
+ * Uses imperative installer via synthetic behavior node.
24
57
  */
25
58
  export async function registerToggleable(hyperfixi?: LokaScriptInstance): Promise<void> {
26
- const hf =
27
- hyperfixi ||
28
- (typeof window !== 'undefined' ? (window as unknown as LokaScriptWindow).lokascript : null);
59
+ const hf = hyperfixi || resolveRuntime();
29
60
 
30
61
  if (!hf) {
31
62
  throw new Error(
@@ -33,18 +64,19 @@ export async function registerToggleable(hyperfixi?: LokaScriptInstance): Promis
33
64
  );
34
65
  }
35
66
 
36
- const result = hf.compileSync(toggleableSchema.source, { traditional: true });
37
-
38
- if (!result.ok) {
39
- throw new Error(`Failed to compile Toggleable behavior: ${JSON.stringify(result.errors)}`);
40
- }
41
-
67
+ const syntheticNode = {
68
+ type: 'behavior',
69
+ name: 'Toggleable',
70
+ parameters: ['cls', 'target'],
71
+ eventHandlers: [],
72
+ imperativeInstaller: installToggleable,
73
+ };
42
74
  const ctx = hf.createContext ? hf.createContext() : { locals: new Map(), globals: new Map() };
43
- await hf.execute(result.ast, ctx);
75
+ await hf.execute(syntheticNode, ctx);
44
76
  }
45
77
 
46
78
  // Auto-register when loaded as a script tag
47
- if (typeof window !== 'undefined' && (window as unknown as LokaScriptWindow).lokascript) {
79
+ if (resolveRuntime()) {
48
80
  registerToggleable().catch(console.error);
49
81
  }
50
82
 
@@ -16,7 +16,13 @@ export const ALL_BEHAVIOR_NAMES: BehaviorName[] = [
16
16
  "Sortable",
17
17
  "Resizable",
18
18
  "Removable",
19
- "Toggleable"
19
+ "Toggleable",
20
+ "Clipboard",
21
+ "AutoDismiss",
22
+ "ClickOutside",
23
+ "FocusTrap",
24
+ "ScrollReveal",
25
+ "Tabs"
20
26
  ];
21
27
 
22
28
  /**
@@ -26,7 +32,12 @@ export const BEHAVIORS_BY_CATEGORY: Record<BehaviorCategory, BehaviorName[]> = {
26
32
  "ui": [
27
33
  "Draggable",
28
34
  "Sortable",
29
- "Resizable"
35
+ "Resizable",
36
+ "Clipboard",
37
+ "AutoDismiss",
38
+ "ClickOutside",
39
+ "FocusTrap",
40
+ "Tabs"
30
41
  ],
31
42
  "data": [
32
43
  "Removable"
@@ -35,7 +46,9 @@ export const BEHAVIORS_BY_CATEGORY: Record<BehaviorCategory, BehaviorName[]> = {
35
46
  "form": [
36
47
  "Toggleable"
37
48
  ],
38
- "layout": []
49
+ "layout": [
50
+ "ScrollReveal"
51
+ ]
39
52
  };
40
53
 
41
54
  /**
@@ -44,11 +57,17 @@ export const BEHAVIORS_BY_CATEGORY: Record<BehaviorCategory, BehaviorName[]> = {
44
57
  export const BEHAVIORS_BY_TIER: Record<BehaviorTier, BehaviorName[]> = {
45
58
  "core": [
46
59
  "Draggable",
47
- "Toggleable"
60
+ "Toggleable",
61
+ "Clipboard",
62
+ "AutoDismiss",
63
+ "ClickOutside",
64
+ "FocusTrap",
65
+ "Tabs"
48
66
  ],
49
67
  "common": [
50
68
  "Sortable",
51
- "Removable"
69
+ "Removable",
70
+ "ScrollReveal"
52
71
  ],
53
72
  "optional": [
54
73
  "Resizable"
@@ -63,7 +82,13 @@ export const BEHAVIOR_CATEGORIES: Record<BehaviorName, BehaviorCategory> = {
63
82
  "Sortable": "ui",
64
83
  "Resizable": "ui",
65
84
  "Removable": "data",
66
- "Toggleable": "form"
85
+ "Toggleable": "form",
86
+ "Clipboard": "ui",
87
+ "AutoDismiss": "ui",
88
+ "ClickOutside": "ui",
89
+ "FocusTrap": "ui",
90
+ "ScrollReveal": "layout",
91
+ "Tabs": "ui"
67
92
  };
68
93
 
69
94
  /**
@@ -74,5 +99,11 @@ export const BEHAVIOR_TIERS: Record<BehaviorName, BehaviorTier> = {
74
99
  "Sortable": "common",
75
100
  "Resizable": "optional",
76
101
  "Removable": "common",
77
- "Toggleable": "core"
102
+ "Toggleable": "core",
103
+ "Clipboard": "core",
104
+ "AutoDismiss": "core",
105
+ "ClickOutside": "core",
106
+ "FocusTrap": "core",
107
+ "ScrollReveal": "common",
108
+ "Tabs": "core"
78
109
  };
@@ -8,12 +8,12 @@
8
8
  /**
9
9
  * Union of all behavior names.
10
10
  */
11
- export type BehaviorName = 'Draggable' | 'Sortable' | 'Resizable' | 'Removable' | 'Toggleable';
11
+ export type BehaviorName = 'Draggable' | 'Sortable' | 'Resizable' | 'Removable' | 'Toggleable' | 'Clipboard' | 'AutoDismiss' | 'ClickOutside' | 'FocusTrap' | 'ScrollReveal' | 'Tabs';
12
12
 
13
13
  /**
14
14
  * UI category behaviors.
15
15
  */
16
- export type UIBehavior = 'Draggable' | 'Sortable' | 'Resizable';
16
+ export type UIBehavior = 'Draggable' | 'Sortable' | 'Resizable' | 'Clipboard' | 'AutoDismiss' | 'ClickOutside' | 'FocusTrap' | 'Tabs';
17
17
 
18
18
  /**
19
19
  * Data category behaviors.
@@ -33,17 +33,17 @@ export type FormBehavior = 'Toggleable';
33
33
  /**
34
34
  * Layout category behaviors.
35
35
  */
36
- export type LayoutBehavior = never;
36
+ export type LayoutBehavior = 'ScrollReveal';
37
37
 
38
38
  /**
39
39
  * Core tier behaviors (always loaded).
40
40
  */
41
- export type CoreBehavior = 'Draggable' | 'Toggleable';
41
+ export type CoreBehavior = 'Draggable' | 'Toggleable' | 'Clipboard' | 'AutoDismiss' | 'ClickOutside' | 'FocusTrap' | 'Tabs';
42
42
 
43
43
  /**
44
44
  * Common tier behaviors (high usage).
45
45
  */
46
- export type CommonBehavior = 'Sortable' | 'Removable';
46
+ export type CommonBehavior = 'Sortable' | 'Removable' | 'ScrollReveal';
47
47
 
48
48
  /**
49
49
  * Optional tier behaviors (load on demand).
package/src/index.test.ts CHANGED
@@ -11,6 +11,18 @@ import {
11
11
  sortableMetadata,
12
12
  resizableSource,
13
13
  resizableMetadata,
14
+ clipboardSource,
15
+ clipboardMetadata,
16
+ autoDismissSource,
17
+ autoDismissMetadata,
18
+ clickOutsideSource,
19
+ clickOutsideMetadata,
20
+ focusTrapSource,
21
+ focusTrapMetadata,
22
+ scrollRevealSource,
23
+ scrollRevealMetadata,
24
+ tabsSource,
25
+ tabsMetadata,
14
26
  // Registry functions
15
27
  getAvailableBehaviors,
16
28
  getBehaviorsByCategory,
@@ -22,6 +34,12 @@ import {
22
34
  registerToggleable,
23
35
  registerSortable,
24
36
  registerResizable,
37
+ registerClipboard,
38
+ registerAutoDismiss,
39
+ registerClickOutside,
40
+ registerFocusTrap,
41
+ registerScrollReveal,
42
+ registerTabs,
25
43
  registerAll,
26
44
  } from './index';
27
45
 
@@ -33,6 +51,12 @@ describe('@hyperfixi/behaviors', () => {
33
51
  expect(toggleableSource).toBeDefined();
34
52
  expect(sortableSource).toBeDefined();
35
53
  expect(resizableSource).toBeDefined();
54
+ expect(clipboardSource).toBeDefined();
55
+ expect(autoDismissSource).toBeDefined();
56
+ expect(clickOutsideSource).toBeDefined();
57
+ expect(focusTrapSource).toBeDefined();
58
+ expect(scrollRevealSource).toBeDefined();
59
+ expect(tabsSource).toBeDefined();
36
60
  });
37
61
 
38
62
  it('should export all behavior metadata', () => {
@@ -41,6 +65,12 @@ describe('@hyperfixi/behaviors', () => {
41
65
  expect(toggleableMetadata.name).toBe('Toggleable');
42
66
  expect(sortableMetadata.name).toBe('Sortable');
43
67
  expect(resizableMetadata.name).toBe('Resizable');
68
+ expect(clipboardMetadata.name).toBe('Clipboard');
69
+ expect(autoDismissMetadata.name).toBe('AutoDismiss');
70
+ expect(clickOutsideMetadata.name).toBe('ClickOutside');
71
+ expect(focusTrapMetadata.name).toBe('FocusTrap');
72
+ expect(scrollRevealMetadata.name).toBe('ScrollReveal');
73
+ expect(tabsMetadata.name).toBe('Tabs');
44
74
  });
45
75
 
46
76
  it('should export register functions', () => {
@@ -49,6 +79,12 @@ describe('@hyperfixi/behaviors', () => {
49
79
  expect(registerToggleable).toBeDefined();
50
80
  expect(registerSortable).toBeDefined();
51
81
  expect(registerResizable).toBeDefined();
82
+ expect(registerClipboard).toBeDefined();
83
+ expect(registerAutoDismiss).toBeDefined();
84
+ expect(registerClickOutside).toBeDefined();
85
+ expect(registerFocusTrap).toBeDefined();
86
+ expect(registerScrollReveal).toBeDefined();
87
+ expect(registerTabs).toBeDefined();
52
88
  expect(registerAll).toBeDefined();
53
89
  });
54
90
  });
@@ -61,29 +97,60 @@ describe('@hyperfixi/behaviors', () => {
61
97
  expect(names).toContain('Toggleable');
62
98
  expect(names).toContain('Sortable');
63
99
  expect(names).toContain('Resizable');
64
- expect(names.length).toBe(5);
100
+ expect(names).toContain('Clipboard');
101
+ expect(names).toContain('AutoDismiss');
102
+ expect(names).toContain('ClickOutside');
103
+ expect(names).toContain('FocusTrap');
104
+ expect(names).toContain('ScrollReveal');
105
+ expect(names).toContain('Tabs');
106
+ expect(names.length).toBe(11);
65
107
  });
66
108
 
67
109
  it('getBehaviorsByCategory should group by category', () => {
68
- expect(getBehaviorsByCategory('ui')).toEqual(['Draggable', 'Sortable', 'Resizable']);
110
+ const ui = getBehaviorsByCategory('ui');
111
+ expect(ui).toContain('Draggable');
112
+ expect(ui).toContain('Sortable');
113
+ expect(ui).toContain('Resizable');
114
+ expect(ui).toContain('Clipboard');
115
+ expect(ui).toContain('AutoDismiss');
116
+ expect(ui).toContain('ClickOutside');
117
+ expect(ui).toContain('FocusTrap');
118
+ expect(ui).toContain('Tabs');
69
119
  expect(getBehaviorsByCategory('data')).toEqual(['Removable']);
70
120
  expect(getBehaviorsByCategory('form')).toEqual(['Toggleable']);
121
+ expect(getBehaviorsByCategory('layout')).toEqual(['ScrollReveal']);
71
122
  });
72
123
 
73
124
  it('getBehaviorsByTier should group by tier', () => {
74
- expect(getBehaviorsByTier('core')).toEqual(['Draggable', 'Toggleable']);
75
- expect(getBehaviorsByTier('common')).toEqual(['Sortable', 'Removable']);
125
+ const core = getBehaviorsByTier('core');
126
+ expect(core).toContain('Draggable');
127
+ expect(core).toContain('Toggleable');
128
+ expect(core).toContain('Clipboard');
129
+ expect(core).toContain('AutoDismiss');
130
+ expect(core).toContain('ClickOutside');
131
+ expect(core).toContain('FocusTrap');
132
+ expect(core).toContain('Tabs');
133
+ const common = getBehaviorsByTier('common');
134
+ expect(common).toContain('Sortable');
135
+ expect(common).toContain('Removable');
136
+ expect(common).toContain('ScrollReveal');
76
137
  expect(getBehaviorsByTier('optional')).toEqual(['Resizable']);
77
138
  });
78
139
 
79
140
  it('getAllSchemas should return all schemas', () => {
80
141
  const schemas = getAllSchemas();
81
- expect(schemas.length).toBe(5);
142
+ expect(schemas.length).toBe(11);
82
143
  expect(schemas.map(s => s.name).sort()).toEqual([
144
+ 'AutoDismiss',
145
+ 'ClickOutside',
146
+ 'Clipboard',
83
147
  'Draggable',
148
+ 'FocusTrap',
84
149
  'Removable',
85
150
  'Resizable',
151
+ 'ScrollReveal',
86
152
  'Sortable',
153
+ 'Tabs',
87
154
  'Toggleable',
88
155
  ]);
89
156
  });
@@ -177,4 +244,75 @@ describe('@hyperfixi/behaviors', () => {
177
244
  expect(resizableSource).toContain('maxWidth');
178
245
  });
179
246
  });
247
+
248
+ describe('Clipboard behavior', () => {
249
+ it('should have valid hyperscript source', () => {
250
+ expect(clipboardSource).toContain('behavior Clipboard');
251
+ expect(clipboardSource).toContain('clipboard:copied');
252
+ });
253
+
254
+ it('should have correct schema metadata', () => {
255
+ expect(clipboardMetadata.category).toBe('ui');
256
+ expect(clipboardMetadata.tier).toBe('core');
257
+ });
258
+ });
259
+
260
+ describe('AutoDismiss behavior', () => {
261
+ it('should have valid hyperscript source', () => {
262
+ expect(autoDismissSource).toContain('behavior AutoDismiss');
263
+ expect(autoDismissSource).toContain('autodismiss:dismissed');
264
+ });
265
+
266
+ it('should have correct schema metadata', () => {
267
+ expect(autoDismissMetadata.category).toBe('ui');
268
+ expect(autoDismissMetadata.tier).toBe('core');
269
+ });
270
+ });
271
+
272
+ describe('ClickOutside behavior', () => {
273
+ it('should have valid hyperscript source', () => {
274
+ expect(clickOutsideSource).toContain('behavior ClickOutside');
275
+ expect(clickOutsideSource).toContain('pointerdown');
276
+ });
277
+
278
+ it('should have correct schema metadata', () => {
279
+ expect(clickOutsideMetadata.category).toBe('ui');
280
+ expect(clickOutsideMetadata.tier).toBe('core');
281
+ });
282
+ });
283
+
284
+ describe('FocusTrap behavior', () => {
285
+ it('should have valid hyperscript source', () => {
286
+ expect(focusTrapSource).toContain('behavior FocusTrap');
287
+ });
288
+
289
+ it('should have correct schema metadata', () => {
290
+ expect(focusTrapMetadata.category).toBe('ui');
291
+ expect(focusTrapMetadata.tier).toBe('core');
292
+ });
293
+ });
294
+
295
+ describe('ScrollReveal behavior', () => {
296
+ it('should have valid hyperscript source', () => {
297
+ expect(scrollRevealSource).toContain('behavior ScrollReveal');
298
+ expect(scrollRevealSource).toContain('IntersectionObserver');
299
+ });
300
+
301
+ it('should have correct schema metadata', () => {
302
+ expect(scrollRevealMetadata.category).toBe('layout');
303
+ expect(scrollRevealMetadata.tier).toBe('common');
304
+ });
305
+ });
306
+
307
+ describe('Tabs behavior', () => {
308
+ it('should have valid hyperscript source', () => {
309
+ expect(tabsSource).toContain('behavior Tabs');
310
+ expect(tabsSource).toContain('orientation');
311
+ });
312
+
313
+ it('should have correct schema metadata', () => {
314
+ expect(tabsMetadata.category).toBe('ui');
315
+ expect(tabsMetadata.tier).toBe('core');
316
+ });
317
+ });
180
318
  });