what-core 0.6.2 → 0.6.3

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 (50) hide show
  1. package/README.md +2 -0
  2. package/compiler.d.ts +30 -0
  3. package/devtools.d.ts +2 -0
  4. package/dist/compiler.js +1787 -0
  5. package/dist/compiler.js.map +7 -0
  6. package/dist/compiler.min.js +2 -0
  7. package/dist/compiler.min.js.map +7 -0
  8. package/dist/devtools.js +10 -0
  9. package/dist/devtools.js.map +7 -0
  10. package/dist/devtools.min.js +2 -0
  11. package/dist/devtools.min.js.map +7 -0
  12. package/dist/index.js +330 -382
  13. package/dist/index.js.map +4 -4
  14. package/dist/index.min.js +62 -62
  15. package/dist/index.min.js.map +4 -4
  16. package/dist/render.js +262 -21
  17. package/dist/render.js.map +4 -4
  18. package/dist/render.min.js +58 -1
  19. package/dist/render.min.js.map +4 -4
  20. package/dist/testing.js +3 -0
  21. package/dist/testing.js.map +2 -2
  22. package/dist/testing.min.js +1 -1
  23. package/dist/testing.min.js.map +2 -2
  24. package/index.d.ts +176 -1
  25. package/jsx-runtime.d.ts +622 -0
  26. package/package.json +20 -2
  27. package/src/agent-context.js +1 -1
  28. package/src/compiler.js +18 -0
  29. package/src/components.js +73 -27
  30. package/src/devtools.js +4 -0
  31. package/src/dom.js +7 -0
  32. package/src/guardrails.js +3 -4
  33. package/src/hooks.js +0 -11
  34. package/src/index.js +5 -9
  35. package/src/render.js +91 -24
  36. package/dist/a11y.js +0 -440
  37. package/dist/animation.js +0 -548
  38. package/dist/components.js +0 -229
  39. package/dist/data.js +0 -638
  40. package/dist/dom.js +0 -439
  41. package/dist/form.js +0 -509
  42. package/dist/h.js +0 -152
  43. package/dist/head.js +0 -51
  44. package/dist/helpers.js +0 -140
  45. package/dist/hooks.js +0 -210
  46. package/dist/reactive.js +0 -432
  47. package/dist/scheduler.js +0 -246
  48. package/dist/skeleton.js +0 -363
  49. package/dist/store.js +0 -83
  50. package/dist/what.js +0 -117
