what-core 0.6.2 → 0.6.5

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 (52) 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/render.d.ts +1 -1
  28. package/src/agent-context.js +1 -1
  29. package/src/compiler.js +18 -0
  30. package/src/components.js +73 -27
  31. package/src/devtools.js +4 -0
  32. package/src/dom.js +7 -0
  33. package/src/guardrails.js +3 -4
  34. package/src/hooks.js +0 -11
  35. package/src/index.js +5 -9
  36. package/src/render.js +91 -24
  37. package/testing.d.ts +1 -1
  38. package/dist/a11y.js +0 -440
  39. package/dist/animation.js +0 -548
  40. package/dist/components.js +0 -229
  41. package/dist/data.js +0 -638
  42. package/dist/dom.js +0 -439
  43. package/dist/form.js +0 -509
  44. package/dist/h.js +0 -152
  45. package/dist/head.js +0 -51
  46. package/dist/helpers.js +0 -140
  47. package/dist/hooks.js +0 -210
  48. package/dist/reactive.js +0 -432
  49. package/dist/scheduler.js +0 -246
  50. package/dist/skeleton.js +0 -363
  51. package/dist/store.js +0 -83
  52. package/dist/what.js +0 -117
package/dist/form.js DELETED
@@ -1,509 +0,0 @@
1
- // What Framework - Form Utilities
2
- // Controlled inputs, validation, and form state management
3
-
4
- import { signal, computed, batch, effect } from './reactive.js';
5
- import { h } from './h.js';
6
-
7
- // --- useForm Hook ---
8
- // Complete form state management with validation
9
-
10
- export function useForm(options = {}) {
11
- const {
12
- defaultValues = {},
13
- mode = 'onSubmit', // 'onSubmit' | 'onChange' | 'onBlur'
14
- reValidateMode = 'onChange',
15
- resolver,
16
- } = options;
17
-
18
- // Per-field signals for granular reactivity (avoids full-form re-renders on each keystroke)
19
- const fieldSignals = {};
20
- const errorSignals = {};
21
- const touchedSignals = {};
22
-
23
- function getFieldSignal(name) {
24
- if (!fieldSignals[name]) {
25
- fieldSignals[name] = signal(defaultValues[name] ?? '');
26
- }
27
- return fieldSignals[name];
28
- }
29
-
30
- function getErrorSignal(name) {
31
- if (!errorSignals[name]) {
32
- errorSignals[name] = signal(null);
33
- }
34
- return errorSignals[name];
35
- }
36
-
37
- function getTouchedSignal(name) {
38
- if (!touchedSignals[name]) {
39
- touchedSignals[name] = signal(false);
40
- }
41
- return touchedSignals[name];
42
- }
43
-
44
- // Aggregate signals for bulk operations
45
- const isDirty = signal(false);
46
- const isSubmitting = signal(false);
47
- const isSubmitted = signal(false);
48
- const submitCount = signal(0);
49
-
50
- // Helper: get all current values as a plain object
51
- function getAllValues() {
52
- const result = { ...defaultValues };
53
- for (const [name, sig] of Object.entries(fieldSignals)) {
54
- result[name] = sig.peek();
55
- }
56
- return result;
57
- }
58
-
59
- // Helper: get all current errors as a plain object
60
- function getAllErrors() {
61
- const result = {};
62
- for (const [name, sig] of Object.entries(errorSignals)) {
63
- const err = sig.peek();
64
- if (err) result[name] = err;
65
- }
66
- return result;
67
- }
68
-
69
- // Computed states
70
- const isValid = computed(() => {
71
- for (const sig of Object.values(errorSignals)) {
72
- if (sig()) return false;
73
- }
74
- return true;
75
- });
76
-
77
- const dirtyFields = computed(() => {
78
- const dirty = {};
79
- for (const [name, sig] of Object.entries(fieldSignals)) {
80
- if (sig() !== (defaultValues[name] ?? '')) {
81
- dirty[name] = true;
82
- }
83
- }
84
- return dirty;
85
- });
86
-
87
- // Validation
88
- async function validate(fieldName) {
89
- if (!resolver) return true;
90
-
91
- const result = await resolver(getAllValues());
92
-
93
- if (fieldName) {
94
- // Validate single field — only update that field's error signal
95
- const errSig = getErrorSignal(fieldName);
96
- if (result.errors[fieldName]) {
97
- errSig.set(result.errors[fieldName]);
98
- return false;
99
- } else {
100
- errSig.set(null);
101
- return true;
102
- }
103
- } else {
104
- // Validate all fields
105
- batch(() => {
106
- // Clear existing errors
107
- for (const sig of Object.values(errorSignals)) {
108
- sig.set(null);
109
- }
110
- // Set new errors
111
- for (const [name, err] of Object.entries(result.errors || {})) {
112
- getErrorSignal(name).set(err);
113
- }
114
- });
115
- return Object.keys(result.errors || {}).length === 0;
116
- }
117
- }
118
-
119
- // Register a field — only subscribes to THIS field's signal
120
- function register(name, options = {}) {
121
- const fieldSig = getFieldSignal(name);
122
- return {
123
- name,
124
- // Use getter so value is always fresh, even if register result is cached
125
- get value() { return fieldSig(); },
126
- onInput: (e) => {
127
- const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
128
- setValue(name, value);
129
-
130
- if (mode === 'onChange' || (isSubmitted.peek() && reValidateMode === 'onChange')) {
131
- validate(name);
132
- }
133
- },
134
- onBlur: () => {
135
- getTouchedSignal(name).set(true);
136
-
137
- if (mode === 'onBlur' || (isSubmitted.peek() && reValidateMode === 'onBlur')) {
138
- validate(name);
139
- }
140
- },
141
- onFocus: () => {},
142
- ref: options.ref,
143
- };
144
- }
145
-
146
- // Set single field value — only triggers re-render for components reading this field
147
- function setValue(name, value, options = {}) {
148
- const { shouldValidate = false, shouldDirty = true } = options;
149
-
150
- getFieldSignal(name).set(value);
151
- if (shouldDirty) isDirty.set(true);
152
-
153
- if (shouldValidate) {
154
- validate(name);
155
- }
156
- }
157
-
158
- // Get single field value
159
- function getValue(name) {
160
- return getFieldSignal(name)();
161
- }
162
-
163
- // Set error for a field
164
- function setError(name, error) {
165
- getErrorSignal(name).set(error);
166
- }
167
-
168
- // Clear error for a field
169
- function clearError(name) {
170
- getErrorSignal(name).set(null);
171
- }
172
-
173
- // Clear all errors
174
- function clearErrors() {
175
- batch(() => {
176
- for (const sig of Object.values(errorSignals)) {
177
- sig.set(null);
178
- }
179
- });
180
- }
181
-
182
- // Reset form
183
- function reset(newValues = defaultValues) {
184
- batch(() => {
185
- for (const [name, sig] of Object.entries(fieldSignals)) {
186
- sig.set(newValues[name] ?? '');
187
- }
188
- for (const sig of Object.values(errorSignals)) {
189
- sig.set(null);
190
- }
191
- for (const sig of Object.values(touchedSignals)) {
192
- sig.set(false);
193
- }
194
- isDirty.set(false);
195
- isSubmitted.set(false);
196
- });
197
- }
198
-
199
- // Handle submit
200
- function handleSubmit(onValid, onInvalid) {
201
- return async (e) => {
202
- if (e) e.preventDefault();
203
-
204
- isSubmitting.set(true);
205
- isSubmitted.set(true);
206
- submitCount.set(submitCount.peek() + 1);
207
-
208
- const isFormValid = await validate();
209
-
210
- if (isFormValid) {
211
- await onValid(getAllValues());
212
- } else if (onInvalid) {
213
- onInvalid(getAllErrors());
214
- }
215
-
216
- isSubmitting.set(false);
217
- };
218
- }
219
-
220
- // Watch a field — returns a computed that subscribes only to this field
221
- function watch(name) {
222
- if (name) {
223
- return computed(() => getFieldSignal(name)());
224
- }
225
- // Watch all: return a computed that reads all field signals
226
- return computed(() => getAllValues());
227
- }
228
-
229
- return {
230
- register,
231
- handleSubmit,
232
- setValue,
233
- getValue,
234
- setError,
235
- clearError,
236
- clearErrors,
237
- reset,
238
- watch,
239
- validate,
240
- // Form state — uses getters for errors/touched to enable per-field granularity
241
- formState: {
242
- get values() { return getAllValues(); },
243
- get errors() { return getAllErrors(); },
244
- get touched() {
245
- const result = {};
246
- for (const [name, sig] of Object.entries(touchedSignals)) {
247
- if (sig()) result[name] = true;
248
- }
249
- return result;
250
- },
251
- isDirty: () => isDirty(),
252
- isValid,
253
- isSubmitting: () => isSubmitting(),
254
- isSubmitted: () => isSubmitted(),
255
- submitCount: () => submitCount(),
256
- dirtyFields,
257
- },
258
- };
259
- }
260
-
261
- // --- Validation Resolvers ---
262
-
263
- export function zodResolver(schema) {
264
- return async (values) => {
265
- try {
266
- const result = await schema.parseAsync(values);
267
- return { values: result, errors: {} };
268
- } catch (e) {
269
- const errors = {};
270
- for (const issue of e.errors || []) {
271
- const path = issue.path.join('.');
272
- if (!errors[path]) {
273
- errors[path] = { type: issue.code, message: issue.message };
274
- }
275
- }
276
- return { values: {}, errors };
277
- }
278
- };
279
- }
280
-
281
- export function yupResolver(schema) {
282
- return async (values) => {
283
- try {
284
- const result = await schema.validate(values, { abortEarly: false });
285
- return { values: result, errors: {} };
286
- } catch (e) {
287
- const errors = {};
288
- for (const err of e.inner || []) {
289
- if (!errors[err.path]) {
290
- errors[err.path] = { type: err.type, message: err.message };
291
- }
292
- }
293
- return { values: {}, errors };
294
- }
295
- };
296
- }
297
-
298
- // Simple validation resolver
299
- export function simpleResolver(rules) {
300
- return async (values) => {
301
- const errors = {};
302
-
303
- for (const [field, fieldRules] of Object.entries(rules)) {
304
- const value = values[field];
305
-
306
- for (const rule of fieldRules) {
307
- const error = rule(value, values);
308
- if (error) {
309
- errors[field] = { type: 'validation', message: error };
310
- break;
311
- }
312
- }
313
- }
314
-
315
- return { values, errors };
316
- };
317
- }
318
-
319
- // Built-in validation rules
320
- export const rules = {
321
- required: (message = 'This field is required') => (value) => {
322
- if (value === undefined || value === null || value === '') {
323
- return message;
324
- }
325
- },
326
-
327
- minLength: (min, message) => (value) => {
328
- if (typeof value === 'string' && value.length < min) {
329
- return message || `Must be at least ${min} characters`;
330
- }
331
- },
332
-
333
- maxLength: (max, message) => (value) => {
334
- if (typeof value === 'string' && value.length > max) {
335
- return message || `Must be at most ${max} characters`;
336
- }
337
- },
338
-
339
- min: (min, message) => (value) => {
340
- if (typeof value === 'number' && value < min) {
341
- return message || `Must be at least ${min}`;
342
- }
343
- },
344
-
345
- max: (max, message) => (value) => {
346
- if (typeof value === 'number' && value > max) {
347
- return message || `Must be at most ${max}`;
348
- }
349
- },
350
-
351
- pattern: (regex, message = 'Invalid format') => (value) => {
352
- if (typeof value === 'string' && !regex.test(value)) {
353
- return message;
354
- }
355
- },
356
-
357
- email: (message = 'Invalid email address') => (value) => {
358
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
359
- if (typeof value === 'string' && !emailRegex.test(value)) {
360
- return message;
361
- }
362
- },
363
-
364
- url: (message = 'Invalid URL') => (value) => {
365
- try {
366
- if (typeof value === 'string' && value) {
367
- new URL(value);
368
- }
369
- } catch {
370
- return message;
371
- }
372
- },
373
-
374
- match: (field, message) => (value, values) => {
375
- if (value !== values[field]) {
376
- return message || `Must match ${field}`;
377
- }
378
- },
379
-
380
- custom: (validator) => validator,
381
- };
382
-
383
- // --- useField Hook ---
384
- // Individual field control
385
-
386
- export function useField(name, options = {}) {
387
- const { validate: validateFn, defaultValue = '' } = options;
388
-
389
- const value = signal(defaultValue);
390
- const error = signal(null);
391
- const isTouched = signal(false);
392
- const isDirty = signal(false);
393
-
394
- async function validate() {
395
- if (!validateFn) return true;
396
- const result = await validateFn(value.peek());
397
- error.set(result || null);
398
- return !result;
399
- }
400
-
401
- return {
402
- name,
403
- value: () => value(),
404
- error: () => error(),
405
- isTouched: () => isTouched(),
406
- isDirty: () => isDirty(),
407
- setValue: (v) => {
408
- value.set(v);
409
- isDirty.set(true);
410
- },
411
- setError: (e) => error.set(e),
412
- validate,
413
- reset: () => {
414
- value.set(defaultValue);
415
- error.set(null);
416
- isTouched.set(false);
417
- isDirty.set(false);
418
- },
419
- inputProps: () => ({
420
- name,
421
- value: value(),
422
- onInput: (e) => {
423
- value.set(e.target.value);
424
- isDirty.set(true);
425
- },
426
- onBlur: () => {
427
- isTouched.set(true);
428
- validate();
429
- },
430
- }),
431
- };
432
- }
433
-
434
- // --- Controlled Input Components ---
435
-
436
- export function Input(props) {
437
- const { register, error, ...rest } = props;
438
- const registered = register ? register(props.name) : {};
439
-
440
- return h('input', {
441
- ...rest,
442
- ...registered,
443
- 'aria-invalid': error ? 'true' : undefined,
444
- });
445
- }
446
-
447
- export function Textarea(props) {
448
- const { register, error, ...rest } = props;
449
- const registered = register ? register(props.name) : {};
450
-
451
- return h('textarea', {
452
- ...rest,
453
- ...registered,
454
- 'aria-invalid': error ? 'true' : undefined,
455
- });
456
- }
457
-
458
- export function Select(props) {
459
- const { register, error, children, ...rest } = props;
460
- const registered = register ? register(props.name) : {};
461
-
462
- return h('select', {
463
- ...rest,
464
- ...registered,
465
- 'aria-invalid': error ? 'true' : undefined,
466
- }, children);
467
- }
468
-
469
- export function Checkbox(props) {
470
- const { register, ...rest } = props;
471
- const registered = register ? register(props.name) : {};
472
-
473
- return h('input', {
474
- type: 'checkbox',
475
- ...rest,
476
- ...registered,
477
- checked: registered.value,
478
- });
479
- }
480
-
481
- export function Radio(props) {
482
- const { register, value: radioValue, ...rest } = props;
483
- const registered = register ? register(props.name) : {};
484
-
485
- return h('input', {
486
- type: 'radio',
487
- value: radioValue,
488
- ...rest,
489
- checked: registered.value === radioValue,
490
- onChange: (e) => {
491
- if (e.target.checked && registered.onInput) {
492
- registered.onInput({ target: { value: radioValue } });
493
- }
494
- },
495
- });
496
- }
497
-
498
- // --- Form Error Display ---
499
-
500
- export function ErrorMessage({ name, errors, render }) {
501
- const error = errors ? errors()[name] : null;
502
- if (!error) return null;
503
-
504
- if (render) {
505
- return render({ message: error.message, type: error.type });
506
- }
507
-
508
- return h('span', { class: 'what-error', role: 'alert' }, error.message);
509
- }
package/dist/h.js DELETED
@@ -1,152 +0,0 @@
1
- const EMPTY_OBJ = Object.create(null);
2
- const EMPTY_ARR = [];
3
- export function h(tag, props, ...children) {
4
- props = props || EMPTY_OBJ;
5
- const flat = flattenChildren(children);
6
- const key = props.key ?? null;
7
- if (props.key !== undefined) {
8
- props = { ...props };
9
- delete props.key;
10
- }
11
- return { tag, props, children: flat, key, _vnode: true };
12
- }
13
- export function Fragment({ children }) {
14
- return children;
15
- }
16
- function flattenChildren(children) {
17
- const out = [];
18
- for (let i = 0; i < children.length; i++) {
19
- const child = children[i];
20
- if (child == null || child === false || child === true) continue;
21
- if (Array.isArray(child)) {
22
- out.push(...flattenChildren(child));
23
- } else if (typeof child === 'object' && child._vnode) {
24
- out.push(child);
25
- } else if (typeof child === 'function') {
26
- out.push(child);
27
- } else {
28
- out.push(String(child));
29
- }
30
- }
31
- return out;
32
- }
33
- export function html(strings, ...values) {
34
- const src = strings.reduce((acc, str, i) =>
35
- acc + str + (i < values.length ? `\x00${i}\x00` : ''), '');
36
- return parseTemplate(src, values);
37
- }
38
- function parseTemplate(src, values) {
39
- src = src.trim();
40
- const nodes = [];
41
- let i = 0;
42
- while (i < src.length) {
43
- if (src[i] === '<') {
44
- const result = parseElement(src, i, values);
45
- if (result) {
46
- nodes.push(result.node);
47
- i = result.end;
48
- continue;
49
- }
50
- }
51
- const result = parseText(src, i, values);
52
- if (result.text) nodes.push(result.text);
53
- i = result.end;
54
- }
55
- return nodes.length === 1 ? nodes[0] : nodes;
56
- }
57
- function parseElement(src, start, values) {
58
- const openMatch = src.slice(start).match(/^<([a-zA-Z][a-zA-Z0-9-]*|[A-Z]\w*)/);
59
- if (!openMatch) return null;
60
- const tag = openMatch[1];
61
- let i = start + openMatch[0].length;
62
- const props = {};
63
- while (i < src.length) {
64
- while (i < src.length && /\s/.test(src[i])) i++;
65
- if (src.slice(i, i + 2) === '/>') {
66
- return { node: h(tag, Object.keys(props).length ? props : null), end: i + 2 };
67
- }
68
- if (src[i] === '>') {
69
- i++;
70
- break;
71
- }
72
- if (src.slice(i, i + 3) === '...') {
73
- const placeholder = src.slice(i + 3).match(/^\x00(\d+)\x00/);
74
- if (placeholder) {
75
- Object.assign(props, values[Number(placeholder[1])]);
76
- i += 3 + placeholder[0].length;
77
- continue;
78
- }
79
- }
80
- const attrMatch = src.slice(i).match(/^([a-zA-Z_@:][a-zA-Z0-9_:.-]*)/);
81
- if (!attrMatch) break;
82
- const attrName = attrMatch[1];
83
- i += attrMatch[0].length;
84
- while (i < src.length && /\s/.test(src[i])) i++;
85
- if (src[i] === '=') {
86
- i++;
87
- while (i < src.length && /\s/.test(src[i])) i++;
88
- const ph = src.slice(i).match(/^\x00(\d+)\x00/);
89
- if (ph) {
90
- props[attrName] = values[Number(ph[1])];
91
- i += ph[0].length;
92
- } else if (src[i] === '"' || src[i] === "'") {
93
- const q = src[i];
94
- i++;
95
- let val = '';
96
- while (i < src.length && src[i] !== q) {
97
- const tph = src.slice(i).match(/^\x00(\d+)\x00/);
98
- if (tph) {
99
- val += String(values[Number(tph[1])]);
100
- i += tph[0].length;
101
- } else {
102
- val += src[i];
103
- i++;
104
- }
105
- }
106
- i++;
107
- props[attrName] = val;
108
- }
109
- } else {
110
- props[attrName] = true;
111
- }
112
- }
113
- const children = [];
114
- const closeTag = `</${tag}>`;
115
- while (i < src.length) {
116
- if (src.slice(i, i + closeTag.length) === closeTag) {
117
- i += closeTag.length;
118
- break;
119
- }
120
- if (src[i] === '<') {
121
- const child = parseElement(src, i, values);
122
- if (child) {
123
- children.push(child.node);
124
- i = child.end;
125
- continue;
126
- }
127
- }
128
- const text = parseText(src, i, values);
129
- if (text.text != null) children.push(text.text);
130
- i = text.end;
131
- }
132
- return {
133
- node: h(tag, Object.keys(props).length ? props : null, ...children),
134
- end: i,
135
- };
136
- }
137
- function parseText(src, start, values) {
138
- let i = start;
139
- let text = '';
140
- while (i < src.length && src[i] !== '<') {
141
- const ph = src.slice(i).match(/^\x00(\d+)\x00/);
142
- if (ph) {
143
- if (text.trim()) {
144
- return { text: text.trim(), end: i };
145
- }
146
- return { text: values[Number(ph[1])], end: i + ph[0].length };
147
- }
148
- text += src[i];
149
- i++;
150
- }
151
- return { text: text.trim() || null, end: i };
152
- }
package/dist/head.js DELETED
@@ -1,51 +0,0 @@
1
- const headState = {
2
- title: null,
3
- metas: new Map(),
4
- links: new Map(),
5
- };
6
- export function Head({ title, meta, link, children }) {
7
- if (typeof document === 'undefined') return null;
8
- if (title) {
9
- document.title = title;
10
- headState.title = title;
11
- }
12
- if (meta) {
13
- for (const attrs of (Array.isArray(meta) ? meta : [meta])) {
14
- const key = attrs.name || attrs.property || attrs.httpEquiv || JSON.stringify(attrs);
15
- setHeadTag('meta', key, attrs);
16
- }
17
- }
18
- if (link) {
19
- for (const attrs of (Array.isArray(link) ? link : [link])) {
20
- const key = attrs.rel + (attrs.href || '');
21
- setHeadTag('link', key, attrs);
22
- }
23
- }
24
- return children || null;
25
- }
26
- function setHeadTag(tag, key, attrs) {
27
- const existing = document.head.querySelector(`[data-what-head="${key}"]`);
28
- if (existing) {
29
- updateElement(existing, attrs);
30
- return;
31
- }
32
- const el = document.createElement(tag);
33
- el.setAttribute('data-what-head', key);
34
- for (const [k, v] of Object.entries(attrs)) {
35
- el.setAttribute(k, v);
36
- }
37
- document.head.appendChild(el);
38
- }
39
- function updateElement(el, attrs) {
40
- for (const [k, v] of Object.entries(attrs)) {
41
- if (el.getAttribute(k) !== v) {
42
- el.setAttribute(k, v);
43
- }
44
- }
45
- }
46
- export function clearHead() {
47
- const tags = document.head.querySelectorAll('[data-what-head]');
48
- for (const tag of tags) tag.remove();
49
- headState.metas.clear();
50
- headState.links.clear();
51
- }