orynn 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,823 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ var __typeError = (msg) => {
7
+ throw TypeError(msg);
8
+ };
9
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
10
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
11
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
12
+
13
+ // src/internal/cx.ts
14
+ function cx(...names) {
15
+ return names.filter(Boolean).join(" ");
16
+ }
17
+ function toMessages(error) {
18
+ if (error == null) return [];
19
+ return (Array.isArray(error) ? error : [error]).filter((m) => Boolean(m));
20
+ }
21
+ function joinIds(...ids) {
22
+ const list = ids.filter((id) => Boolean(id));
23
+ return list.length > 0 ? list.join(" ") : void 0;
24
+ }
25
+ function Field(props) {
26
+ const { label, description, error, required, disabled, className, children } = props;
27
+ const reactId = react.useId();
28
+ const id = props.id ?? reactId;
29
+ const messages = toMessages(error);
30
+ const invalid = messages.length > 0;
31
+ const descriptionId = description != null ? `${id}-description` : void 0;
32
+ const errorId = invalid ? `${id}-error` : void 0;
33
+ const describedById = joinIds(descriptionId, errorId);
34
+ const ctx = { id, describedById, invalid, required, disabled };
35
+ return /* @__PURE__ */ jsxRuntime.jsxs(
36
+ "div",
37
+ {
38
+ className: cx("orynn-field", className),
39
+ "data-invalid": invalid || void 0,
40
+ "data-disabled": disabled || void 0,
41
+ "data-required": required || void 0,
42
+ children: [
43
+ label != null && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "orynn-field__label", htmlFor: id, children: label }),
44
+ description != null && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "orynn-field__description", id: descriptionId, children: description }),
45
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "orynn-field__control", children: typeof children === "function" ? children(ctx) : children }),
46
+ invalid && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "orynn-field__error", id: errorId, role: "alert", children: messages.length === 1 ? messages[0] : messages.map((m) => /* @__PURE__ */ jsxRuntime.jsx("span", { children: m }, m)) })
47
+ ]
48
+ }
49
+ );
50
+ }
51
+ var Dropdown = /* @__PURE__ */ react.forwardRef(
52
+ function Dropdown2(props, ref) {
53
+ const {
54
+ label,
55
+ description,
56
+ error,
57
+ className,
58
+ options,
59
+ value,
60
+ onChange,
61
+ placeholder,
62
+ id,
63
+ required,
64
+ disabled,
65
+ onBlur,
66
+ onFocus,
67
+ ...rest
68
+ } = props;
69
+ const handleChange = react.useCallback(
70
+ (event) => onChange?.(event.target.value, event),
71
+ [onChange]
72
+ );
73
+ const controlled = value !== void 0;
74
+ return /* @__PURE__ */ jsxRuntime.jsx(
75
+ Field,
76
+ {
77
+ label,
78
+ description,
79
+ error,
80
+ required,
81
+ disabled,
82
+ id,
83
+ className,
84
+ children: ({ id: fieldId, describedById, invalid }) => /* @__PURE__ */ jsxRuntime.jsxs(
85
+ "select",
86
+ {
87
+ ...rest,
88
+ ref,
89
+ id: fieldId,
90
+ className: "orynn-dropdown",
91
+ required,
92
+ disabled,
93
+ "aria-invalid": invalid || void 0,
94
+ "aria-describedby": describedById,
95
+ ...controlled ? { value } : {},
96
+ onChange: handleChange,
97
+ onBlur,
98
+ onFocus,
99
+ children: [
100
+ placeholder != null && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: required, children: placeholder }),
101
+ options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value))
102
+ ]
103
+ }
104
+ )
105
+ }
106
+ );
107
+ }
108
+ );
109
+ var Input = /* @__PURE__ */ react.forwardRef(
110
+ function Input2(props, ref) {
111
+ const {
112
+ label,
113
+ description,
114
+ error,
115
+ className,
116
+ value,
117
+ onChange,
118
+ type = "text",
119
+ id,
120
+ required,
121
+ disabled,
122
+ readOnly,
123
+ onBlur,
124
+ onFocus,
125
+ ...rest
126
+ } = props;
127
+ const handleChange = react.useCallback(
128
+ (event) => onChange?.(event.target.value, event),
129
+ [onChange]
130
+ );
131
+ const controlled = value !== void 0;
132
+ return /* @__PURE__ */ jsxRuntime.jsx(
133
+ Field,
134
+ {
135
+ label,
136
+ description,
137
+ error,
138
+ required,
139
+ disabled,
140
+ id,
141
+ className,
142
+ children: ({ id: fieldId, describedById, invalid }) => /* @__PURE__ */ jsxRuntime.jsx(
143
+ "input",
144
+ {
145
+ ...rest,
146
+ ref,
147
+ id: fieldId,
148
+ className: "orynn-input",
149
+ type,
150
+ required,
151
+ disabled,
152
+ readOnly,
153
+ "aria-invalid": invalid || void 0,
154
+ "aria-describedby": describedById,
155
+ ...controlled ? { value } : {},
156
+ onChange: handleChange,
157
+ onBlur,
158
+ onFocus
159
+ }
160
+ )
161
+ }
162
+ );
163
+ }
164
+ );
165
+
166
+ // src/internal/dev.ts
167
+ var __DEV__ = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
168
+ function invariant(condition, message) {
169
+ if (condition) return;
170
+ if (__DEV__) {
171
+ throw new Error(`[orynn] ${message}`);
172
+ }
173
+ }
174
+ var warned = /* @__PURE__ */ new Set();
175
+ function warnOnce(message) {
176
+ if (!__DEV__ || warned.has(message)) return;
177
+ warned.add(message);
178
+ console.warn(`[orynn] ${message}`);
179
+ }
180
+
181
+ // src/core/registry/registry.ts
182
+ var _entries;
183
+ var Registry = class {
184
+ constructor() {
185
+ __privateAdd(this, _entries, /* @__PURE__ */ new Map());
186
+ }
187
+ register(type, registration) {
188
+ if (__DEV__ && __privateGet(this, _entries).has(type)) {
189
+ warnOnce(`Field type "${type}" is being re-registered; the previous definition is replaced.`);
190
+ }
191
+ __privateGet(this, _entries).set(type, registration);
192
+ }
193
+ get(type) {
194
+ return __privateGet(this, _entries).get(type);
195
+ }
196
+ has(type) {
197
+ return __privateGet(this, _entries).has(type);
198
+ }
199
+ types() {
200
+ return [...__privateGet(this, _entries).keys()];
201
+ }
202
+ };
203
+ _entries = new WeakMap();
204
+ function createRegistry() {
205
+ return new Registry();
206
+ }
207
+ var defaultRegistry = /* @__PURE__ */ createRegistry();
208
+ function registerField(type, registration) {
209
+ defaultRegistry.register(type, registration);
210
+ }
211
+
212
+ // src/layout/resolve-columns.ts
213
+ var DEFAULT_COLUMNS = { desktop: 3, tablet: 2, mobile: 1 };
214
+ var clampCount = (n, fallback) => typeof n === "number" && Number.isFinite(n) && n >= 1 ? Math.floor(n) : fallback;
215
+ function resolveColumns(partial) {
216
+ return {
217
+ desktop: clampCount(partial?.desktop, DEFAULT_COLUMNS.desktop),
218
+ tablet: clampCount(partial?.tablet, DEFAULT_COLUMNS.tablet),
219
+ mobile: clampCount(partial?.mobile, DEFAULT_COLUMNS.mobile)
220
+ };
221
+ }
222
+ function clampSpan(span, columnCount) {
223
+ const n = clampCount(span, 1);
224
+ return Math.min(n, Math.max(columnCount, 1));
225
+ }
226
+
227
+ // src/core/config/normalize.ts
228
+ function normalizeGap(gap) {
229
+ if (gap == null) return void 0;
230
+ return typeof gap === "number" ? `${gap}px` : gap;
231
+ }
232
+ function normalizeConfig(config, registry2) {
233
+ invariant(
234
+ config != null && typeof config === "object" && Array.isArray(config.fields),
235
+ "`config` must be an object with a `fields` array."
236
+ );
237
+ const seen = /* @__PURE__ */ new Set();
238
+ for (const field of config.fields) {
239
+ invariant(
240
+ field != null && typeof field === "object",
241
+ "Every entry in `config.fields` must be an object."
242
+ );
243
+ invariant(
244
+ typeof field.name === "string" && field.name.length > 0,
245
+ "Every field needs a non-empty string `name`."
246
+ );
247
+ invariant(!seen.has(field.name), `Duplicate field name "${field.name}" in config.fields.`);
248
+ seen.add(field.name);
249
+ invariant(
250
+ typeof field.type === "string" && field.type.length > 0,
251
+ `Field "${field.name}" is missing a "type".`
252
+ );
253
+ if (registry2) {
254
+ invariant(
255
+ registry2.has(field.type),
256
+ `Unknown field type "${field.type}" on field "${field.name}". Registered types: ${registry2.types().join(", ") || "(none)"}.`
257
+ );
258
+ const typeError = registry2.get(field.type)?.validateConfig?.(field);
259
+ invariant(typeError == null, `Field "${field.name}" (${field.type}): ${typeError}`);
260
+ }
261
+ }
262
+ return {
263
+ fields: config.fields,
264
+ columns: resolveColumns(config.columns),
265
+ gap: normalizeGap(config.gap),
266
+ legend: config.legend,
267
+ id: config.id
268
+ };
269
+ }
270
+
271
+ // src/core/validation/messages.ts
272
+ var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
273
+ var defaultMessages = {
274
+ required: ({ label }) => `${label} is required`,
275
+ minLength: ({ param, label }) => `${label} must be at least ${plural(param, "character")}`,
276
+ maxLength: ({ param, label }) => `${label} must be at most ${plural(param, "character")}`
277
+ };
278
+ var FALLBACK_LABEL = "This field";
279
+ function resolveMessage(token, param, label, overrides) {
280
+ const override = overrides?.[token];
281
+ if (override != null) return override;
282
+ const formatter = defaultMessages[token];
283
+ if (formatter) return formatter({ param, label: label ?? FALLBACK_LABEL });
284
+ return token;
285
+ }
286
+
287
+ // src/core/validation/rules.ts
288
+ var isEmpty = (value) => value == null || value === "" || Array.isArray(value) && value.length === 0;
289
+ var asLength = (value) => typeof value === "string" || Array.isArray(value) ? value.length : String(value ?? "").length;
290
+ var builtInRules = {
291
+ required: (value, param) => {
292
+ if (param === false) return null;
293
+ return isEmpty(value) ? "required" : null;
294
+ },
295
+ minLength: (value, param) => {
296
+ if (typeof param !== "number" || isEmpty(value)) return null;
297
+ return asLength(value) < param ? "minLength" : null;
298
+ },
299
+ maxLength: (value, param) => {
300
+ if (typeof param !== "number" || isEmpty(value)) return null;
301
+ return asLength(value) > param ? "maxLength" : null;
302
+ }
303
+ };
304
+ var registry = new Map(Object.entries(builtInRules));
305
+ function registerRule(name, fn) {
306
+ registry.set(name, fn);
307
+ }
308
+ function getRule(name) {
309
+ return registry.get(name);
310
+ }
311
+
312
+ // src/core/validation/resolver.ts
313
+ function createRuleResolver() {
314
+ return (values, config) => {
315
+ const result = {};
316
+ for (const field of config.fields) {
317
+ const validation = field.validation;
318
+ if (!validation) continue;
319
+ const value = values[field.name];
320
+ const errors = [];
321
+ for (const key of Object.keys(validation)) {
322
+ if (key === "messages") continue;
323
+ const param = validation[key];
324
+ if (param === void 0 || param === false) continue;
325
+ const rule = getRule(key);
326
+ if (!rule) {
327
+ if (__DEV__) {
328
+ warnOnce(`No validator registered for rule "${key}" (field "${field.name}").`);
329
+ }
330
+ continue;
331
+ }
332
+ const token = rule(value, param, values);
333
+ if (token == null) continue;
334
+ errors.push({
335
+ rule: key,
336
+ message: resolveMessage(token, param, field.label, validation.messages)
337
+ });
338
+ }
339
+ if (errors.length > 0) result[field.name] = errors;
340
+ }
341
+ return result;
342
+ };
343
+ }
344
+ var defaultResolver = /* @__PURE__ */ createRuleResolver();
345
+
346
+ // src/core/state/reducer.ts
347
+ var initialMeta = {
348
+ touched: {},
349
+ errors: {},
350
+ validating: false,
351
+ everValidated: false
352
+ };
353
+ function metaReducer(state, action) {
354
+ switch (action.type) {
355
+ case "touch": {
356
+ if (state.touched[action.name]) return state;
357
+ return { ...state, touched: { ...state.touched, [action.name]: true } };
358
+ }
359
+ case "touchAll": {
360
+ const touched = { ...state.touched };
361
+ for (const name of action.names) touched[name] = true;
362
+ return { ...state, touched };
363
+ }
364
+ case "validation:start":
365
+ return { ...state, validating: true };
366
+ case "validation:field": {
367
+ const errors = { ...state.errors };
368
+ if (action.errors.length > 0) errors[action.name] = action.errors;
369
+ else delete errors[action.name];
370
+ return { ...state, errors, validating: false, everValidated: true };
371
+ }
372
+ case "validation:all":
373
+ return { ...state, errors: action.result, validating: false, everValidated: true };
374
+ case "reset":
375
+ return { ...initialMeta };
376
+ default:
377
+ return state;
378
+ }
379
+ }
380
+ function deriveStatus(meta) {
381
+ if (meta.validating) return "validating";
382
+ if (!meta.everValidated) return "idle";
383
+ return Object.keys(meta.errors).length > 0 ? "invalid" : "valid";
384
+ }
385
+ function computeDirty(values, initial) {
386
+ const dirty = {};
387
+ for (const key of Object.keys(values)) {
388
+ if (!Object.is(values[key], initial[key])) dirty[key] = true;
389
+ }
390
+ return dirty;
391
+ }
392
+ function useLatestRef(value) {
393
+ const ref = react.useRef(value);
394
+ ref.current = value;
395
+ return ref;
396
+ }
397
+
398
+ // src/core/state/use-controllable-state.ts
399
+ function useControllableState({
400
+ value,
401
+ defaultValue,
402
+ onChange
403
+ }) {
404
+ const isControlled = value !== void 0;
405
+ const [uncontrolled, setUncontrolled] = react.useState(defaultValue);
406
+ const current = isControlled ? value : uncontrolled;
407
+ const currentRef = useLatestRef(current);
408
+ const onChangeRef = useLatestRef(onChange);
409
+ const isControlledRef = useLatestRef(isControlled);
410
+ const setValue = react.useCallback((next) => {
411
+ const resolved = typeof next === "function" ? next(currentRef.current) : next;
412
+ if (Object.is(resolved, currentRef.current)) return;
413
+ if (!isControlledRef.current) setUncontrolled(resolved);
414
+ onChangeRef.current?.(resolved);
415
+ }, []);
416
+ return [current, setValue];
417
+ }
418
+
419
+ // src/core/state/use-fieldset-state.ts
420
+ var NO_ERRORS = Object.freeze([]);
421
+ function computeInitialValues(config, registry2, provided) {
422
+ const out = {};
423
+ for (const field of config.fields) {
424
+ if (provided && Object.prototype.hasOwnProperty.call(provided, field.name)) {
425
+ out[field.name] = provided[field.name];
426
+ } else if (field.defaultValue !== void 0) {
427
+ out[field.name] = field.defaultValue;
428
+ } else {
429
+ out[field.name] = registry2?.get(field.type)?.defaultValue ?? "";
430
+ }
431
+ }
432
+ return out;
433
+ }
434
+ function useFieldsetState(config, options = {}) {
435
+ const {
436
+ value,
437
+ defaultValue,
438
+ onChange,
439
+ onValidationChange,
440
+ resolver = defaultResolver,
441
+ registry: registry2,
442
+ validateOn = "blur"
443
+ } = options;
444
+ const normalized = react.useMemo(() => normalizeConfig(config, registry2), [config, registry2]);
445
+ const initialRef = react.useRef(null);
446
+ if (initialRef.current === null) {
447
+ initialRef.current = computeInitialValues(
448
+ normalized,
449
+ registry2,
450
+ value ?? defaultValue
451
+ );
452
+ }
453
+ const [rawValues, setRawValues] = useControllableState({
454
+ value,
455
+ defaultValue: initialRef.current,
456
+ onChange
457
+ });
458
+ const values = react.useMemo(() => ({ ...initialRef.current, ...rawValues }), [rawValues]);
459
+ const [meta, dispatch] = react.useReducer(metaReducer, initialMeta);
460
+ const valuesRef = useLatestRef(values);
461
+ const resolverRef = useLatestRef(resolver);
462
+ const configRef = useLatestRef(config);
463
+ const normalizedRef = useLatestRef(normalized);
464
+ const registryRef = useLatestRef(registry2);
465
+ const validateOnRef = useLatestRef(validateOn);
466
+ const onValidationChangeRef = useLatestRef(onValidationChange);
467
+ const runIdRef = react.useRef(0);
468
+ const runValidation = react.useCallback(async (scope) => {
469
+ const runId = ++runIdRef.current;
470
+ const input = scope.values ?? valuesRef.current;
471
+ dispatch({ type: "validation:start" });
472
+ const result = await resolverRef.current(input, configRef.current, {
473
+ trigger: scope.trigger,
474
+ ...scope.field ? { changedField: scope.field } : {}
475
+ });
476
+ if (runId !== runIdRef.current) return result;
477
+ if (scope.field) {
478
+ dispatch({ type: "validation:field", name: scope.field, errors: result[scope.field] ?? [] });
479
+ } else {
480
+ dispatch({ type: "validation:all", result });
481
+ onValidationChangeRef.current?.(result);
482
+ }
483
+ return result;
484
+ }, []);
485
+ const setFieldValue = react.useCallback(
486
+ (name, next) => {
487
+ setRawValues((prev) => ({ ...prev, [name]: next }));
488
+ if (validateOnRef.current === "change") {
489
+ void runValidation({
490
+ field: name,
491
+ trigger: "change",
492
+ values: { ...valuesRef.current, [name]: next }
493
+ });
494
+ }
495
+ },
496
+ [setRawValues, runValidation]
497
+ );
498
+ const setValues = react.useCallback((next) => setRawValues(next), [setRawValues]);
499
+ const markTouched = react.useCallback(
500
+ (name) => {
501
+ dispatch({ type: "touch", name });
502
+ if (validateOnRef.current === "blur") {
503
+ void runValidation({ field: name, trigger: "blur" });
504
+ }
505
+ },
506
+ [runValidation]
507
+ );
508
+ const validate = react.useCallback(() => runValidation({ trigger: "submit" }), [runValidation]);
509
+ const handleSubmit = react.useCallback(
510
+ (onValid, onInvalid) => async (event) => {
511
+ event?.preventDefault?.();
512
+ dispatch({
513
+ type: "touchAll",
514
+ names: normalizedRef.current.fields.map((f) => f.name)
515
+ });
516
+ const result = await runValidation({ trigger: "submit" });
517
+ if (Object.keys(result).length === 0) onValid(valuesRef.current);
518
+ else onInvalid?.(result);
519
+ },
520
+ [runValidation]
521
+ );
522
+ const reset = react.useCallback(
523
+ (next) => {
524
+ const fresh = computeInitialValues(
525
+ normalizedRef.current,
526
+ registryRef.current,
527
+ next
528
+ );
529
+ initialRef.current = fresh;
530
+ setRawValues(fresh);
531
+ dispatch({ type: "reset" });
532
+ runIdRef.current++;
533
+ },
534
+ [setRawValues]
535
+ );
536
+ const handlerCacheRef = react.useRef(
537
+ /* @__PURE__ */ new Map()
538
+ );
539
+ const getHandlers = react.useCallback(
540
+ (name) => {
541
+ const cache = handlerCacheRef.current;
542
+ let entry = cache.get(name);
543
+ if (!entry) {
544
+ entry = {
545
+ setValue: (v) => setFieldValue(name, v),
546
+ markTouched: () => markTouched(name)
547
+ };
548
+ cache.set(name, entry);
549
+ }
550
+ return entry;
551
+ },
552
+ [setFieldValue, markTouched]
553
+ );
554
+ const dirty = react.useMemo(() => computeDirty(values, initialRef.current ?? {}), [values]);
555
+ const status = deriveStatus(meta);
556
+ const isValid = Object.keys(meta.errors).length === 0;
557
+ const getFieldProps = react.useCallback(
558
+ (name) => {
559
+ const handlers = getHandlers(name);
560
+ return {
561
+ name,
562
+ value: values[name],
563
+ errors: meta.errors[name] ?? NO_ERRORS,
564
+ touched: meta.touched[name] ?? false,
565
+ dirty: dirty[name] ?? false,
566
+ setValue: handlers.setValue,
567
+ markTouched: handlers.markTouched
568
+ };
569
+ },
570
+ [values, meta.errors, meta.touched, dirty, getHandlers]
571
+ );
572
+ const state = react.useMemo(
573
+ () => ({
574
+ values,
575
+ touched: meta.touched,
576
+ dirty,
577
+ errors: meta.errors,
578
+ status
579
+ }),
580
+ [values, meta.touched, dirty, meta.errors, status]
581
+ );
582
+ return {
583
+ config: normalized,
584
+ state,
585
+ values,
586
+ errors: meta.errors,
587
+ touched: meta.touched,
588
+ dirty,
589
+ status,
590
+ isValid,
591
+ setFieldValue,
592
+ setValues,
593
+ reset,
594
+ validate,
595
+ handleSubmit,
596
+ getFieldProps
597
+ };
598
+ }
599
+ function FieldGrid({ columns, gap, className, children }) {
600
+ const style = {
601
+ "--orynn-cols-desktop": columns.desktop,
602
+ "--orynn-cols-tablet": columns.tablet,
603
+ "--orynn-cols-mobile": columns.mobile,
604
+ ...gap ? { "--orynn-grid-gap": gap } : {}
605
+ };
606
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("orynn-grid", className), style, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "orynn-grid__track", children }) });
607
+ }
608
+ function FieldGridItem({ span, columns, children }) {
609
+ if (!span) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
610
+ const style = {
611
+ "--orynn-span-desktop": clampSpan(span.desktop, columns.desktop),
612
+ "--orynn-span-tablet": clampSpan(span.tablet, columns.tablet),
613
+ "--orynn-span-mobile": clampSpan(span.mobile, columns.mobile)
614
+ };
615
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "orynn-grid__item", style, children });
616
+ }
617
+ var noop = () => {
618
+ };
619
+ function FieldRendererImpl({ field, registry: registry2, slice, disabled }) {
620
+ const registration = registry2.get(field.type);
621
+ invariant(
622
+ registration,
623
+ `No control registered for field type "${field.type}" (field "${field.name}").`
624
+ );
625
+ if (!registration) return null;
626
+ const Control = registration.component;
627
+ const required = field.validation?.required === true;
628
+ const isDisabled = disabled === true || field.disabled === true;
629
+ const value = registration.parseValue ? registration.parseValue(slice.value) : slice.value;
630
+ const messages = slice.errors.map((e) => e.message);
631
+ const handleChange = (next) => {
632
+ slice.setValue(registration.formatValue ? registration.formatValue(next) : next);
633
+ };
634
+ return /* @__PURE__ */ jsxRuntime.jsx(
635
+ Field,
636
+ {
637
+ label: field.label,
638
+ description: field.description,
639
+ error: messages,
640
+ required,
641
+ disabled: isDisabled,
642
+ children: ({ id, describedById, invalid }) => /* @__PURE__ */ jsxRuntime.jsx(
643
+ Control,
644
+ {
645
+ id,
646
+ name: field.name,
647
+ value,
648
+ onChange: handleChange,
649
+ onBlur: slice.markTouched,
650
+ onFocus: noop,
651
+ disabled: isDisabled,
652
+ readOnly: field.readOnly === true,
653
+ required,
654
+ invalid,
655
+ describedById,
656
+ config: field
657
+ }
658
+ )
659
+ }
660
+ );
661
+ }
662
+ var FieldRenderer = /* @__PURE__ */ react.memo(
663
+ FieldRendererImpl,
664
+ (prev, next) => prev.field === next.field && prev.registry === next.registry && prev.disabled === next.disabled && prev.slice.value === next.slice.value && prev.slice.touched === next.slice.touched && prev.slice.errors === next.slice.errors && prev.slice.setValue === next.slice.setValue && prev.slice.markTouched === next.slice.markTouched
665
+ );
666
+ var toStringValue = (value) => typeof value === "string" ? value : value == null ? "" : String(value);
667
+ var InputControl = ({
668
+ id,
669
+ name,
670
+ value,
671
+ onChange,
672
+ onBlur,
673
+ onFocus,
674
+ disabled,
675
+ readOnly,
676
+ required,
677
+ invalid,
678
+ describedById,
679
+ config
680
+ }) => {
681
+ const field = config;
682
+ return /* @__PURE__ */ jsxRuntime.jsx(
683
+ "input",
684
+ {
685
+ id,
686
+ name,
687
+ className: "orynn-input",
688
+ type: field.inputType ?? "text",
689
+ value: toStringValue(value),
690
+ placeholder: field.placeholder,
691
+ required,
692
+ disabled,
693
+ readOnly,
694
+ "aria-invalid": invalid || void 0,
695
+ "aria-describedby": describedById,
696
+ onChange: (event) => onChange(event.target.value, event),
697
+ onBlur,
698
+ onFocus,
699
+ ...field.props
700
+ }
701
+ );
702
+ };
703
+ var DropdownControl = ({
704
+ id,
705
+ name,
706
+ value,
707
+ onChange,
708
+ onBlur,
709
+ onFocus,
710
+ disabled,
711
+ required,
712
+ invalid,
713
+ describedById,
714
+ config
715
+ }) => {
716
+ const field = config;
717
+ return /* @__PURE__ */ jsxRuntime.jsxs(
718
+ "select",
719
+ {
720
+ id,
721
+ name,
722
+ className: "orynn-dropdown",
723
+ value: toStringValue(value),
724
+ required,
725
+ disabled,
726
+ "aria-invalid": invalid || void 0,
727
+ "aria-describedby": describedById,
728
+ onChange: (event) => onChange(event.target.value, event),
729
+ onBlur,
730
+ onFocus,
731
+ ...field.props,
732
+ children: [
733
+ field.placeholder != null && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: required, children: field.placeholder }),
734
+ field.options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value))
735
+ ]
736
+ }
737
+ );
738
+ };
739
+ function registerBuiltins(registry2) {
740
+ if (!registry2.has("input")) {
741
+ registry2.register("input", { component: InputControl, defaultValue: "" });
742
+ }
743
+ if (!registry2.has("dropdown")) {
744
+ registry2.register("dropdown", {
745
+ component: DropdownControl,
746
+ defaultValue: "",
747
+ validateConfig: (config) => Array.isArray(config.options) ? null : "a dropdown field requires an `options` array"
748
+ });
749
+ }
750
+ }
751
+ var seeded = false;
752
+ function ensureBuiltins(defaultRegistry2) {
753
+ if (seeded) return;
754
+ seeded = true;
755
+ registerBuiltins(defaultRegistry2);
756
+ }
757
+ function Fieldset(props) {
758
+ const {
759
+ config,
760
+ value,
761
+ defaultValue,
762
+ onChange,
763
+ onValidationChange,
764
+ resolver,
765
+ registry: registry2,
766
+ validateOn,
767
+ disabled,
768
+ className,
769
+ id
770
+ } = props;
771
+ ensureBuiltins(defaultRegistry);
772
+ const activeRegistry = registry2 ?? defaultRegistry;
773
+ const options = {
774
+ registry: activeRegistry,
775
+ ...value !== void 0 ? { value } : {},
776
+ ...defaultValue !== void 0 ? { defaultValue } : {},
777
+ ...onChange ? { onChange } : {},
778
+ ...onValidationChange ? { onValidationChange } : {},
779
+ ...resolver ? { resolver } : {},
780
+ ...validateOn ? { validateOn } : {}
781
+ };
782
+ const fs = useFieldsetState(config, options);
783
+ const { columns, gap, legend, fields, id: configId } = fs.config;
784
+ return /* @__PURE__ */ jsxRuntime.jsxs(
785
+ "fieldset",
786
+ {
787
+ className: cx("orynn-fieldset", className),
788
+ id: id ?? configId,
789
+ disabled: disabled || void 0,
790
+ children: [
791
+ legend != null && /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "orynn-fieldset__legend", children: legend }),
792
+ /* @__PURE__ */ jsxRuntime.jsx(FieldGrid, { columns, gap, children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(FieldGridItem, { span: field.layout, columns, children: /* @__PURE__ */ jsxRuntime.jsx(
793
+ FieldRenderer,
794
+ {
795
+ field,
796
+ registry: activeRegistry,
797
+ slice: fs.getFieldProps(field.name),
798
+ disabled: disabled === true
799
+ }
800
+ ) }, field.name)) })
801
+ ]
802
+ }
803
+ );
804
+ }
805
+
806
+ // src/types/validation.ts
807
+ var FORM_ERROR_KEY = "$form";
808
+
809
+ exports.Dropdown = Dropdown;
810
+ exports.FORM_ERROR_KEY = FORM_ERROR_KEY;
811
+ exports.Field = Field;
812
+ exports.Fieldset = Fieldset;
813
+ exports.Input = Input;
814
+ exports.createRegistry = createRegistry;
815
+ exports.createRuleResolver = createRuleResolver;
816
+ exports.defaultRegistry = defaultRegistry;
817
+ exports.defaultResolver = defaultResolver;
818
+ exports.normalizeConfig = normalizeConfig;
819
+ exports.registerField = registerField;
820
+ exports.registerRule = registerRule;
821
+ exports.useFieldsetState = useFieldsetState;
822
+ //# sourceMappingURL=index.cjs.map
823
+ //# sourceMappingURL=index.cjs.map