package/dist/a11y.js DELETED
@@ -1,440 +0,0 @@
1
- // What Framework - Accessibility Utilities
2
- // Focus management, ARIA helpers, screen reader announcements
3
-
4
- import { signal, effect } from './reactive.js';
5
- import { h } from './h.js';
6
- import { getCurrentComponent } from './dom.js';
7
-
8
- // --- Focus Management ---
9
-
10
- // Track currently focused element
11
- const focusedElement = signal(null);
12
-
13
- if (typeof document !== 'undefined') {
14
- document.addEventListener('focusin', (e) => {
15
- focusedElement.set(e.target);
16
- });
17
- }
18
-
19
- export function useFocus() {
20
- return {
21
- current: () => focusedElement(),
22
- focus: (element) => element?.focus(),
23
- blur: () => document.activeElement?.blur(),
24
- };
25
- }
26
-
27
- // --- Focus Trap ---
28
- // Keep focus within a container (for modals, dialogs, etc.)
29
-
30
- export function useFocusTrap(containerRef) {
31
- let previousFocus = null;
32
-
33
- function activate() {
34
- if (typeof document === 'undefined') return;
35
-
36
- previousFocus = document.activeElement;
37
- const container = containerRef.current || containerRef;
38
-
39
- if (!container) return;
40
-
41
- // Find all focusable elements
42
- const focusables = getFocusableElements(container);
43
- if (focusables.length === 0) return;
44
-
45
- // Focus first element
46
- focusables[0].focus();
47
-
48
- // Handle Tab key
49
- function handleKeydown(e) {
50
- if (e.key !== 'Tab') return;
51
-
52
- const focusables = getFocusableElements(container);
53
- const first = focusables[0];
54
- const last = focusables[focusables.length - 1];
55
-
56
- if (e.shiftKey) {
57
- // Shift+Tab: if on first, go to last
58
- if (document.activeElement === first) {
59
- e.preventDefault();
60
- last.focus();
61
- }
62
- } else {
63
- // Tab: if on last, go to first
64
- if (document.activeElement === last) {
65
- e.preventDefault();
66
- first.focus();
67
- }
68
- }
69
- }
70
-
71
- container.addEventListener('keydown', handleKeydown);
72
-
73
- return () => {
74
- container.removeEventListener('keydown', handleKeydown);
75
- };
76
- }
77
-
78
- function deactivate() {
79
- if (previousFocus && typeof previousFocus.focus === 'function') {
80
- previousFocus.focus();
81
- }
82
- }
83
-
84
- return { activate, deactivate };
85
- }
86
-
87
- function getFocusableElements(container) {
88
- const selector = [
89
- 'button:not([disabled])',
90
- 'a[href]',
91
- 'input:not([disabled])',
92
- 'select:not([disabled])',
93
- 'textarea:not([disabled])',
94
- '[tabindex]:not([tabindex="-1"])',
95
- ].join(',');
96
-
97
- return Array.from(container.querySelectorAll(selector)).filter(el => {
98
- return el.offsetParent !== null; // Visible
99
- });
100
- }
101
-
102
- // --- Focus Scope ---
103
- // Component wrapper that traps focus
104
-
105
- export function FocusTrap({ children, active = true }) {
106
- const containerRef = { current: null };
107
- const trap = useFocusTrap(containerRef);
108
-
109
- // Scope the effect to the component lifecycle so it disposes on unmount
110
- const dispose = effect(() => {
111
- if (active) {
112
- const cleanup = trap.activate();
113
- return () => {
114
- cleanup?.();
115
- trap.deactivate();
116
- };
117
- }
118
- });
119
-
120
- // Register cleanup on component context
121
- const ctx = getCurrentComponent?.();
122
- if (ctx) {
123
- ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
124
- ctx._cleanupCallbacks.push(dispose);
125
- }
126
-
127
- return h('div', { ref: containerRef }, children);
128
- }
129
-
130
- // --- Screen Reader Announcements ---
131
-
132
- let announcer = null;
133
- let announcerId = 0;
134
-
135
- function getAnnouncer() {
136
- if (typeof document === 'undefined') return null;
137
-
138
- if (!announcer) {
139
- announcer = document.createElement('div');
140
- announcer.id = 'what-announcer';
141
- announcer.setAttribute('aria-live', 'polite');
142
- announcer.setAttribute('aria-atomic', 'true');
143
- announcer.style.cssText = `
144
- position: absolute;
145
- width: 1px;
146
- height: 1px;
147
- padding: 0;
148
- margin: -1px;
149
- overflow: hidden;
150
- clip: rect(0, 0, 0, 0);
151
- white-space: nowrap;
152
- border: 0;
153
- `;
154
- document.body.appendChild(announcer);
155
- }
156
- return announcer;
157
- }
158
-
159
- export function announce(message, options = {}) {
160
- const { priority = 'polite', timeout = 1000 } = options;
161
- const announcer = getAnnouncer();
162
- if (!announcer) return;
163
-
164
- announcer.setAttribute('aria-live', priority);
165
-
166
- // Clear and re-announce (required for some screen readers)
167
- const id = ++announcerId;
168
- announcer.textContent = '';
169
-
170
- requestAnimationFrame(() => {
171
- if (announcerId === id) {
172
- announcer.textContent = message;
173
- }
174
- });
175
-
176
- // Clear after timeout
177
- setTimeout(() => {
178
- if (announcerId === id) {
179
- announcer.textContent = '';
180
- }
181
- }, timeout);
182
- }
183
-
184
- export function announceAssertive(message) {
185
- return announce(message, { priority: 'assertive' });
186
- }
187
-
188
- // --- Skip Link ---
189
- // Accessible skip navigation
190
-
191
- export function SkipLink({ href = '#main', children = 'Skip to content' }) {
192
- return h('a', {
193
- href,
194
- class: 'what-skip-link',
195
- onClick: (e) => {
196
- e.preventDefault();
197
- const target = document.querySelector(href);
198
- if (target) {
199
- target.focus();
200
- target.scrollIntoView();
201
- }
202
- },
203
- style: {
204
- position: 'absolute',
205
- top: '-40px',
206
- left: '0',
207
- padding: '8px',
208
- background: '#000',
209
- color: '#fff',
210
- textDecoration: 'none',
211
- zIndex: '10000',
212
- },
213
- onFocus: (e) => {
214
- e.target.style.top = '0';
215
- },
216
- onBlur: (e) => {
217
- e.target.style.top = '-40px';
218
- },
219
- }, children);
220
- }
221
-
222
- // --- ARIA Helpers ---
223
-
224
- export function useAriaExpanded(initialExpanded = false) {
225
- const expanded = signal(initialExpanded);
226
-
227
- return {
228
- expanded: () => expanded(),
229
- toggle: () => expanded.set(!expanded.peek()),
230
- open: () => expanded.set(true),
231
- close: () => expanded.set(false),
232
- buttonProps: () => ({
233
- 'aria-expanded': expanded(),
234
- onClick: () => expanded.set(!expanded.peek()),
235
- }),
236
- panelProps: () => ({
237
- hidden: !expanded(),
238
- }),
239
- };
240
- }
241
-
242
- export function useAriaSelected(initialSelected = null) {
243
- const selected = signal(initialSelected);
244
-
245
- return {
246
- selected: () => selected(),
247
- select: (value) => selected.set(value),
248
- isSelected: (value) => selected() === value,
249
- itemProps: (value) => ({
250
- 'aria-selected': selected() === value,
251
- onClick: () => selected.set(value),
252
- }),
253
- };
254
- }
255
-
256
- export function useAriaChecked(initialChecked = false) {
257
- const checked = signal(initialChecked);
258
-
259
- return {
260
- checked: () => checked(),
261
- toggle: () => checked.set(!checked.peek()),
262
- set: (value) => checked.set(value),
263
- checkboxProps: () => ({
264
- role: 'checkbox',
265
- 'aria-checked': checked(),
266
- tabIndex: 0,
267
- onClick: () => checked.set(!checked.peek()),
268
- onKeyDown: (e) => {
269
- if (e.key === ' ' || e.key === 'Enter') {
270
- e.preventDefault();
271
- checked.set(!checked.peek());
272
- }
273
- },
274
- }),
275
- };
276
- }
277
-
278
- // --- Roving Tab Index ---
279
- // For keyboard navigation in lists, toolbars, etc.
280
-
281
- export function useRovingTabIndex(itemCountOrSignal) {
282
- // Accept either a static number or a signal/getter for dynamic lists
283
- const getCount = typeof itemCountOrSignal === 'function'
284
- ? itemCountOrSignal
285
- : () => itemCountOrSignal;
286
- const focusIndex = signal(0);
287
-
288
- function handleKeyDown(e) {
289
- const count = getCount();
290
- if (count <= 0) return;
291
- switch (e.key) {
292
- case 'ArrowDown':
293
- case 'ArrowRight':
294
- e.preventDefault();
295
- focusIndex.set((focusIndex.peek() + 1) % count);
296
- break;
297
- case 'ArrowUp':
298
- case 'ArrowLeft':
299
- e.preventDefault();
300
- focusIndex.set((focusIndex.peek() - 1 + count) % count);
301
- break;
302
- case 'Home':
303
- e.preventDefault();
304
- focusIndex.set(0);
305
- break;
306
- case 'End':
307
- e.preventDefault();
308
- focusIndex.set(count - 1);
309
- break;
310
- }
311
- }
312
-
313
- return {
314
- focusIndex: () => focusIndex(),
315
- setFocusIndex: (i) => focusIndex.set(i),
316
- getItemProps: (index) => ({
317
- tabIndex: focusIndex() === index ? 0 : -1,
318
- onKeyDown: handleKeyDown,
319
- onFocus: () => focusIndex.set(index),
320
- }),
321
- containerProps: () => ({
322
- role: 'listbox',
323
- }),
324
- };
325
- }
326
-
327
- // --- Visually Hidden ---
328
- // Hide content visually but keep accessible to screen readers
329
-
330
- export function VisuallyHidden({ children, as = 'span' }) {
331
- return h(as, {
332
- style: {
333
- position: 'absolute',
334
- width: '1px',
335
- height: '1px',
336
- padding: '0',
337
- margin: '-1px',
338
- overflow: 'hidden',
339
- clip: 'rect(0, 0, 0, 0)',
340
- whiteSpace: 'nowrap',
341
- border: '0',
342
- },
343
- }, children);
344
- }
345
-
346
- // --- Live Region Component ---
347
-
348
- export function LiveRegion({ children, priority = 'polite', atomic = true }) {
349
- return h('div', {
350
- 'aria-live': priority,
351
- 'aria-atomic': atomic,
352
- }, children);
353
- }
354
-
355
- // --- ID Generator ---
356
- // Generate unique IDs for ARIA attributes
357
-
358
- let idCounter = 0;
359
-
360
- export function useId(prefix = 'what') {
361
- const id = `${prefix}-${++idCounter}`;
362
- return () => id;
363
- }
364
-
365
- export function useIds(count, prefix = 'what') {
366
- const ids = [];
367
- for (let i = 0; i < count; i++) {
368
- ids.push(`${prefix}-${++idCounter}`);
369
- }
370
- return ids;
371
- }
372
-
373
- // --- Describe ---
374
- // Associate description with an element
375
-
376
- export function useDescribedBy(description) {
377
- const id = useId('desc');
378
-
379
- return {
380
- descriptionId: id,
381
- descriptionProps: () => ({
382
- id: id(),
383
- style: { display: 'none' },
384
- }),
385
- describedByProps: () => ({
386
- 'aria-describedby': id(),
387
- }),
388
- Description: () => h('div', {
389
- id: id(),
390
- style: { display: 'none' },
391
- }, description),
392
- };
393
- }
394
-
395
- // --- Labelledby ---
396
-
397
- export function useLabelledBy(label) {
398
- const id = useId('label');
399
-
400
- return {
401
- labelId: id,
402
- labelProps: () => ({
403
- id: id(),
404
- }),
405
- labelledByProps: () => ({
406
- 'aria-labelledby': id(),
407
- }),
408
- };
409
- }
410
-
411
- // --- Keyboard Navigation Helpers ---
412
-
413
- export const Keys = {
414
- Enter: 'Enter',
415
- Space: ' ',
416
- Escape: 'Escape',
417
- ArrowUp: 'ArrowUp',
418
- ArrowDown: 'ArrowDown',
419
- ArrowLeft: 'ArrowLeft',
420
- ArrowRight: 'ArrowRight',
421
- Home: 'Home',
422
- End: 'End',
423
- Tab: 'Tab',
424
- };
425
-
426
- export function onKey(key, handler) {
427
- return (e) => {
428
- if (e.key === key) {
429
- handler(e);
430
- }
431
- };
432
- }
433
-
434
- export function onKeys(keys, handler) {
435
- return (e) => {
436
- if (keys.includes(e.key)) {
437
- handler(e);
438
- }
439
- };
440
- }