orynn 0.2.0 → 0.5.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.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createContext, forwardRef, useContext, useId, useRef, useEffect, useCallback, useMemo, useState, useImperativeHandle, useLayoutEffect, useReducer, memo } from 'react';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { createPortal } from 'react-dom';
4
+ import { Dialog as Dialog$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, Slot, Label as Label$1, Separator as Separator$1, Switch as Switch$1, Tabs as Tabs$1, Tooltip as Tooltip$1 } from 'radix-ui';
4
5
 
5
6
  var __typeError = (msg) => {
6
7
  throw TypeError(msg);
@@ -28,8 +29,8 @@ var base = (props) => ({
28
29
  });
29
30
  var CheckIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base(p), children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
30
31
  var MinusIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base(p), children: /* @__PURE__ */ jsx("path", { d: "M5 12h14" }) });
31
- var PlusIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base(p), children: /* @__PURE__ */ jsx("path", { d: "M12 5v14M5 12h14" }) });
32
32
  var ChevronDownIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base(p), children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) });
33
+ var ChevronUpIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base(p), children: /* @__PURE__ */ jsx("path", { d: "m6 15 6-6 6 6" }) });
33
34
  var ChevronLeftIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base(p), children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) });
34
35
  var ChevronRightIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base(p), children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
35
36
  var XIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base(p), children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
@@ -46,11 +47,15 @@ var CalendarIcon = (p) => /* @__PURE__ */ jsxs("svg", { ...base(p), children: [
46
47
  /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
47
48
  /* @__PURE__ */ jsx("path", { d: "M16 2v4M8 2v4M3 10h18" })
48
49
  ] });
50
+ var CircleIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base({ strokeWidth: 0, fill: "currentColor", ...p }), children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "6" }) });
49
51
  var CheckboxGroupContext = createContext(null);
50
52
  var sizePx = {
51
- sm: "0.95rem",
52
- md: "1.125rem",
53
- lg: "1.35rem"
53
+ sm: "0.875rem",
54
+ // 14px
55
+ md: "1rem",
56
+ // 16px — shadcn size-4
57
+ lg: "1.125rem"
58
+ // 18px
54
59
  };
55
60
  var Checkbox = /* @__PURE__ */ forwardRef(
56
61
  function Checkbox2(props, ref) {
@@ -208,15 +213,7 @@ function Field(props) {
208
213
  description != null && /* @__PURE__ */ jsx("p", { className: "orynn-field__description", id: descriptionId, children: description }),
209
214
  /* @__PURE__ */ jsx("div", { className: "orynn-field__control", children: typeof children === "function" ? children(ctx) : children }),
210
215
  invalid && /* @__PURE__ */ jsx("p", { className: "orynn-field__error", id: errorId, role: "alert", children: messages.length === 1 ? messages[0] : messages.map((m) => /* @__PURE__ */ jsx("span", { children: m }, m)) }),
211
- successMsg && /* @__PURE__ */ jsx(
212
- "p",
213
- {
214
- className: "orynn-field__error",
215
- id: successId,
216
- style: { color: "var(--orynn-color-success)" },
217
- children: successMsg
218
- }
219
- )
216
+ successMsg && /* @__PURE__ */ jsx("p", { className: "orynn-field__success", id: successId, children: successMsg })
220
217
  ]
221
218
  }
222
219
  );
@@ -235,8 +232,11 @@ function CheckboxGroup(props) {
235
232
  defaultValue,
236
233
  onChange,
237
234
  orientation = "vertical",
235
+ columns,
238
236
  min,
239
237
  max,
238
+ showSelectAll = false,
239
+ selectAllLabel = "Select all",
240
240
  options,
241
241
  children,
242
242
  className,
@@ -261,6 +261,19 @@ function CheckboxGroup(props) {
261
261
  },
262
262
  [value, setValue, onChange]
263
263
  );
264
+ const selectableValues = (options ?? []).filter((o) => !o.disabled).map((o) => o.value);
265
+ const allSelected = selectableValues.length > 0 && selectableValues.every((v) => value.includes(v));
266
+ const someSelected = selectableValues.some((v) => value.includes(v));
267
+ const toggleAll = () => {
268
+ if (allSelected) {
269
+ setValue([]);
270
+ onChange?.([]);
271
+ } else {
272
+ const next = max != null ? selectableValues.slice(0, max) : selectableValues;
273
+ setValue(next);
274
+ onChange?.(next);
275
+ }
276
+ };
264
277
  const ctx = useMemo(
265
278
  () => ({
266
279
  name,
@@ -284,23 +297,40 @@ function CheckboxGroup(props) {
284
297
  disabled,
285
298
  id,
286
299
  className,
287
- children: () => /* @__PURE__ */ jsx(CheckboxGroupContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
300
+ children: () => /* @__PURE__ */ jsx(CheckboxGroupContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs(
288
301
  "div",
289
302
  {
290
303
  role: "group",
291
304
  "aria-labelledby": label != null ? `${id}-label` : void 0,
292
305
  className: cx("orynn-choice-group"),
293
- "data-orientation": orientation,
294
- children: options ? options.map((o) => /* @__PURE__ */ jsx(
295
- Checkbox,
296
- {
297
- value: o.value,
298
- label: o.label,
299
- description: o.description,
300
- disabled: o.disabled
301
- },
302
- o.value
303
- )) : children
306
+ "data-orientation": columns ? void 0 : orientation,
307
+ "data-columns": columns ? "" : void 0,
308
+ style: columns ? { "--orynn-choice-columns": columns } : void 0,
309
+ children: [
310
+ showSelectAll && selectableValues.length > 0 && /* @__PURE__ */ jsx("div", { className: "orynn-choice-group__selectall", children: /* @__PURE__ */ jsx(
311
+ Checkbox,
312
+ {
313
+ checked: allSelected,
314
+ indeterminate: !allSelected && someSelected,
315
+ disabled,
316
+ label: selectAllLabel,
317
+ onChange: toggleAll
318
+ }
319
+ ) }),
320
+ options ? options.map((o) => /* @__PURE__ */ jsx(
321
+ Checkbox,
322
+ {
323
+ value: o.value,
324
+ label: o.icon != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
325
+ /* @__PURE__ */ jsx("span", { className: "orynn-choice__icon", children: o.icon }),
326
+ o.label
327
+ ] }) : o.label,
328
+ description: o.description,
329
+ disabled: o.disabled
330
+ },
331
+ o.value
332
+ )) : children
333
+ ]
304
334
  }
305
335
  ) })
306
336
  }
@@ -321,6 +351,8 @@ var FieldBox = /* @__PURE__ */ forwardRef(
321
351
  floatingLabel,
322
352
  left,
323
353
  right,
354
+ addonBefore,
355
+ addonAfter,
324
356
  textarea,
325
357
  className,
326
358
  children,
@@ -340,7 +372,8 @@ var FieldBox = /* @__PURE__ */ forwardRef(
340
372
  }
341
373
  onBlur?.(e);
342
374
  };
343
- return /* @__PURE__ */ jsxs(
375
+ const hasAddon = addonBefore != null || addonAfter != null;
376
+ const box = /* @__PURE__ */ jsxs(
344
377
  "div",
345
378
  {
346
379
  ref,
@@ -359,6 +392,8 @@ var FieldBox = /* @__PURE__ */ forwardRef(
359
392
  "data-focused": isFocused || void 0,
360
393
  "data-active": active || isFocused || void 0,
361
394
  "data-open": open || void 0,
395
+ "data-addon-before": addonBefore != null || void 0,
396
+ "data-addon-after": addonAfter != null || void 0,
362
397
  onFocus: handleFocus,
363
398
  onBlur: handleBlur,
364
399
  ...rest,
@@ -370,8 +405,137 @@ var FieldBox = /* @__PURE__ */ forwardRef(
370
405
  ]
371
406
  }
372
407
  );
408
+ if (!hasAddon) return box;
409
+ return /* @__PURE__ */ jsxs("div", { className: "orynn-box__group", "data-size": size, "data-disabled": disabled || void 0, children: [
410
+ addonBefore != null && /* @__PURE__ */ jsx("span", { className: "orynn-box__addon", "data-side": "before", children: addonBefore }),
411
+ box,
412
+ addonAfter != null && /* @__PURE__ */ jsx("span", { className: "orynn-box__addon", "data-side": "after", children: addonAfter })
413
+ ] });
373
414
  }
374
415
  );
416
+
417
+ // src/theme/theme.ts
418
+ var len = (v) => typeof v === "number" ? `${v}px` : v;
419
+ var RADIUS_KEYWORDS = {
420
+ none: "0px",
421
+ sm: "0.375rem",
422
+ md: "0.5rem",
423
+ lg: "0.625rem",
424
+ xl: "0.875rem",
425
+ pill: "9999px"
426
+ };
427
+ var DARK_FG = "#0b0d12";
428
+ var LIGHT_FG = "#ffffff";
429
+ function contrastFor(color) {
430
+ const c = color.trim().toLowerCase();
431
+ const hex = /^#?([\da-f]{3}|[\da-f]{6})$/.exec(c)?.[1];
432
+ if (hex) {
433
+ const full = hex.length === 3 ? hex.replace(/./g, "$&$&") : hex;
434
+ const n = Number.parseInt(full, 16);
435
+ const toLin = (v) => {
436
+ const x = v / 255;
437
+ return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
438
+ };
439
+ const lum = 0.2126 * toLin(n >> 16 & 255) + 0.7152 * toLin(n >> 8 & 255) + 0.0722 * toLin(n & 255);
440
+ return lum > 0.42 ? DARK_FG : LIGHT_FG;
441
+ }
442
+ const okl = /^oklch\(\s*([\d.]+)(%?)/.exec(c);
443
+ if (okl) {
444
+ const l = Number.parseFloat(okl[1]) / (okl[2] ? 100 : 1);
445
+ return l >= 0.62 ? DARK_FG : LIGHT_FG;
446
+ }
447
+ const hsl = /^hsla?\(\s*[\d.]+(?:deg)?\s*[, ]\s*[\d.]+%\s*[, ]\s*([\d.]+)%/.exec(c)?.[1];
448
+ if (hsl) return Number.parseFloat(hsl) >= 60 ? DARK_FG : LIGHT_FG;
449
+ if (c === "white") return DARK_FG;
450
+ if (c === "black") return LIGHT_FG;
451
+ return LIGHT_FG;
452
+ }
453
+ function normalizeTheme(input) {
454
+ return typeof input === "string" ? { preset: input } : input ?? {};
455
+ }
456
+ function resolveThemeVars(config) {
457
+ const vars = {};
458
+ const primary = config.primary ?? config.accent;
459
+ if (primary) {
460
+ const p = primary;
461
+ vars["--orynn-color-primary"] = p;
462
+ vars["--orynn-color-primary-foreground"] = config.primaryForeground ?? config.accentContrast ?? contrastFor(p);
463
+ if (!config.ring) vars["--orynn-color-ring"] = p;
464
+ }
465
+ if (config.ring) vars["--orynn-color-ring"] = config.ring;
466
+ if (config.font) vars["--orynn-font-family"] = config.font;
467
+ if (config.fontSize != null) vars["--orynn-font-size"] = len(config.fontSize);
468
+ if (config.controlWidth != null) {
469
+ vars["--orynn-control-width"] = len(config.controlWidth);
470
+ vars["--orynn-control-max-width"] = "none";
471
+ }
472
+ if (config.controlMaxWidth != null)
473
+ vars["--orynn-control-max-width"] = len(config.controlMaxWidth);
474
+ if (config.radius != null) {
475
+ const r = config.radius;
476
+ vars["--orynn-radius"] = typeof r === "string" && r in RADIUS_KEYWORDS ? RADIUS_KEYWORDS[r] : len(r);
477
+ }
478
+ Object.assign(vars, config.vars ?? {});
479
+ return vars;
480
+ }
481
+ var OrynnContext = createContext({
482
+ preset: "default",
483
+ density: "comfortable",
484
+ config: {},
485
+ portalContainer: null
486
+ });
487
+ function syncThemeEl(el, preset, density, config, style) {
488
+ const attr = (name, value) => value == null ? el.removeAttribute(name) : el.setAttribute(name, value);
489
+ attr("data-orynn-theme", preset === "default" ? null : preset);
490
+ attr("data-orynn-density", density === "comfortable" ? null : density);
491
+ attr("data-orynn-color-scheme", config.colorScheme ?? null);
492
+ el.classList.toggle("dark", config.colorScheme === "dark");
493
+ el.removeAttribute("style");
494
+ for (const [k, v] of Object.entries(style)) {
495
+ if (v != null) el.style.setProperty(k, String(v));
496
+ }
497
+ }
498
+ function OrynnProvider({ theme, as = "div", className, children }) {
499
+ const config = useMemo(() => normalizeTheme(theme), [theme]);
500
+ const preset = config.preset ?? "default";
501
+ const density = config.density ?? "comfortable";
502
+ const style = useMemo(() => resolveThemeVars(config), [config]);
503
+ const [portalContainer, setPortalContainer] = useState(null);
504
+ useEffect(() => {
505
+ if (typeof document === "undefined") return;
506
+ const el = document.createElement("div");
507
+ el.className = "orynn-portal";
508
+ document.body.appendChild(el);
509
+ setPortalContainer(el);
510
+ return () => {
511
+ el.remove();
512
+ setPortalContainer(null);
513
+ };
514
+ }, []);
515
+ useEffect(() => {
516
+ if (portalContainer)
517
+ syncThemeEl(portalContainer, preset, density, config, style);
518
+ }, [portalContainer, preset, density, config, style]);
519
+ const ctx = useMemo(
520
+ () => ({ preset, density, config, portalContainer }),
521
+ [preset, density, config, portalContainer]
522
+ );
523
+ const dataProps = {
524
+ "data-orynn-theme": preset === "default" ? void 0 : preset,
525
+ "data-orynn-density": density === "comfortable" ? void 0 : density,
526
+ "data-orynn-color-scheme": config.colorScheme
527
+ };
528
+ const darkClass = config.colorScheme === "dark" ? "dark" : void 0;
529
+ return /* @__PURE__ */ jsx(OrynnContext.Provider, { value: ctx, children: as === "contents" ? /* @__PURE__ */ jsx("div", { className: darkClass, style: { display: "contents", ...style }, ...dataProps, children }) : as === "span" ? /* @__PURE__ */ jsx("span", { className: cx("orynn-root", className, darkClass), style, ...dataProps, children }) : /* @__PURE__ */ jsx("div", { className: cx("orynn-root", className, darkClass), style, ...dataProps, children }) });
530
+ }
531
+ function useOrynnTheme() {
532
+ return useContext(OrynnContext);
533
+ }
534
+ function usePortalContainer() {
535
+ const { portalContainer } = useContext(OrynnContext);
536
+ if (portalContainer) return portalContainer;
537
+ return typeof document === "undefined" ? null : document.body;
538
+ }
375
539
  var useIsoLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
376
540
  function Popover({
377
541
  open,
@@ -380,6 +544,7 @@ function Popover({
380
544
  children,
381
545
  matchWidth = true,
382
546
  offset = 6,
547
+ maxHeight,
383
548
  className,
384
549
  popoverRef,
385
550
  role,
@@ -391,6 +556,7 @@ function Popover({
391
556
  if (popoverRef) popoverRef.current = el;
392
557
  };
393
558
  const [pos, setPos] = useState(null);
559
+ const container = usePortalContainer();
394
560
  useIsoLayoutEffect(() => {
395
561
  if (!open) return;
396
562
  const anchor = anchorRef.current;
@@ -402,12 +568,13 @@ function Popover({
402
568
  const above = r.top - offset;
403
569
  const wanted = innerRef.current?.scrollHeight ?? 280;
404
570
  const placeTop = below < Math.min(wanted, 240) && above > below;
571
+ const avail = Math.max(140, (placeTop ? above : below) - 4);
405
572
  setPos({
406
573
  left: r.left,
407
574
  top: placeTop ? r.top - offset : r.bottom + offset,
408
575
  width: r.width,
409
576
  placement: placeTop ? "top" : "bottom",
410
- maxHeight: Math.max(140, (placeTop ? above : below) - 4)
577
+ maxHeight: maxHeight != null ? Math.min(maxHeight, avail) : avail
411
578
  });
412
579
  };
413
580
  update();
@@ -420,7 +587,7 @@ function Popover({
420
587
  window.removeEventListener("scroll", update, true);
421
588
  window.removeEventListener("resize", update);
422
589
  };
423
- }, [open, anchorRef, offset]);
590
+ }, [open, anchorRef, offset, maxHeight]);
424
591
  useEffect(() => {
425
592
  if (!open) return;
426
593
  const onPointerDown = (e) => {
@@ -441,7 +608,7 @@ function Popover({
441
608
  document.removeEventListener("keydown", onKey, true);
442
609
  };
443
610
  }, [open, anchorRef, onClose]);
444
- if (!open || typeof document === "undefined" || !pos) return null;
611
+ if (!open || !pos || !container) return null;
445
612
  return createPortal(
446
613
  /* @__PURE__ */ jsx(
447
614
  "div",
@@ -461,7 +628,7 @@ function Popover({
461
628
  children
462
629
  }
463
630
  ),
464
- document.body
631
+ container
465
632
  );
466
633
  }
467
634
 
@@ -486,27 +653,82 @@ var clampDate = (d, min, max) => {
486
653
  return d;
487
654
  };
488
655
  var pad = (n, len2 = 2) => String(n).padStart(len2, "0");
489
- function formatDate(d, fmt, locale) {
490
- const monthLong = new Intl.DateTimeFormat(locale, { month: "long" }).format(d);
491
- const monthShort = new Intl.DateTimeFormat(locale, { month: "short" }).format(d);
492
- return fmt.replace(/yyyy/g, String(d.getFullYear())).replace(/MMMM/g, monthLong).replace(/MMM/g, monthShort).replace(/MM/g, pad(d.getMonth() + 1)).replace(/dd/g, pad(d.getDate())).replace(/\bM\b/g, String(d.getMonth() + 1)).replace(/\bd\b/g, String(d.getDate()));
493
- }
494
- function parseDate(input, fmt) {
495
- const nums = input.match(/\d+/g);
496
- if (!nums) return null;
497
- const order = (fmt.match(/yyyy|MM|dd|M|d/g) ?? ["yyyy", "MM", "dd"]).map((t) => t[0]);
656
+ var FORMAT_TOKENS = /(?<![A-Za-z])(yyyy|yy|MMMM|MMM|MM|M|dd|d|EEEE|EEE|HH|H|hh|h|mm|m|ss|s|aa|a|A)(?![A-Za-z])/g;
657
+ function formatDate(d, fmt = "yyyy-MM-dd", locale) {
658
+ if (typeof fmt !== "string") return new Intl.DateTimeFormat(locale, fmt).format(d);
659
+ const h12 = d.getHours() % 12 || 12;
660
+ const map = {
661
+ yyyy: String(d.getFullYear()),
662
+ yy: pad(d.getFullYear() % 100),
663
+ MMMM: new Intl.DateTimeFormat(locale, { month: "long" }).format(d),
664
+ MMM: new Intl.DateTimeFormat(locale, { month: "short" }).format(d),
665
+ MM: pad(d.getMonth() + 1),
666
+ M: String(d.getMonth() + 1),
667
+ dd: pad(d.getDate()),
668
+ d: String(d.getDate()),
669
+ EEEE: new Intl.DateTimeFormat(locale, { weekday: "long" }).format(d),
670
+ EEE: new Intl.DateTimeFormat(locale, { weekday: "short" }).format(d),
671
+ HH: pad(d.getHours()),
672
+ H: String(d.getHours()),
673
+ hh: pad(h12),
674
+ h: String(h12),
675
+ mm: pad(d.getMinutes()),
676
+ m: String(d.getMinutes()),
677
+ ss: pad(d.getSeconds()),
678
+ s: String(d.getSeconds()),
679
+ aa: d.getHours() < 12 ? "am" : "pm",
680
+ a: d.getHours() < 12 ? "am" : "pm",
681
+ A: d.getHours() < 12 ? "AM" : "PM"
682
+ };
683
+ return fmt.replace(FORMAT_TOKENS, (t) => map[t] ?? t);
684
+ }
685
+ function matchMonthName(input, locale) {
686
+ for (let m = 0; m < 12; m += 1) {
687
+ for (const style of ["long", "short"]) {
688
+ const name = new Intl.DateTimeFormat(locale, { month: style }).format(new Date(2021, m, 1));
689
+ const i = input.toLowerCase().indexOf(name.toLowerCase());
690
+ if (i !== -1) return { month: m, rest: input.slice(0, i) + input.slice(i + name.length) };
691
+ }
692
+ }
693
+ return null;
694
+ }
695
+ function parseDate(input, fmt = "yyyy-MM-dd", locale) {
696
+ const pattern = typeof fmt === "string" ? fmt : "yyyy-MM-dd";
697
+ let text = input;
698
+ let monthFromName;
699
+ if (/MMM/.test(pattern)) {
700
+ const hit = matchMonthName(text, locale);
701
+ if (hit) {
702
+ monthFromName = hit.month;
703
+ text = hit.rest;
704
+ }
705
+ }
706
+ const nums = text.match(/\d+/g);
707
+ if (!nums && monthFromName === void 0) return null;
708
+ const order = (pattern.match(/yyyy|yy|MM|dd|HH|hh|mm|ss|M|d|H|h|m|s/g) ?? ["yyyy", "MM", "dd"]).map((t) => t.length === 2 ? t.charAt(0) + t.charAt(0) : t);
498
709
  let y;
499
- let m;
500
- let dd;
501
- order.forEach((t, i) => {
502
- const n = Number(nums[i]);
503
- if (Number.isNaN(n)) return;
504
- if (t === "y") y = n < 100 ? 2e3 + n : n;
505
- else if (t === "M") m = n - 1;
506
- else if (t === "d") dd = n;
507
- });
508
- if (y == null || m == null || dd == null) return null;
509
- const d = new Date(y, m, dd);
710
+ let mo = monthFromName;
711
+ let day;
712
+ let hh = 0;
713
+ let mm = 0;
714
+ let ss = 0;
715
+ let ni = 0;
716
+ for (const tok of order) {
717
+ if ((tok === "MM" || tok === "M") && monthFromName !== void 0) continue;
718
+ const n = Number((nums ?? [])[ni]);
719
+ ni += 1;
720
+ if (Number.isNaN(n)) continue;
721
+ if (tok === "yyyy" || tok === "yy") y = n < 100 ? 2e3 + n : n;
722
+ else if (tok === "MM" || tok === "M") mo = n - 1;
723
+ else if (tok === "dd" || tok === "d") day = n;
724
+ else if (tok === "HH" || tok === "H" || tok === "hh" || tok === "h") hh = n;
725
+ else if (tok === "mm" || tok === "m") mm = n;
726
+ else if (tok === "ss" || tok === "s") ss = n;
727
+ }
728
+ if (/pm/i.test(input) && hh < 12) hh += 12;
729
+ if (/am/i.test(input) && hh === 12) hh = 0;
730
+ if (y == null && day == null && mo == null) return null;
731
+ const d = new Date(y ?? (/* @__PURE__ */ new Date()).getFullYear(), mo ?? 0, day ?? 1, hh, mm, ss);
510
732
  return Number.isNaN(d.getTime()) ? null : d;
511
733
  }
512
734
  function toDate(v) {
@@ -515,11 +737,14 @@ function toDate(v) {
515
737
  const d = new Date(v);
516
738
  return Number.isNaN(d.getTime()) ? null : startOfDay(d);
517
739
  }
518
- function monthMatrix(viewYear, viewMonth, firstDayOfWeek) {
740
+ function monthMatrix(viewYear, viewMonth, firstDayOfWeek, fixedWeeks = true) {
519
741
  const first = new Date(viewYear, viewMonth, 1);
520
742
  const offset = (first.getDay() - firstDayOfWeek + 7) % 7;
521
743
  const start = addDays(first, -offset);
522
- return Array.from({ length: 42 }, (_, i) => addDays(start, i));
744
+ if (fixedWeeks) return Array.from({ length: 42 }, (_, i) => addDays(start, i));
745
+ const last = new Date(viewYear, viewMonth + 1, 0);
746
+ const used = Math.ceil((offset + last.getDate()) / 7);
747
+ return Array.from({ length: used * 7 }, (_, i) => addDays(start, i));
523
748
  }
524
749
  function weekdayLabels(firstDayOfWeek, locale) {
525
750
  const fmt = new Intl.DateTimeFormat(locale, { weekday: "short" });
@@ -528,6 +753,10 @@ function weekdayLabels(firstDayOfWeek, locale) {
528
753
  (_, i) => fmt.format(new Date(2023, 0, 1 + (firstDayOfWeek + i) % 7))
529
754
  );
530
755
  }
756
+ function decadeGrid(year) {
757
+ const start = Math.floor(year / 10) * 10;
758
+ return { start, years: Array.from({ length: 12 }, (_, i) => start - 1 + i) };
759
+ }
531
760
  function isoWeek(d) {
532
761
  const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
533
762
  const dayNum = (date.getUTCDay() + 6) % 7;
@@ -536,9 +765,40 @@ function isoWeek(d) {
536
765
  const diff = date.getTime() - firstThursday.getTime();
537
766
  return 1 + Math.round(diff / (7 * 24 * 3600 * 1e3));
538
767
  }
768
+
769
+ // src/controls/DatePicker/calendar-range.ts
770
+ function normalizeRange(r) {
771
+ if (r.start && r.end && startOfDay(r.start) > startOfDay(r.end)) {
772
+ return { start: r.end, end: r.start };
773
+ }
774
+ return r;
775
+ }
776
+ function isRangeComplete(r) {
777
+ return !!r && !!r.start && !!r.end;
778
+ }
779
+ function isWithinRange(d, r) {
780
+ if (!isRangeComplete(r)) return false;
781
+ const t = startOfDay(d).getTime();
782
+ return t > startOfDay(r.start).getTime() && t < startOfDay(r.end).getTime();
783
+ }
784
+ var isRangeStart = (d, r) => !!r && isSameDay(d, r.start);
785
+ var isRangeEnd = (d, r) => !!r && isSameDay(d, r.end);
786
+ function previewRange(anchor, hover) {
787
+ if (!anchor || !hover) return null;
788
+ return normalizeRange({ start: anchor, end: hover });
789
+ }
790
+ function nextRange(current, d) {
791
+ if (!current.start || current.start && current.end) return { start: startOfDay(d), end: null };
792
+ return normalizeRange({ start: current.start, end: startOfDay(d) });
793
+ }
539
794
  function Calendar({
540
795
  value,
541
796
  onSelect,
797
+ mode = "single",
798
+ selectedDates,
799
+ range,
800
+ previewRange: previewRange2,
801
+ onHoverDate,
542
802
  minDate,
543
803
  maxDate,
544
804
  isDisabled,
@@ -548,16 +808,33 @@ function Calendar({
548
808
  showToday,
549
809
  showClear,
550
810
  onClear,
551
- autoFocus
811
+ autoFocus,
812
+ view: viewProp,
813
+ defaultView,
814
+ onViewChange,
815
+ precision = "day",
816
+ fixedWeeks = true,
817
+ defaultMonth,
818
+ numberOfMonths = 1
552
819
  }) {
820
+ const panels = Math.max(1, Math.floor(numberOfMonths));
553
821
  const today = startOfDay(/* @__PURE__ */ new Date());
554
- const initial = value ?? clampDate(today, minDate, maxDate);
555
- const [view, setView] = useState({ y: initial.getFullYear(), m: initial.getMonth() });
822
+ const anchor = value ?? (mode === "range" ? range?.start ?? range?.end ?? null : null) ?? (mode === "multiple" ? selectedDates?.[0] ?? null : null) ?? defaultMonth ?? null;
823
+ const initial = anchor ?? clampDate(today, minDate, maxDate);
824
+ const [nav, setNav] = useState({ y: initial.getFullYear(), m: initial.getMonth() });
556
825
  const [focused, setFocused] = useState(initial);
826
+ const [innerView, setInnerView] = useState(
827
+ defaultView ?? (precision === "day" ? "day" : precision)
828
+ );
829
+ const view = viewProp ?? innerView;
830
+ const setView = (v) => {
831
+ if (viewProp === void 0) setInnerView(v);
832
+ onViewChange?.(v);
833
+ };
557
834
  const gridRef = useRef(null);
558
835
  useEffect(() => {
559
836
  if (value) {
560
- setView({ y: value.getFullYear(), m: value.getMonth() });
837
+ setNav({ y: value.getFullYear(), m: value.getMonth() });
561
838
  setFocused(value);
562
839
  }
563
840
  }, [value]);
@@ -566,94 +843,118 @@ function Calendar({
566
843
  gridRef.current?.querySelector('[data-focused="true"]')?.focus();
567
844
  }
568
845
  }, [autoFocus]);
569
- const cells = monthMatrix(view.y, view.m, firstDayOfWeek);
570
- const weekdays = weekdayLabels(firstDayOfWeek, locale);
571
- const monthName = new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(
572
- new Date(view.y, view.m, 1)
573
- );
574
846
  const outOfRange = (d) => minDate != null && d < startOfDay(minDate) || maxDate != null && d > startOfDay(maxDate) || Boolean(isDisabled?.(d));
575
- const goto = (d) => {
576
- const c = clampDate(d, minDate, maxDate);
577
- setFocused(c);
578
- setView({ y: c.getFullYear(), m: c.getMonth() });
847
+ const monthOutOfRange = (y, m) => {
848
+ const first = new Date(y, m, 1);
849
+ const last = new Date(y, m + 1, 0);
850
+ return maxDate != null && first > startOfDay(maxDate) || minDate != null && last < startOfDay(minDate);
851
+ };
852
+ const yearOutOfRange = (y) => maxDate != null && new Date(y, 0, 1) > startOfDay(maxDate) || minDate != null && new Date(y, 11, 31) < startOfDay(minDate);
853
+ const focusGrid = () => {
579
854
  requestAnimationFrame(() => {
580
855
  gridRef.current?.querySelector('[data-focused="true"]')?.focus();
581
856
  });
582
857
  };
583
- const onKeyDown = (e) => {
584
- const k = e.key;
585
- let next = null;
586
- if (k === "ArrowLeft") next = addDays(focused, -1);
587
- else if (k === "ArrowRight") next = addDays(focused, 1);
588
- else if (k === "ArrowUp") next = addDays(focused, -7);
589
- else if (k === "ArrowDown") next = addDays(focused, 7);
590
- else if (k === "Home") next = addDays(focused, -((focused.getDay() - firstDayOfWeek + 7) % 7));
591
- else if (k === "End")
592
- next = addDays(focused, 6 - (focused.getDay() - firstDayOfWeek + 7) % 7);
593
- else if (k === "PageUp") next = addMonths(focused, e.shiftKey ? -12 : -1);
594
- else if (k === "PageDown") next = addMonths(focused, e.shiftKey ? 12 : 1);
595
- else if (k === "Enter" || k === " ") {
596
- e.preventDefault();
597
- if (!outOfRange(focused)) onSelect(focused);
598
- return;
599
- } else return;
600
- e.preventDefault();
601
- goto(next);
858
+ const navIndex = nav.y * 12 + nav.m;
859
+ const goto = (d) => {
860
+ const c = clampDate(d, minDate, maxDate);
861
+ setFocused(c);
862
+ const ci = c.getFullYear() * 12 + c.getMonth();
863
+ if (ci < navIndex || ci > navIndex + panels - 1) {
864
+ setNav({ y: c.getFullYear(), m: c.getMonth() });
865
+ }
866
+ focusGrid();
602
867
  };
603
- const years = [];
604
- const minY = minDate ? minDate.getFullYear() : view.y - 10;
605
- const maxY = maxDate ? maxDate.getFullYear() : view.y + 10;
606
- for (let y = minY; y <= maxY; y += 1) years.push(y);
607
- return /* @__PURE__ */ jsxs("div", { className: "orynn-calendar", children: [
608
- /* @__PURE__ */ jsxs("div", { className: "orynn-calendar__header", children: [
609
- /* @__PURE__ */ jsx(
610
- "button",
611
- {
612
- type: "button",
613
- className: "orynn-calendar__nav",
614
- "aria-label": "Previous month",
615
- onClick: () => setView((v) => ({ y: v.m === 0 ? v.y - 1 : v.y, m: (v.m + 11) % 12 })),
616
- children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
617
- }
618
- ),
619
- /* @__PURE__ */ jsxs("div", { className: "orynn-calendar__title", children: [
620
- /* @__PURE__ */ jsx(
621
- "select",
622
- {
623
- className: "orynn-calendar__select",
624
- "aria-label": "Month",
625
- value: view.m,
626
- onChange: (e) => setView((v) => ({ ...v, m: Number(e.target.value) })),
627
- children: Array.from({ length: 12 }, (_, i) => /* @__PURE__ */ jsx("option", { value: i, children: new Intl.DateTimeFormat(locale, { month: "long" }).format(new Date(2023, i, 1)) }, i))
628
- }
629
- ),
630
- /* @__PURE__ */ jsx(
631
- "select",
632
- {
633
- className: "orynn-calendar__select",
634
- "aria-label": "Year",
635
- value: view.y,
636
- onChange: (e) => setView((v) => ({ ...v, y: Number(e.target.value) })),
637
- children: years.map((y) => /* @__PURE__ */ jsx("option", { value: y, children: y }, y))
638
- }
639
- )
640
- ] }),
641
- /* @__PURE__ */ jsx(
642
- "button",
643
- {
644
- type: "button",
645
- className: "orynn-calendar__nav",
646
- "aria-label": "Next month",
647
- onClick: () => setView((v) => ({ y: v.m === 11 ? v.y + 1 : v.y, m: (v.m + 1) % 12 })),
648
- children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
649
- }
650
- )
651
- ] }),
652
- " ",
653
- /* @__PURE__ */ jsxs(
868
+ const canDrillUp = view !== "year";
869
+ const dayTitle = () => {
870
+ const first = new Date(nav.y, nav.m, 1);
871
+ if (panels <= 1) {
872
+ return new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(first);
873
+ }
874
+ const last = new Date(nav.y, nav.m + panels - 1, 1);
875
+ const f = new Intl.DateTimeFormat(locale, { month: "short" }).format(first);
876
+ const l = new Intl.DateTimeFormat(locale, { month: "short", year: "numeric" }).format(last);
877
+ return first.getFullYear() === last.getFullYear() ? `${f} \u2013 ${l}` : `${f} ${first.getFullYear()} \u2013 ${l}`;
878
+ };
879
+ const titleText = view === "day" ? dayTitle() : view === "month" ? String(nav.y) : (() => {
880
+ const g = decadeGrid(nav.y);
881
+ return `${g.start} \u2013 ${g.start + 9}`;
882
+ })();
883
+ const step = (dir) => {
884
+ if (view === "day") setNav((v) => ({ ...addMonthNav(v, dir) }));
885
+ else if (view === "month") setNav((v) => ({ ...v, y: v.y + dir }));
886
+ else setNav((v) => ({ ...v, y: v.y + dir * 10 }));
887
+ };
888
+ const header = /* @__PURE__ */ jsxs("div", { className: "orynn-calendar__header", children: [
889
+ /* @__PURE__ */ jsx(
890
+ "button",
891
+ {
892
+ type: "button",
893
+ className: "orynn-calendar__nav",
894
+ "aria-label": view === "day" ? "Previous month" : view === "month" ? "Previous year" : "Previous decade",
895
+ onClick: () => step(-1),
896
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
897
+ }
898
+ ),
899
+ /* @__PURE__ */ jsx(
900
+ "button",
901
+ {
902
+ type: "button",
903
+ className: "orynn-calendar__title",
904
+ "aria-live": "polite",
905
+ disabled: !canDrillUp,
906
+ onClick: () => {
907
+ if (!canDrillUp) return;
908
+ setView(view === "day" ? "month" : "year");
909
+ },
910
+ children: titleText
911
+ }
912
+ ),
913
+ /* @__PURE__ */ jsx(
914
+ "button",
915
+ {
916
+ type: "button",
917
+ className: "orynn-calendar__nav",
918
+ "aria-label": view === "day" ? "Next month" : view === "month" ? "Next year" : "Next decade",
919
+ onClick: () => step(1),
920
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
921
+ }
922
+ )
923
+ ] });
924
+ const dayGrid = (monthOffset = 0) => {
925
+ const panelMonth = new Date(nav.y, nav.m + monthOffset, 1);
926
+ const py = panelMonth.getFullYear();
927
+ const pm = panelMonth.getMonth();
928
+ const cells = monthMatrix(py, pm, firstDayOfWeek, fixedWeeks);
929
+ const weeks = cells.length / 7;
930
+ const weekdays = weekdayLabels(firstDayOfWeek, locale);
931
+ const monthName = new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(
932
+ panelMonth
933
+ );
934
+ const onKeyDown = (e) => {
935
+ const k = e.key;
936
+ let next = null;
937
+ if (k === "ArrowLeft") next = addDays(focused, -1);
938
+ else if (k === "ArrowRight") next = addDays(focused, 1);
939
+ else if (k === "ArrowUp") next = addDays(focused, -7);
940
+ else if (k === "ArrowDown") next = addDays(focused, 7);
941
+ else if (k === "Home")
942
+ next = addDays(focused, -((focused.getDay() - firstDayOfWeek + 7) % 7));
943
+ else if (k === "End")
944
+ next = addDays(focused, 6 - (focused.getDay() - firstDayOfWeek + 7) % 7);
945
+ else if (k === "PageUp") next = addMonths(focused, e.shiftKey ? -12 : -1);
946
+ else if (k === "PageDown") next = addMonths(focused, e.shiftKey ? 12 : 1);
947
+ else if (k === "Enter" || k === " ") {
948
+ e.preventDefault();
949
+ if (!outOfRange(focused)) onSelect(focused);
950
+ return;
951
+ } else return;
952
+ e.preventDefault();
953
+ goto(next);
954
+ };
955
+ return /* @__PURE__ */ jsxs(
654
956
  "table",
655
957
  {
656
- ref: gridRef,
657
958
  className: "orynn-calendar__grid",
658
959
  role: "grid",
659
960
  "aria-label": monthName,
@@ -663,16 +964,29 @@ function Calendar({
663
964
  showWeekNumbers && /* @__PURE__ */ jsx("th", { "aria-hidden": "true" }),
664
965
  weekdays.map((w) => /* @__PURE__ */ jsx("th", { scope: "col", children: w }, w))
665
966
  ] }) }),
666
- /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: 6 }, (_, week) => {
967
+ /* @__PURE__ */ jsx("tbody", { onMouseLeave: onHoverDate ? () => onHoverDate(null) : void 0, children: Array.from({ length: weeks }, (_, week) => {
667
968
  const row = cells.slice(week * 7, week * 7 + 7);
668
969
  const firstOfRow = row[0];
669
970
  return /* @__PURE__ */ jsxs("tr", { children: [
670
971
  showWeekNumbers && /* @__PURE__ */ jsx("td", { className: "orynn-calendar__weeknum", children: isoWeek(firstOfRow) }),
671
972
  row.map((d) => {
672
- const inMonth = d.getMonth() === view.m;
973
+ const inMonth = d.getMonth() === pm;
673
974
  const disabled = outOfRange(d);
674
- const selected = isSameDay(d, value);
675
975
  const isFocused = isSameDay(d, focused);
976
+ let selected;
977
+ let rangeStart = false;
978
+ let rangeEnd = false;
979
+ let inRange = false;
980
+ if (mode === "multiple") {
981
+ selected = (selectedDates ?? []).some((x) => isSameDay(x, d));
982
+ } else if (mode === "range") {
983
+ rangeStart = isRangeStart(d, range) || !!previewRange2 && isRangeStart(d, previewRange2);
984
+ rangeEnd = isRangeEnd(d, range) || !!previewRange2 && isRangeEnd(d, previewRange2);
985
+ inRange = isWithinRange(d, range) || !!previewRange2 && isWithinRange(d, previewRange2);
986
+ selected = rangeStart || rangeEnd;
987
+ } else {
988
+ selected = isSameDay(d, value);
989
+ }
676
990
  return /* @__PURE__ */ jsx("td", { role: "gridcell", "aria-selected": selected, children: /* @__PURE__ */ jsx(
677
991
  "button",
678
992
  {
@@ -683,10 +997,15 @@ function Calendar({
683
997
  "data-outside": !inMonth || void 0,
684
998
  "data-today": isSameDay(d, today) || void 0,
685
999
  "data-selected": selected || void 0,
1000
+ "data-range-start": rangeStart || void 0,
1001
+ "data-range-end": rangeEnd || void 0,
1002
+ "data-in-range": inRange || void 0,
686
1003
  disabled,
687
1004
  "aria-label": new Intl.DateTimeFormat(locale, { dateStyle: "full" }).format(
688
1005
  d
689
1006
  ),
1007
+ onMouseEnter: onHoverDate ? () => onHoverDate(d) : void 0,
1008
+ onFocus: onHoverDate ? () => onHoverDate(d) : void 0,
690
1009
  onClick: () => {
691
1010
  setFocused(d);
692
1011
  onSelect(d);
@@ -698,25 +1017,187 @@ function Calendar({
698
1017
  ] }, firstOfRow.toISOString());
699
1018
  }) })
700
1019
  ]
1020
+ },
1021
+ `${py}-${pm}`
1022
+ );
1023
+ };
1024
+ const monthGrid = () => {
1025
+ const names = Array.from(
1026
+ { length: 12 },
1027
+ (_, i) => new Intl.DateTimeFormat(locale, { month: "short" }).format(new Date(2021, i, 1))
1028
+ );
1029
+ const onKeyDown = (e) => {
1030
+ let m = nav.m;
1031
+ if (e.key === "ArrowLeft") m -= 1;
1032
+ else if (e.key === "ArrowRight") m += 1;
1033
+ else if (e.key === "ArrowUp") m -= 3;
1034
+ else if (e.key === "ArrowDown") m += 3;
1035
+ else if (e.key === "Enter" || e.key === " ") {
1036
+ e.preventDefault();
1037
+ pickMonth(nav.m);
1038
+ return;
1039
+ } else return;
1040
+ e.preventDefault();
1041
+ const d = addMonths(new Date(nav.y, nav.m, 1), m - nav.m);
1042
+ setNav({ y: d.getFullYear(), m: d.getMonth() });
1043
+ focusGrid();
1044
+ };
1045
+ return /* @__PURE__ */ jsx(
1046
+ "div",
1047
+ {
1048
+ className: "orynn-calendar__pickgrid",
1049
+ role: "grid",
1050
+ "aria-label": `Months of ${nav.y}`,
1051
+ onKeyDown,
1052
+ children: [0, 3, 6, 9].map((rowStart) => /* @__PURE__ */ jsx("div", { role: "row", className: "orynn-calendar__pickrow", children: names.slice(rowStart, rowStart + 3).map((name, j) => {
1053
+ const i = rowStart + j;
1054
+ const isFocused = i === nav.m;
1055
+ const selected = value != null && value.getFullYear() === nav.y && value.getMonth() === i;
1056
+ return /* @__PURE__ */ jsx(
1057
+ "button",
1058
+ {
1059
+ type: "button",
1060
+ className: "orynn-calendar__cell",
1061
+ tabIndex: isFocused ? 0 : -1,
1062
+ "data-focused": isFocused || void 0,
1063
+ "data-selected": selected || void 0,
1064
+ disabled: monthOutOfRange(nav.y, i),
1065
+ onClick: () => pickMonth(i),
1066
+ children: name
1067
+ },
1068
+ name
1069
+ );
1070
+ }) }, rowStart))
701
1071
  }
702
- ),
703
- (showToday || showClear) && /* @__PURE__ */ jsxs("div", { className: "orynn-calendar__footer", children: [
704
- showToday ? /* @__PURE__ */ jsx(
705
- "button",
706
- {
707
- type: "button",
708
- className: "orynn-calendar__action",
709
- disabled: outOfRange(today),
710
- onClick: () => onSelect(today),
711
- children: "Today"
712
- }
713
- ) : /* @__PURE__ */ jsx("span", {}),
714
- showClear && /* @__PURE__ */ jsx("button", { type: "button", className: "orynn-calendar__action", onClick: onClear, children: "Clear" })
715
- ] })
716
- ] });
1072
+ );
1073
+ };
1074
+ const pickMonth = (m) => {
1075
+ if (monthOutOfRange(nav.y, m)) return;
1076
+ setNav((v) => ({ ...v, m }));
1077
+ if (precision === "month") {
1078
+ onSelect(new Date(nav.y, m, 1));
1079
+ } else {
1080
+ setView("day");
1081
+ setFocused(clampDate(new Date(nav.y, m, 1), minDate, maxDate));
1082
+ focusGrid();
1083
+ }
1084
+ };
1085
+ const yearGrid = () => {
1086
+ const g = decadeGrid(nav.y);
1087
+ const onKeyDown = (e) => {
1088
+ let y = nav.y;
1089
+ if (e.key === "ArrowLeft") y -= 1;
1090
+ else if (e.key === "ArrowRight") y += 1;
1091
+ else if (e.key === "ArrowUp") y -= 3;
1092
+ else if (e.key === "ArrowDown") y += 3;
1093
+ else if (e.key === "Enter" || e.key === " ") {
1094
+ e.preventDefault();
1095
+ pickYear(nav.y);
1096
+ return;
1097
+ } else return;
1098
+ e.preventDefault();
1099
+ setNav((v) => ({ ...v, y }));
1100
+ focusGrid();
1101
+ };
1102
+ return /* @__PURE__ */ jsx(
1103
+ "div",
1104
+ {
1105
+ className: "orynn-calendar__pickgrid",
1106
+ role: "grid",
1107
+ "aria-label": `Years ${g.start}\u2013${g.start + 9}`,
1108
+ onKeyDown,
1109
+ children: [0, 3, 6, 9].map((rowStart) => /* @__PURE__ */ jsx("div", { role: "row", className: "orynn-calendar__pickrow", children: g.years.slice(rowStart, rowStart + 3).map((y) => {
1110
+ const isFocused = y === nav.y;
1111
+ const selected = value != null && value.getFullYear() === y;
1112
+ return /* @__PURE__ */ jsx(
1113
+ "button",
1114
+ {
1115
+ type: "button",
1116
+ className: "orynn-calendar__cell",
1117
+ tabIndex: isFocused ? 0 : -1,
1118
+ "data-focused": isFocused || void 0,
1119
+ "data-outside": y < g.start || y > g.start + 9 || void 0,
1120
+ "data-selected": selected || void 0,
1121
+ disabled: yearOutOfRange(y),
1122
+ onClick: () => pickYear(y),
1123
+ children: y
1124
+ },
1125
+ y
1126
+ );
1127
+ }) }, rowStart))
1128
+ }
1129
+ );
1130
+ };
1131
+ const pickYear = (y) => {
1132
+ if (yearOutOfRange(y)) return;
1133
+ setNav((v) => ({ ...v, y }));
1134
+ if (precision === "year") {
1135
+ onSelect(new Date(y, 0, 1));
1136
+ } else {
1137
+ setView("month");
1138
+ focusGrid();
1139
+ }
1140
+ };
1141
+ const dayView = panels <= 1 ? dayGrid() : /* @__PURE__ */ jsx("div", { className: "orynn-calendar__panels", children: Array.from({ length: panels }, (_, i) => dayGrid(i)) });
1142
+ return /* @__PURE__ */ jsxs(
1143
+ "div",
1144
+ {
1145
+ className: "orynn-calendar",
1146
+ ref: gridRef,
1147
+ "data-view": view,
1148
+ "data-months": panels > 1 ? panels : void 0,
1149
+ children: [
1150
+ header,
1151
+ " ",
1152
+ view === "day" ? dayView : view === "month" ? monthGrid() : yearGrid(),
1153
+ (showToday || showClear) && /* @__PURE__ */ jsxs("div", { className: "orynn-calendar__footer", children: [
1154
+ showToday ? /* @__PURE__ */ jsx(
1155
+ "button",
1156
+ {
1157
+ type: "button",
1158
+ className: "orynn-calendar__action",
1159
+ disabled: outOfRange(today),
1160
+ onClick: () => onSelect(today),
1161
+ children: "Today"
1162
+ }
1163
+ ) : /* @__PURE__ */ jsx("span", {}),
1164
+ showClear && /* @__PURE__ */ jsx("button", { type: "button", className: "orynn-calendar__action", onClick: onClear, children: "Clear" })
1165
+ ] })
1166
+ ]
1167
+ }
1168
+ );
1169
+ }
1170
+ function addMonthNav(v, dir) {
1171
+ const total = v.y * 12 + v.m + dir;
1172
+ return { y: Math.floor(total / 12), m: (total % 12 + 12) % 12 };
717
1173
  }
1174
+
1175
+ // src/controls/DatePicker/time-utils.ts
1176
+ var timeParts = (d) => ({
1177
+ h: d.getHours(),
1178
+ m: d.getMinutes(),
1179
+ s: d.getSeconds()
1180
+ });
1181
+ function withTime(date, time) {
1182
+ const r = new Date(date);
1183
+ r.setHours(time.h, time.m, time.s, 0);
1184
+ return r;
1185
+ }
1186
+ function minuteOptions(step) {
1187
+ const s = Math.max(1, Math.min(30, Math.floor(step)));
1188
+ const out = [];
1189
+ for (let m = 0; m < 60; m += s) out.push(m);
1190
+ return out;
1191
+ }
1192
+ var pad2 = (n) => String(n).padStart(2, "0");
1193
+ var normDates = (v) => Array.isArray(v) ? v.map((x) => toDate(x)).filter(Boolean) : [];
1194
+ var normRange = (v) => {
1195
+ const r = v ?? {};
1196
+ return { start: toDate(r.start ?? null), end: toDate(r.end ?? null) };
1197
+ };
718
1198
  var DatePicker = /* @__PURE__ */ forwardRef(
719
- function DatePicker2(props, ref) {
1199
+ function DatePicker2(propsIn, ref) {
1200
+ const props = propsIn;
720
1201
  const {
721
1202
  label,
722
1203
  description,
@@ -734,6 +1215,7 @@ var DatePicker = /* @__PURE__ */ forwardRef(
734
1215
  id: idProp,
735
1216
  name,
736
1217
  className,
1218
+ mode = "single",
737
1219
  value: valueProp,
738
1220
  defaultValue,
739
1221
  onChange,
@@ -741,46 +1223,123 @@ var DatePicker = /* @__PURE__ */ forwardRef(
741
1223
  minDate,
742
1224
  maxDate,
743
1225
  disabledDates,
1226
+ isDateUnavailable,
744
1227
  firstDayOfWeek = 0,
745
1228
  locale,
746
1229
  showWeekNumbers,
747
1230
  showToday = true,
748
1231
  showClear,
1232
+ defaultView,
1233
+ precision = "day",
1234
+ fixedWeeks = true,
1235
+ numberOfMonths,
1236
+ showTime = false,
1237
+ timeStep = 5,
1238
+ defaultMonth,
1239
+ presets,
749
1240
  inline = false,
750
1241
  allowInput = true,
751
- closeOnSelect = true,
1242
+ closeOnSelect: closeOnSelectProp,
1243
+ open: openProp,
1244
+ defaultOpen = false,
1245
+ onOpenChange,
752
1246
  placeholder
753
1247
  } = props;
754
1248
  const reactId = useId();
755
1249
  const id = idProp ?? reactId;
756
1250
  const panelId = `${id}-cal`;
1251
+ const withTimePicker = showTime && mode === "single";
1252
+ const closeOnSelect = closeOnSelectProp ?? !withTimePicker;
1253
+ const fmt = withTimePicker && format === "yyyy-MM-dd" ? "yyyy-MM-dd HH:mm" : format;
757
1254
  const isControlled = valueProp !== void 0;
758
- const [inner, setInner] = useState(() => toDate(defaultValue));
759
- const value = isControlled ? toDate(valueProp) : inner;
760
- const [open, setOpen] = useState(false);
1255
+ const [innerRaw, setInnerRaw] = useState(() => defaultValue ?? null);
1256
+ const raw = isControlled ? valueProp : innerRaw;
1257
+ const toDateKeepTime = (v) => {
1258
+ if (v == null) return null;
1259
+ const d = new Date(v);
1260
+ return Number.isNaN(d.getTime()) ? null : d;
1261
+ };
1262
+ const curDate = mode === "single" ? withTimePicker ? toDateKeepTime(raw) : toDate(raw) : null;
1263
+ const curRange = mode === "range" ? normRange(raw) : { start: null, end: null };
1264
+ const curDates = mode === "multiple" ? normDates(raw) : [];
1265
+ const commit = (next) => {
1266
+ if (!isControlled) {
1267
+ setInnerRaw(next);
1268
+ }
1269
+ onChange?.(next);
1270
+ };
1271
+ const openIsControlled = openProp !== void 0;
1272
+ const [innerOpen, setInnerOpen] = useState(defaultOpen);
1273
+ const open = openIsControlled ? openProp : innerOpen;
1274
+ const setOpen = (next) => {
1275
+ if (!openIsControlled) setInnerOpen(next);
1276
+ onOpenChange?.(next);
1277
+ };
761
1278
  const [gridFocus, setGridFocus] = useState(false);
762
- const [text, setText] = useState(() => value ? formatDate(value, format, locale) : "");
1279
+ const [hoverDate, setHoverDate] = useState(null);
1280
+ const displayText = () => {
1281
+ if (mode === "single") return curDate ? formatDate(curDate, fmt, locale) : "";
1282
+ if (mode === "multiple") {
1283
+ if (curDates.length === 0) return "";
1284
+ if (curDates.length === 1) return formatDate(curDates[0], fmt, locale);
1285
+ return `${curDates.length} selected`;
1286
+ }
1287
+ if (!curRange.start && !curRange.end) return "";
1288
+ const s = curRange.start ? formatDate(curRange.start, fmt, locale) : "\u2026";
1289
+ const e = curRange.end ? formatDate(curRange.end, fmt, locale) : "\u2026";
1290
+ return `${s} \u2013 ${e}`;
1291
+ };
1292
+ const [text, setText] = useState(displayText);
763
1293
  const boxRef = useRef(null);
764
1294
  const inputRef = useRef(null);
1295
+ const skipFocusOpen = useRef(false);
1296
+ const valueKey = [
1297
+ mode,
1298
+ typeof fmt === "string" ? fmt : JSON.stringify(fmt),
1299
+ locale ?? "",
1300
+ curDate?.getTime() ?? "",
1301
+ curRange.start?.getTime() ?? "",
1302
+ curRange.end?.getTime() ?? "",
1303
+ curDates.map((d) => d.getTime()).join(",")
1304
+ ].join("|");
765
1305
  useEffect(() => {
766
- setText(value ? formatDate(value, format, locale) : "");
767
- }, [value, format, locale]);
1306
+ setText(displayText());
1307
+ }, [valueKey]);
768
1308
  const isDisabledDate = (d) => {
1309
+ if (isDateUnavailable?.(d)) return true;
769
1310
  if (Array.isArray(disabledDates))
770
1311
  return disabledDates.some((x) => isSameDay(startOfDay(x), d));
771
1312
  return Boolean(disabledDates?.(d));
772
1313
  };
773
- const commit = (d) => {
774
- if (!isControlled) setInner(d);
775
- onChange?.(d);
1314
+ const closeAndRefocus = () => {
1315
+ setOpen(false);
1316
+ setGridFocus(false);
1317
+ skipFocusOpen.current = true;
1318
+ inputRef.current?.focus();
776
1319
  };
777
1320
  const select = (d) => {
778
1321
  if (isDisabledDate(d)) return;
779
- commit(startOfDay(d));
780
- if (closeOnSelect) {
781
- setOpen(false);
782
- inputRef.current?.focus();
1322
+ const day = startOfDay(d);
1323
+ if (mode === "single") {
1324
+ const picked = withTimePicker ? withTime(day, curDate ? timeParts(curDate) : { h: 0, m: 0, s: 0 }) : day;
1325
+ commit(picked);
1326
+ if (closeOnSelect) closeAndRefocus();
1327
+ return;
1328
+ }
1329
+ if (mode === "multiple") {
1330
+ const has = curDates.some((x) => isSameDay(x, day));
1331
+ commit(has ? curDates.filter((x) => !isSameDay(x, day)) : [...curDates, day]);
1332
+ return;
783
1333
  }
1334
+ const next = nextRange(curRange, day);
1335
+ commit(next);
1336
+ if (isRangeComplete(next) && closeOnSelect) closeAndRefocus();
1337
+ };
1338
+ const clearValue = () => {
1339
+ if (mode === "single") commit(null);
1340
+ else if (mode === "multiple") commit([]);
1341
+ else commit({ start: null, end: null });
1342
+ onClear?.();
784
1343
  };
785
1344
  const onKeyDown = (e) => {
786
1345
  if (disabled || readOnly) return;
@@ -791,31 +1350,96 @@ var DatePicker = /* @__PURE__ */ forwardRef(
791
1350
  } else if (e.key === "Escape" && open) {
792
1351
  e.preventDefault();
793
1352
  setOpen(false);
794
- } else if (e.key === "Enter") {
795
- const parsed = parseDate(text, format);
1353
+ } else if (e.key === "Enter" && mode === "single") {
1354
+ const parsed = parseDate(text, fmt, locale);
796
1355
  if (parsed && !isDisabledDate(startOfDay(parsed))) select(parsed);
797
1356
  }
798
1357
  };
799
- const cal = /* @__PURE__ */ jsx(
1358
+ const preview = mode === "range" && curRange.start && !curRange.end ? previewRange(curRange.start, hoverDate) : null;
1359
+ const applyPreset = (p) => {
1360
+ const resolved = p.getValue ? p.getValue() : p.value;
1361
+ if (resolved == null) return;
1362
+ commit(resolved);
1363
+ if (closeOnSelect && !inline) closeAndRefocus();
1364
+ };
1365
+ const setTimeField = (part, n) => {
1366
+ const base2 = curDate ?? startOfDay(/* @__PURE__ */ new Date());
1367
+ const t = timeParts(base2);
1368
+ commit(withTime(base2, { ...t, [part]: n, s: 0 }));
1369
+ };
1370
+ const timeColumn = withTimePicker ? /* @__PURE__ */ jsxs("div", { className: "orynn-datepicker__time", children: [
1371
+ /* @__PURE__ */ jsx("span", { className: "orynn-datepicker__time-label", children: "Time" }),
1372
+ /* @__PURE__ */ jsxs("div", { className: "orynn-datepicker__time-fields", children: [
1373
+ /* @__PURE__ */ jsx(
1374
+ "select",
1375
+ {
1376
+ "aria-label": "Hour",
1377
+ className: "orynn-datepicker__time-select",
1378
+ value: curDate ? curDate.getHours() : 0,
1379
+ onChange: (e) => setTimeField("h", Number(e.target.value)),
1380
+ children: Array.from({ length: 24 }, (_, h) => /* @__PURE__ */ jsx("option", { value: h, children: pad2(h) }, h))
1381
+ }
1382
+ ),
1383
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: ":" }),
1384
+ /* @__PURE__ */ jsx(
1385
+ "select",
1386
+ {
1387
+ "aria-label": "Minute",
1388
+ className: "orynn-datepicker__time-select",
1389
+ value: curDate ? curDate.getMinutes() - curDate.getMinutes() % timeStep : 0,
1390
+ onChange: (e) => setTimeField("m", Number(e.target.value)),
1391
+ children: minuteOptions(timeStep).map((m) => /* @__PURE__ */ jsx("option", { value: m, children: pad2(m) }, m))
1392
+ }
1393
+ )
1394
+ ] })
1395
+ ] }) : null;
1396
+ const calendar = /* @__PURE__ */ jsx(
800
1397
  Calendar,
801
1398
  {
802
- value,
1399
+ value: curDate,
803
1400
  onSelect: select,
1401
+ mode,
1402
+ selectedDates: mode === "multiple" ? curDates : void 0,
1403
+ range: mode === "range" ? curRange : void 0,
1404
+ previewRange: preview,
1405
+ onHoverDate: mode === "range" ? setHoverDate : void 0,
804
1406
  minDate,
805
1407
  maxDate,
806
1408
  isDisabled: isDisabledDate,
807
1409
  firstDayOfWeek,
808
1410
  locale,
809
1411
  showWeekNumbers,
810
- showToday,
1412
+ showToday: showToday && mode === "single",
811
1413
  showClear,
1414
+ defaultView,
1415
+ precision,
1416
+ fixedWeeks,
1417
+ numberOfMonths,
1418
+ defaultMonth,
812
1419
  onClear: () => {
813
- commit(null);
1420
+ clearValue();
814
1421
  setOpen(false);
815
1422
  },
816
1423
  autoFocus: open && gridFocus || inline
817
1424
  }
818
1425
  );
1426
+ const body = timeColumn ? /* @__PURE__ */ jsxs("div", { className: "orynn-datepicker__withtime", children: [
1427
+ calendar,
1428
+ timeColumn
1429
+ ] }) : calendar;
1430
+ const cal = presets && presets.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "orynn-datepicker__panel", children: [
1431
+ /* @__PURE__ */ jsx("div", { className: "orynn-datepicker__presets", children: presets.map((p) => /* @__PURE__ */ jsx(
1432
+ "button",
1433
+ {
1434
+ type: "button",
1435
+ className: "orynn-datepicker__preset",
1436
+ onClick: () => applyPreset(p),
1437
+ children: p.label
1438
+ },
1439
+ p.label
1440
+ )) }),
1441
+ body
1442
+ ] }) : body;
819
1443
  if (inline) {
820
1444
  return /* @__PURE__ */ jsx(
821
1445
  Field,
@@ -832,7 +1456,8 @@ var DatePicker = /* @__PURE__ */ forwardRef(
832
1456
  }
833
1457
  );
834
1458
  }
835
- const hasValue = value != null;
1459
+ const hasValue = mode === "single" ? curDate != null : mode === "multiple" ? curDates.length > 0 : Boolean(curRange.start || curRange.end);
1460
+ const typingAllowed = mode === "single" && allowInput && !readOnly;
836
1461
  return /* @__PURE__ */ jsx(
837
1462
  Field,
838
1463
  {
@@ -867,10 +1492,7 @@ var DatePicker = /* @__PURE__ */ forwardRef(
867
1492
  className: "orynn-box__iconbtn",
868
1493
  "aria-label": "Clear",
869
1494
  tabIndex: -1,
870
- onClick: () => {
871
- commit(null);
872
- onClear?.();
873
- },
1495
+ onClick: clearValue,
874
1496
  children: /* @__PURE__ */ jsx(XIcon, {})
875
1497
  }
876
1498
  ),
@@ -886,7 +1508,7 @@ var DatePicker = /* @__PURE__ */ forwardRef(
886
1508
  onClick: () => {
887
1509
  if (disabled || readOnly) return;
888
1510
  setGridFocus(true);
889
- setOpen((o) => !o);
1511
+ setOpen(!open);
890
1512
  },
891
1513
  children: /* @__PURE__ */ jsx(CalendarIcon, {})
892
1514
  }
@@ -905,7 +1527,7 @@ var DatePicker = /* @__PURE__ */ forwardRef(
905
1527
  name,
906
1528
  className: "orynn-box__field",
907
1529
  type: "text",
908
- inputMode: "numeric",
1530
+ inputMode: typingAllowed ? "numeric" : void 0,
909
1531
  autoComplete: "off",
910
1532
  role: "combobox",
911
1533
  "aria-expanded": open,
@@ -915,19 +1537,26 @@ var DatePicker = /* @__PURE__ */ forwardRef(
915
1537
  "aria-describedby": describedById,
916
1538
  value: text,
917
1539
  disabled,
918
- readOnly: !allowInput || readOnly,
1540
+ readOnly: !typingAllowed,
919
1541
  required,
920
- placeholder: floatingLabel ? void 0 : placeholder ?? format.toLowerCase(),
1542
+ placeholder: floatingLabel ? void 0 : placeholder ?? (typeof fmt === "string" ? fmt.toLowerCase() : void 0),
921
1543
  onChange: (e) => setText(e.target.value),
922
1544
  onKeyDown,
923
- onFocus: () => setOpen(true),
1545
+ onFocus: () => {
1546
+ if (skipFocusOpen.current) {
1547
+ skipFocusOpen.current = false;
1548
+ return;
1549
+ }
1550
+ setOpen(true);
1551
+ },
924
1552
  onBlur: () => {
925
- const parsed = parseDate(text, format);
1553
+ if (mode !== "single") return;
1554
+ const parsed = parseDate(text, fmt, locale);
926
1555
  if (parsed && !isDisabledDate(startOfDay(parsed))) {
927
1556
  commit(startOfDay(parsed));
928
- setText(formatDate(startOfDay(parsed), format, locale));
1557
+ setText(formatDate(startOfDay(parsed), fmt, locale));
929
1558
  } else {
930
- setText(value ? formatDate(value, format, locale) : "");
1559
+ setText(curDate ? formatDate(curDate, fmt, locale) : "");
931
1560
  }
932
1561
  }
933
1562
  }
@@ -954,7 +1583,13 @@ var DatePicker = /* @__PURE__ */ forwardRef(
954
1583
  );
955
1584
  }
956
1585
  );
1586
+ var CREATE_VALUE = "\0orynn-create";
957
1587
  var toArray = (v) => v == null ? [] : Array.isArray(v) ? v : [v];
1588
+ var isGrouped = (o) => {
1589
+ const first = o[0];
1590
+ return first != null && "items" in first && Array.isArray(first.items);
1591
+ };
1592
+ var flattenOptions = (o) => isGrouped(o) ? o.flatMap((g) => g.items.map((it) => ({ ...it, group: it.group ?? g.group }))) : o;
958
1593
  function Highlight({ text, query }) {
959
1594
  if (!query) return /* @__PURE__ */ jsx(Fragment, { children: text });
960
1595
  const i = text.toLowerCase().indexOf(query.toLowerCase());
@@ -996,52 +1631,103 @@ var Dropdown = /* @__PURE__ */ forwardRef(
996
1631
  highlightMatch,
997
1632
  renderOption,
998
1633
  renderValue,
1634
+ renderGroupLabel,
999
1635
  placeholder,
1000
1636
  emptyMessage = "No results",
1637
+ loadingMessage = "Loading\u2026",
1001
1638
  closeOnSelect = !multiple,
1002
1639
  maxSelectedLabels,
1640
+ maxValues,
1641
+ hidePickedOptions = false,
1642
+ showSelectAll = false,
1003
1643
  native = false,
1004
- startContent
1644
+ startContent,
1645
+ creatable = false,
1646
+ onCreate,
1647
+ isValidNewOption,
1648
+ formatCreateLabel,
1649
+ createPosition = "last",
1650
+ onSearch,
1651
+ searchDebounce = 0,
1652
+ open: openProp,
1653
+ defaultOpen = false,
1654
+ onOpenChange,
1655
+ maxHeight
1005
1656
  } = props;
1006
1657
  const reactId = useId();
1007
1658
  const id = idProp ?? reactId;
1008
1659
  const listId = `${id}-listbox`;
1009
- const isControlled = valueProp !== void 0;
1010
- const [inner, setInner] = useState(() => toArray(defaultValue));
1011
- const selectedValues = isControlled ? toArray(valueProp) : inner;
1012
- const commit = (next) => {
1013
- if (!isControlled) setInner(next);
1014
- onChange?.(multiple ? next : next[0] ?? null);
1660
+ const baseOptions = useMemo(() => flattenOptions(options), [options]);
1661
+ const [createdOptions, setCreatedOptions] = useState([]);
1662
+ const allOptions = useMemo(
1663
+ () => [...baseOptions, ...createdOptions],
1664
+ [baseOptions, createdOptions]
1665
+ );
1666
+ const [selectedValues, setSelectedValues] = useControllableState({
1667
+ value: valueProp === void 0 ? void 0 : toArray(valueProp),
1668
+ defaultValue: toArray(defaultValue),
1669
+ onChange: void 0
1670
+ });
1671
+ const commit = (next) => {
1672
+ setSelectedValues(next);
1673
+ onChange?.(multiple ? next : next[0] ?? null);
1674
+ };
1675
+ const openIsControlled = openProp !== void 0;
1676
+ const [innerOpen, setInnerOpen] = useState(defaultOpen);
1677
+ const open = openIsControlled ? openProp : innerOpen;
1678
+ const setOpen = (next) => {
1679
+ if (!openIsControlled) setInnerOpen(next);
1680
+ onOpenChange?.(next);
1015
1681
  };
1016
- const [open, setOpen] = useState(false);
1017
1682
  const [query, setQuery] = useState("");
1018
1683
  const [activeIndex, setActiveIndex] = useState(0);
1019
1684
  const boxRef = useRef(null);
1020
1685
  const inputRef = useRef(null);
1021
1686
  const listRef = useRef(null);
1022
- const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);
1023
- const selectedOptions = selectedValues.map((v) => byValue.get(v)).filter(Boolean);
1687
+ const byValue = useMemo(() => new Map(allOptions.map((o) => [o.value, o])), [allOptions]);
1688
+ const selectedOptions = selectedValues.map((v) => byValue.get(v) ?? { label: v, value: v }).filter(Boolean);
1689
+ useEffect(() => {
1690
+ if (!onSearch) return;
1691
+ const t = setTimeout(() => onSearch(query), searchDebounce);
1692
+ return () => clearTimeout(t);
1693
+ }, [query, onSearch, searchDebounce]);
1024
1694
  const filtered = useMemo(() => {
1025
- if (!searchable || !query) return options;
1026
- const q = query.toLowerCase();
1027
- const match = filterFn ?? ((o) => filterMode === "startsWith" ? o.label.toLowerCase().startsWith(q) : o.label.toLowerCase().includes(q));
1028
- return options.filter((o) => match(o, query));
1029
- }, [options, searchable, query, filterMode, filterFn]);
1695
+ let list = allOptions;
1696
+ if (searchable && query && !onSearch) {
1697
+ const q = query.toLowerCase();
1698
+ const match = filterFn ?? ((o) => filterMode === "startsWith" ? o.label.toLowerCase().startsWith(q) : o.label.toLowerCase().includes(q));
1699
+ list = list.filter((o) => match(o, query));
1700
+ }
1701
+ if (multiple && hidePickedOptions) {
1702
+ list = list.filter((o) => !selectedValues.includes(o.value));
1703
+ }
1704
+ return list;
1705
+ }, [
1706
+ allOptions,
1707
+ searchable,
1708
+ query,
1709
+ onSearch,
1710
+ filterMode,
1711
+ filterFn,
1712
+ multiple,
1713
+ hidePickedOptions,
1714
+ selectedValues
1715
+ ]);
1716
+ const showCreateRow = creatable && searchable && query.trim().length > 0 && (isValidNewOption ? isValidNewOption(query.trim(), allOptions) : !allOptions.some((o) => o.label.toLowerCase() === query.trim().toLowerCase()));
1717
+ const createRow = { value: CREATE_VALUE, label: query.trim() };
1718
+ const selectableOptions = showCreateRow ? createPosition === "first" ? [createRow, ...filtered] : [...filtered, createRow] : filtered;
1030
1719
  const rows = useMemo(() => {
1031
1720
  const out = [];
1032
1721
  let seenGroup;
1033
- let optionIndex = 0;
1034
- for (const o of filtered) {
1035
- if (o.group && o.group !== seenGroup) {
1722
+ selectableOptions.forEach((o, optionIndex) => {
1723
+ if (o.value !== CREATE_VALUE && o.group && o.group !== seenGroup) {
1036
1724
  seenGroup = o.group;
1037
1725
  out.push({ type: "group", label: o.group });
1038
1726
  }
1039
1727
  out.push({ type: "option", option: o, optionIndex });
1040
- optionIndex += 1;
1041
- }
1728
+ });
1042
1729
  return out;
1043
- }, [filtered]);
1044
- const selectableOptions = filtered;
1730
+ }, [selectableOptions]);
1045
1731
  useEffect(() => {
1046
1732
  setActiveIndex((i) => Math.min(Math.max(0, i), Math.max(0, selectableOptions.length - 1)));
1047
1733
  }, [selectableOptions.length]);
@@ -1062,10 +1748,34 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1062
1748
  setActiveIndex(firstRelevantIndex());
1063
1749
  setOpen(true);
1064
1750
  };
1751
+ const handleCreate = (raw) => {
1752
+ const input = raw.trim();
1753
+ if (!input) return;
1754
+ onCreate?.(input);
1755
+ setCreatedOptions(
1756
+ (prev) => prev.some((o) => o.value === input) || baseOptions.some((o) => o.value === input) ? prev : [...prev, { label: input, value: input }]
1757
+ );
1758
+ if (multiple) {
1759
+ if (maxValues != null && selectedValues.length >= maxValues) return;
1760
+ commit([...selectedValues, input]);
1761
+ } else {
1762
+ commit([input]);
1763
+ }
1764
+ setQuery("");
1765
+ if (closeOnSelect) {
1766
+ setOpen(false);
1767
+ inputRef.current?.focus();
1768
+ }
1769
+ };
1065
1770
  const pick = (opt) => {
1771
+ if (opt.value === CREATE_VALUE) {
1772
+ handleCreate(opt.label);
1773
+ return;
1774
+ }
1066
1775
  if (opt.disabled) return;
1067
1776
  if (multiple) {
1068
1777
  const has = selectedValues.includes(opt.value);
1778
+ if (!has && maxValues != null && selectedValues.length >= maxValues) return;
1069
1779
  commit(
1070
1780
  has ? selectedValues.filter((v) => v !== opt.value) : [...selectedValues, opt.value]
1071
1781
  );
@@ -1083,6 +1793,16 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1083
1793
  onClear?.();
1084
1794
  setQuery("");
1085
1795
  };
1796
+ const selectAllTargets = filtered.filter((o) => !o.disabled).map((o) => o.value);
1797
+ const allSelected = selectAllTargets.length > 0 && selectAllTargets.every((v) => selectedValues.includes(v));
1798
+ const toggleSelectAll = () => {
1799
+ if (allSelected) {
1800
+ commit(selectedValues.filter((v) => !selectAllTargets.includes(v)));
1801
+ } else {
1802
+ const merged = Array.from(/* @__PURE__ */ new Set([...selectedValues, ...selectAllTargets]));
1803
+ commit(maxValues != null ? merged.slice(0, maxValues) : merged);
1804
+ }
1805
+ };
1086
1806
  const onKeyDown = (e) => {
1087
1807
  if (disabled || readOnly) return;
1088
1808
  switch (e.key) {
@@ -1147,6 +1867,7 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1147
1867
  };
1148
1868
  const hasValue = selectedValues.length > 0;
1149
1869
  const doHighlight = highlightMatch ?? searchable;
1870
+ const isLoadingEmpty = Boolean(loading) && selectableOptions.length === 0;
1150
1871
  if (native && !multiple && !searchable) {
1151
1872
  return /* @__PURE__ */ jsx(
1152
1873
  Field,
@@ -1173,7 +1894,7 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1173
1894
  onChange: (e) => commit(e.target.value ? [e.target.value] : []),
1174
1895
  children: [
1175
1896
  placeholder != null && /* @__PURE__ */ jsx("option", { value: "", disabled: required, children: placeholder }),
1176
- options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value))
1897
+ baseOptions.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value))
1177
1898
  ]
1178
1899
  }
1179
1900
  )
@@ -1260,7 +1981,6 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1260
1981
  {
1261
1982
  ...ref ? { ref: setRefs(ref, inputRef) } : { ref: inputRef },
1262
1983
  id,
1263
- name,
1264
1984
  className: "orynn-select__search",
1265
1985
  role: "combobox",
1266
1986
  "aria-expanded": open,
@@ -1272,7 +1992,11 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1272
1992
  autoComplete: "off",
1273
1993
  readOnly: !searchable || readOnly,
1274
1994
  disabled,
1275
- placeholder: !hasValue && (searchable || !floatingLabel) ? placeholder : void 0,
1995
+ placeholder: (
1996
+ // only the search input shows the placeholder; when not
1997
+ // searchable the sibling span owns it (avoids a double render)
1998
+ !hasValue && searchable ? placeholder : void 0
1999
+ ),
1276
2000
  value: query,
1277
2001
  size: 1,
1278
2002
  onChange: (e) => {
@@ -1282,7 +2006,8 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1282
2006
  onKeyDown,
1283
2007
  onFocus: openPanel
1284
2008
  }
1285
- )
2009
+ ),
2010
+ name != null && (multiple ? selectedValues.map((v) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: v }, v)) : /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selectedValues[0] ?? "" }))
1286
2011
  ] })
1287
2012
  }
1288
2013
  ),
@@ -1293,44 +2018,61 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1293
2018
  anchorRef: boxRef,
1294
2019
  onClose: () => setOpen(false),
1295
2020
  popoverRef: listRef,
1296
- children: /* @__PURE__ */ jsx(
2021
+ maxHeight,
2022
+ children: /* @__PURE__ */ jsxs(
1297
2023
  "div",
1298
2024
  {
1299
2025
  className: "orynn-listbox",
1300
2026
  id: listId,
1301
2027
  role: "listbox",
1302
2028
  "aria-multiselectable": multiple || void 0,
1303
- children: selectableOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "orynn-listbox__empty", children: emptyMessage }) : rows.map(
1304
- (row) => row.type === "group" ? /* @__PURE__ */ jsx("div", { className: "orynn-listbox__group", children: row.label }, `g-${row.label}`) : /* @__PURE__ */ jsx(
1305
- "div",
1306
- {
1307
- id: `${id}-opt-${row.option.value}`,
1308
- role: "option",
1309
- "aria-selected": selectedValues.includes(row.option.value),
1310
- "aria-disabled": row.option.disabled || void 0,
1311
- className: "orynn-option",
1312
- "data-active": row.optionIndex === activeIndex || void 0,
1313
- "data-selected": selectedValues.includes(row.option.value) || void 0,
1314
- "data-disabled": row.option.disabled || void 0,
1315
- onMouseEnter: () => setActiveIndex(row.optionIndex),
1316
- onClick: () => pick(row.option),
1317
- children: renderOption ? renderOption(row.option, {
1318
- selected: selectedValues.includes(row.option.value),
1319
- active: row.optionIndex === activeIndex,
1320
- disabled: Boolean(row.option.disabled),
1321
- query
1322
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1323
- row.option.icon,
1324
- /* @__PURE__ */ jsxs("span", { className: "orynn-option__body", children: [
1325
- /* @__PURE__ */ jsx("span", { className: "orynn-option__label", children: doHighlight ? /* @__PURE__ */ jsx(Highlight, { text: row.option.label, query }) : row.option.label }),
1326
- row.option.description != null && /* @__PURE__ */ jsx("span", { className: "orynn-option__desc", children: row.option.description })
1327
- ] }),
1328
- selectedValues.includes(row.option.value) && /* @__PURE__ */ jsx(CheckIcon, { className: "orynn-option__check" })
1329
- ] })
1330
- },
1331
- row.option.value
2029
+ children: [
2030
+ multiple && showSelectAll && selectAllTargets.length > 0 && /* @__PURE__ */ jsx("button", { type: "button", className: "orynn-listbox__action", onClick: toggleSelectAll, children: allSelected ? "Clear all" : "Select all" }),
2031
+ isLoadingEmpty ? /* @__PURE__ */ jsx("div", { className: "orynn-listbox__empty", children: loadingMessage }) : selectableOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "orynn-listbox__empty", children: emptyMessage }) : rows.map(
2032
+ (row) => row.type === "group" ? /* @__PURE__ */ jsx("div", { className: "orynn-listbox__group", children: renderGroupLabel ? renderGroupLabel(row.label) : row.label }, `g-${row.label}`) : row.option.value === CREATE_VALUE ? /* @__PURE__ */ jsx(
2033
+ "div",
2034
+ {
2035
+ id: `${id}-opt-${CREATE_VALUE}`,
2036
+ role: "option",
2037
+ "aria-selected": false,
2038
+ className: "orynn-option orynn-option--create",
2039
+ "data-active": row.optionIndex === activeIndex || void 0,
2040
+ onMouseEnter: () => setActiveIndex(row.optionIndex),
2041
+ onClick: () => pick(row.option),
2042
+ children: /* @__PURE__ */ jsx("span", { className: "orynn-option__body", children: /* @__PURE__ */ jsx("span", { className: "orynn-option__label", children: formatCreateLabel ? formatCreateLabel(row.option.label) : `Create "${row.option.label}"` }) })
2043
+ },
2044
+ CREATE_VALUE
2045
+ ) : /* @__PURE__ */ jsx(
2046
+ "div",
2047
+ {
2048
+ id: `${id}-opt-${row.option.value}`,
2049
+ role: "option",
2050
+ "aria-selected": selectedValues.includes(row.option.value),
2051
+ "aria-disabled": row.option.disabled || void 0,
2052
+ className: "orynn-option",
2053
+ "data-active": row.optionIndex === activeIndex || void 0,
2054
+ "data-selected": selectedValues.includes(row.option.value) || void 0,
2055
+ "data-disabled": row.option.disabled || void 0,
2056
+ onMouseEnter: () => setActiveIndex(row.optionIndex),
2057
+ onClick: () => pick(row.option),
2058
+ children: renderOption ? renderOption(row.option, {
2059
+ selected: selectedValues.includes(row.option.value),
2060
+ active: row.optionIndex === activeIndex,
2061
+ disabled: Boolean(row.option.disabled),
2062
+ query
2063
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2064
+ row.option.icon,
2065
+ /* @__PURE__ */ jsxs("span", { className: "orynn-option__body", children: [
2066
+ /* @__PURE__ */ jsx("span", { className: "orynn-option__label", children: doHighlight ? /* @__PURE__ */ jsx(Highlight, { text: row.option.label, query }) : row.option.label }),
2067
+ row.option.description != null && /* @__PURE__ */ jsx("span", { className: "orynn-option__desc", children: row.option.description })
2068
+ ] }),
2069
+ selectedValues.includes(row.option.value) && /* @__PURE__ */ jsx(CheckIcon, { className: "orynn-option__check" })
2070
+ ] })
2071
+ },
2072
+ row.option.value
2073
+ )
1332
2074
  )
1333
- )
2075
+ ]
1334
2076
  }
1335
2077
  )
1336
2078
  }
@@ -1378,8 +2120,14 @@ var Input = /* @__PURE__ */ forwardRef(
1378
2120
  type = "text",
1379
2121
  prefix,
1380
2122
  suffix,
2123
+ addonBefore,
2124
+ addonAfter,
1381
2125
  passwordToggle,
2126
+ passwordVisible,
2127
+ onPasswordVisibleChange,
2128
+ visibilityToggleIcon,
1382
2129
  showCount,
2130
+ debounce,
1383
2131
  maxLength,
1384
2132
  placeholder,
1385
2133
  ...rest
@@ -1391,14 +2139,32 @@ var Input = /* @__PURE__ */ forwardRef(
1391
2139
  defaultValue: defaultValue ?? "",
1392
2140
  onChange: void 0
1393
2141
  });
1394
- const [reveal, setReveal] = useState(false);
2142
+ const revealIsControlled = passwordVisible !== void 0;
2143
+ const [innerReveal, setInnerReveal] = useState(false);
2144
+ const reveal = revealIsControlled ? passwordVisible : innerReveal;
2145
+ const setReveal = (next) => {
2146
+ if (!revealIsControlled) setInnerReveal(next);
2147
+ onPasswordVisibleChange?.(next);
2148
+ };
2149
+ const debounceTimer = useRef();
2150
+ useEffect(() => () => clearTimeout(debounceTimer.current), []);
2151
+ const emitChange = (v, e) => {
2152
+ if (debounce && debounce > 0) {
2153
+ clearTimeout(debounceTimer.current);
2154
+ debounceTimer.current = setTimeout(() => onChange?.(v, e), debounce);
2155
+ } else {
2156
+ onChange?.(v, e);
2157
+ }
2158
+ };
1395
2159
  const showToggle = (passwordToggle ?? type === "password") && !disabled && !readOnly;
1396
2160
  const effectiveType = type === "password" && reveal ? "text" : type;
1397
2161
  const hasValue = value.length > 0;
1398
2162
  const showClear = clearable && hasValue && !disabled && !readOnly;
2163
+ const countMax = showCount && typeof showCount === "object" ? showCount.max ?? maxLength : maxLength;
2164
+ const countNode = showCount && typeof showCount === "object" && showCount.formatter ? showCount.formatter(value.length, countMax) : `${value.length}${countMax ? `/${countMax}` : ""}`;
1399
2165
  const handleChange = (e) => {
1400
2166
  setValue(e.target.value);
1401
- onChange?.(e.target.value, e);
2167
+ emitChange(e.target.value, e);
1402
2168
  };
1403
2169
  const handleKeyDown = (e) => {
1404
2170
  if (e.key === "Enter") onEnter?.(value);
@@ -1430,16 +2196,15 @@ var Input = /* @__PURE__ */ forwardRef(
1430
2196
  invalid,
1431
2197
  valid,
1432
2198
  active: hasValue,
2199
+ addonBefore,
2200
+ addonAfter,
1433
2201
  left: startContent != null || prefix != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
1434
2202
  startContent,
1435
2203
  prefix != null && /* @__PURE__ */ jsx("span", { className: "orynn-box__adornment--affix", children: prefix })
1436
2204
  ] }) : void 0,
1437
2205
  right: /* @__PURE__ */ jsxs(Fragment, { children: [
1438
2206
  suffix != null && /* @__PURE__ */ jsx("span", { className: "orynn-box__adornment--affix", children: suffix }),
1439
- showCount && /* @__PURE__ */ jsxs("span", { className: "orynn-box__adornment--affix", children: [
1440
- value.length,
1441
- maxLength ? `/${maxLength}` : ""
1442
- ] }),
2207
+ showCount && /* @__PURE__ */ jsx("span", { className: "orynn-box__adornment--affix", children: countNode }),
1443
2208
  showClear && /* @__PURE__ */ jsx(
1444
2209
  "button",
1445
2210
  {
@@ -1459,8 +2224,8 @@ var Input = /* @__PURE__ */ forwardRef(
1459
2224
  "aria-label": reveal ? "Hide" : "Show",
1460
2225
  "aria-pressed": reveal,
1461
2226
  tabIndex: -1,
1462
- onClick: () => setReveal((v) => !v),
1463
- children: reveal ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {})
2227
+ onClick: () => setReveal(!reveal),
2228
+ children: visibilityToggleIcon ? visibilityToggleIcon(reveal) : reveal ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {})
1464
2229
  }
1465
2230
  ),
1466
2231
  loading && /* @__PURE__ */ jsx(SpinnerIcon, {}),
@@ -1497,6 +2262,11 @@ var Input = /* @__PURE__ */ forwardRef(
1497
2262
  );
1498
2263
  var clamp = (n, min, max) => Math.min(max ?? Number.POSITIVE_INFINITY, Math.max(min ?? Number.NEGATIVE_INFINITY, n));
1499
2264
  var round = (n, p) => p == null ? n : Math.round(n * 10 ** p) / 10 ** p;
2265
+ var SENTINEL_GROUP = "[[g]]";
2266
+ var SENTINEL_DECIMAL = "[[d]]";
2267
+ function swapSeparators(out, seps, thousandSeparator, decimalSeparator) {
2268
+ return out.split(seps.group).join(SENTINEL_GROUP).split(seps.decimal).join(SENTINEL_DECIMAL).split(SENTINEL_GROUP).join(thousandSeparator ?? seps.group).split(SENTINEL_DECIMAL).join(decimalSeparator ?? seps.decimal);
2269
+ }
1500
2270
  var NumberField = /* @__PURE__ */ forwardRef(
1501
2271
  function NumberField2(props, ref) {
1502
2272
  const {
@@ -1519,19 +2289,37 @@ var NumberField = /* @__PURE__ */ forwardRef(
1519
2289
  value: valueProp,
1520
2290
  defaultValue = null,
1521
2291
  onChange,
2292
+ onValueChange,
1522
2293
  min,
1523
2294
  max,
1524
2295
  step = 1,
1525
2296
  shiftStep,
2297
+ smallStep,
1526
2298
  precision,
2299
+ decimalScale,
2300
+ fixedDecimalScale,
1527
2301
  buttons = "stacked",
2302
+ hideControls,
1528
2303
  grouping,
2304
+ thousandSeparator,
2305
+ decimalSeparator,
1529
2306
  locale,
2307
+ style,
1530
2308
  currency,
2309
+ unit,
2310
+ formatOptions,
2311
+ format: formatFn,
2312
+ parse: parseFn,
1531
2313
  prefix,
1532
2314
  suffix,
1533
- clampOnBlur = true,
2315
+ allowNegative = true,
2316
+ clampBehavior,
2317
+ clampOnBlur,
2318
+ emptyValue = null,
1534
2319
  allowMouseWheel,
2320
+ isWheelDisabled,
2321
+ stepHoldDelay = 300,
2322
+ stepHoldInterval = 60,
1535
2323
  placeholder,
1536
2324
  ...rest
1537
2325
  } = props;
@@ -1542,15 +2330,45 @@ var NumberField = /* @__PURE__ */ forwardRef(
1542
2330
  defaultValue,
1543
2331
  onChange: void 0
1544
2332
  });
1545
- const fmt = useMemo(
1546
- () => new Intl.NumberFormat(locale, {
1547
- ...currency ? { style: "currency", currency } : {},
1548
- useGrouping: grouping ?? Boolean(currency),
1549
- maximumFractionDigits: precision ?? (currency ? 2 : 20),
1550
- minimumFractionDigits: currency ? 2 : 0
1551
- }),
1552
- [locale, currency, grouping, precision]
1553
- );
2333
+ const resolvedStyle = style ?? (currency ? "currency" : "decimal");
2334
+ const scale = decimalScale ?? precision;
2335
+ const clampMode = clampBehavior ?? (clampOnBlur === false ? "none" : "blur");
2336
+ const effMin = allowNegative ? min : Math.max(0, min ?? 0);
2337
+ const showButtons = hideControls ? false : buttons;
2338
+ const wheelEnabled = isWheelDisabled != null ? !isWheelDisabled : Boolean(allowMouseWheel);
2339
+ const fmt = useMemo(() => {
2340
+ if (formatOptions) return new Intl.NumberFormat(locale, formatOptions);
2341
+ const opts = {
2342
+ useGrouping: grouping ?? (thousandSeparator != null || resolvedStyle === "currency")
2343
+ };
2344
+ if (resolvedStyle === "currency") {
2345
+ opts.style = "currency";
2346
+ opts.currency = currency ?? "USD";
2347
+ } else if (resolvedStyle === "percent") {
2348
+ opts.style = "percent";
2349
+ } else if (resolvedStyle === "unit" && unit) {
2350
+ opts.style = "unit";
2351
+ opts.unit = unit;
2352
+ }
2353
+ if (scale != null) {
2354
+ opts.maximumFractionDigits = scale;
2355
+ if (fixedDecimalScale) opts.minimumFractionDigits = scale;
2356
+ } else {
2357
+ opts.maximumFractionDigits = resolvedStyle === "currency" ? 2 : 20;
2358
+ opts.minimumFractionDigits = resolvedStyle === "currency" ? 2 : 0;
2359
+ }
2360
+ return new Intl.NumberFormat(locale, opts);
2361
+ }, [
2362
+ locale,
2363
+ formatOptions,
2364
+ grouping,
2365
+ thousandSeparator,
2366
+ resolvedStyle,
2367
+ currency,
2368
+ unit,
2369
+ scale,
2370
+ fixedDecimalScale
2371
+ ]);
1554
2372
  const seps = useMemo(() => {
1555
2373
  const parts = new Intl.NumberFormat(locale).formatToParts(11111.1);
1556
2374
  return {
@@ -1559,23 +2377,47 @@ var NumberField = /* @__PURE__ */ forwardRef(
1559
2377
  };
1560
2378
  }, [locale]);
1561
2379
  const display = useCallback(
1562
- (n) => n == null || Number.isNaN(n) ? "" : `${prefix ?? ""}${fmt.format(n)}${suffix ?? ""}`,
1563
- [fmt, prefix, suffix]
2380
+ (n) => {
2381
+ if (n == null || Number.isNaN(n)) return "";
2382
+ if (formatFn) return formatFn(n);
2383
+ let out = fmt.format(n);
2384
+ if (thousandSeparator != null || decimalSeparator != null) {
2385
+ out = swapSeparators(out, seps, thousandSeparator, decimalSeparator);
2386
+ }
2387
+ return `${prefix ?? ""}${out}${suffix ?? ""}`;
2388
+ },
2389
+ [fmt, formatFn, prefix, suffix, seps, thousandSeparator, decimalSeparator]
1564
2390
  );
1565
2391
  const parse = useCallback(
1566
2392
  (raw) => {
2393
+ if (parseFn) return parseFn(raw);
1567
2394
  let s = raw;
1568
2395
  if (prefix) s = s.split(prefix).join("");
1569
2396
  if (suffix) s = s.split(suffix).join("");
1570
- s = s.split(seps.group).join("").split(seps.decimal).join(".");
2397
+ const grp = thousandSeparator ?? seps.group;
2398
+ const dec = decimalSeparator ?? seps.decimal;
2399
+ s = s.split(grp).join("").split(dec).join(".");
1571
2400
  s = s.replace(/[^\d.\-]/g, "");
2401
+ if (!allowNegative) s = s.replace(/-/g, "");
1572
2402
  if (s === "" || s === "-" || s === ".") return null;
1573
2403
  const first = s.indexOf(".");
1574
2404
  if (first !== -1) s = s.slice(0, first + 1) + s.slice(first + 1).replace(/\./g, "");
1575
- const n = Number(s);
1576
- return Number.isNaN(n) ? null : n;
2405
+ let n = Number(s);
2406
+ if (Number.isNaN(n)) return null;
2407
+ if (resolvedStyle === "percent" && !formatFn) n = n / 100;
2408
+ return n;
1577
2409
  },
1578
- [prefix, suffix, seps]
2410
+ [
2411
+ parseFn,
2412
+ prefix,
2413
+ suffix,
2414
+ seps,
2415
+ thousandSeparator,
2416
+ decimalSeparator,
2417
+ allowNegative,
2418
+ resolvedStyle,
2419
+ formatFn
2420
+ ]
1579
2421
  );
1580
2422
  const [text, setText] = useState(() => display(value));
1581
2423
  const [editing, setEditing] = useState(false);
@@ -1585,65 +2427,76 @@ var NumberField = /* @__PURE__ */ forwardRef(
1585
2427
  const commit = (n) => {
1586
2428
  setValue(n);
1587
2429
  onChange?.(n);
2430
+ onValueChange?.({ value: n, formatted: display(n) });
1588
2431
  };
1589
2432
  const setNumber = (n) => {
1590
2433
  commit(n);
1591
2434
  setText(display(n));
1592
2435
  };
1593
- const bump = (dir, big = false) => {
1594
- const delta = (big ? shiftStep ?? step * 10 : step) * dir;
1595
- const base2 = value ?? (dir > 0 ? min ?? 0 : max ?? 0);
1596
- setNumber(round(clamp(base2 + delta, min, max), precision));
2436
+ const bump = (dir, mode = "n") => {
2437
+ const amount = mode === "l" ? shiftStep ?? step * 10 : mode === "s" ? smallStep ?? step / 10 : step;
2438
+ const base2 = value ?? (dir > 0 ? effMin ?? 0 : max ?? 0);
2439
+ let next = round(base2 + amount * dir, scale);
2440
+ if (clampMode !== "none") next = clamp(next, effMin, max);
2441
+ else if (!allowNegative && next < 0) next = 0;
2442
+ setNumber(next);
1597
2443
  };
1598
2444
  const innerRef = useRef(null);
1599
2445
  useEffect(() => {
1600
2446
  const el = innerRef.current;
1601
- if (!el || !allowMouseWheel) return;
2447
+ if (!el || !wheelEnabled) return;
1602
2448
  const onWheel = (e) => {
1603
2449
  if (document.activeElement !== el) return;
1604
2450
  e.preventDefault();
1605
- bump(e.deltaY < 0 ? 1 : -1, e.shiftKey);
2451
+ bump(e.deltaY < 0 ? 1 : -1, e.shiftKey ? "l" : e.altKey ? "s" : "n");
1606
2452
  };
1607
2453
  el.addEventListener("wheel", onWheel, { passive: false });
1608
2454
  return () => el.removeEventListener("wheel", onWheel);
1609
2455
  });
1610
2456
  const onKeyDown = (e) => {
1611
2457
  if (disabled || readOnly) return;
2458
+ const mode = e.shiftKey ? "l" : e.altKey ? "s" : "n";
1612
2459
  if (e.key === "ArrowUp") {
1613
2460
  e.preventDefault();
1614
- bump(1, e.shiftKey);
2461
+ bump(1, mode);
1615
2462
  } else if (e.key === "ArrowDown") {
1616
2463
  e.preventDefault();
1617
- bump(-1, e.shiftKey);
2464
+ bump(-1, mode);
1618
2465
  } else if (e.key === "PageUp") {
1619
2466
  e.preventDefault();
1620
- bump(1, true);
2467
+ bump(1, "l");
1621
2468
  } else if (e.key === "PageDown") {
1622
2469
  e.preventDefault();
1623
- bump(-1, true);
1624
- } else if (e.key === "Home" && min != null) {
2470
+ bump(-1, "l");
2471
+ } else if (e.key === "Home" && effMin != null) {
1625
2472
  e.preventDefault();
1626
- setNumber(min);
2473
+ setNumber(effMin);
1627
2474
  } else if (e.key === "End" && max != null) {
1628
2475
  e.preventDefault();
1629
2476
  setNumber(max);
1630
2477
  }
1631
2478
  };
1632
2479
  const holdTimer = useRef();
2480
+ const holdDelay = useRef();
1633
2481
  const startHold = (dir) => {
1634
2482
  bump(dir);
1635
2483
  let count = 0;
1636
- holdTimer.current = setInterval(() => {
1637
- count += 1;
1638
- bump(dir, count > 6);
1639
- }, 110);
2484
+ holdDelay.current = setTimeout(() => {
2485
+ holdTimer.current = setInterval(() => {
2486
+ count += 1;
2487
+ bump(dir, count > 6 ? "l" : "n");
2488
+ }, stepHoldInterval);
2489
+ }, stepHoldDelay);
1640
2490
  };
1641
- const stopHold = () => clearInterval(holdTimer.current);
1642
- useEffect(() => () => clearInterval(holdTimer.current), []);
1643
- const StepButtons = buttons && /* @__PURE__ */ jsxs(
2491
+ const stopHold = () => {
2492
+ clearTimeout(holdDelay.current);
2493
+ clearInterval(holdTimer.current);
2494
+ };
2495
+ useEffect(() => stopHold, []);
2496
+ const StepButtons = showButtons && /* @__PURE__ */ jsxs(
1644
2497
  "div",
1645
2498
  {
1646
- className: `orynn-number__buttons${buttons === "horizontal" ? " orynn-number__buttons--horizontal" : ""}`,
2499
+ className: `orynn-number__buttons${showButtons === "horizontal" ? " orynn-number__buttons--horizontal" : ""}`,
1647
2500
  children: [
1648
2501
  /* @__PURE__ */ jsx(
1649
2502
  "button",
@@ -1656,7 +2509,7 @@ var NumberField = /* @__PURE__ */ forwardRef(
1656
2509
  onPointerDown: () => startHold(1),
1657
2510
  onPointerUp: stopHold,
1658
2511
  onPointerLeave: stopHold,
1659
- children: /* @__PURE__ */ jsx(PlusIcon, {})
2512
+ children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "orynn-number__caret" })
1660
2513
  }
1661
2514
  ),
1662
2515
  /* @__PURE__ */ jsx(
@@ -1666,11 +2519,11 @@ var NumberField = /* @__PURE__ */ forwardRef(
1666
2519
  className: "orynn-number__btn",
1667
2520
  "aria-label": "Decrement",
1668
2521
  tabIndex: -1,
1669
- disabled: disabled || readOnly || min != null && (value ?? 0) <= min,
2522
+ disabled: disabled || readOnly || effMin != null && (value ?? 0) <= effMin,
1670
2523
  onPointerDown: () => startHold(-1),
1671
2524
  onPointerUp: stopHold,
1672
2525
  onPointerLeave: stopHold,
1673
- children: /* @__PURE__ */ jsx(MinusIcon, {})
2526
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "orynn-number__caret" })
1674
2527
  }
1675
2528
  )
1676
2529
  ]
@@ -1721,7 +2574,7 @@ var NumberField = /* @__PURE__ */ forwardRef(
1721
2574
  inputMode: "decimal",
1722
2575
  role: "spinbutton",
1723
2576
  "aria-valuenow": value ?? void 0,
1724
- "aria-valuemin": min,
2577
+ "aria-valuemin": effMin,
1725
2578
  "aria-valuemax": max,
1726
2579
  value: text,
1727
2580
  disabled,
@@ -1733,13 +2586,16 @@ var NumberField = /* @__PURE__ */ forwardRef(
1733
2586
  onFocus: () => setEditing(true),
1734
2587
  onChange: (e) => {
1735
2588
  setText(e.target.value);
1736
- commit(parse(e.target.value));
2589
+ const parsed = parse(e.target.value);
2590
+ commit(
2591
+ parsed == null ? null : clampMode === "strict" ? clamp(parsed, effMin, max) : parsed
2592
+ );
1737
2593
  },
1738
2594
  onKeyDown,
1739
2595
  onBlur: () => {
1740
2596
  setEditing(false);
1741
2597
  const parsed = parse(text);
1742
- const final = parsed == null ? null : round(clampOnBlur ? clamp(parsed, min, max) : parsed, precision);
2598
+ const final = parsed == null ? emptyValue : round(clampMode !== "none" ? clamp(parsed, effMin, max) : parsed, scale);
1743
2599
  if (final !== value) commit(final);
1744
2600
  setText(display(final));
1745
2601
  }
@@ -1752,7 +2608,7 @@ var NumberField = /* @__PURE__ */ forwardRef(
1752
2608
  }
1753
2609
  );
1754
2610
  var RadioGroupContext = createContext(null);
1755
- var sizePx2 = { sm: "0.95rem", md: "1.125rem", lg: "1.35rem" };
2611
+ var sizePx2 = { sm: "0.875rem", md: "1rem", lg: "1.125rem" };
1756
2612
  var Radio = /* @__PURE__ */ forwardRef(
1757
2613
  function Radio2(props, ref) {
1758
2614
  const group = useContext(RadioGroupContext);
@@ -1829,6 +2685,7 @@ function RadioGroup(props) {
1829
2685
  defaultValue = null,
1830
2686
  onChange,
1831
2687
  orientation = "vertical",
2688
+ columns,
1832
2689
  options,
1833
2690
  children,
1834
2691
  className,
@@ -1864,12 +2721,17 @@ function RadioGroup(props) {
1864
2721
  "aria-labelledby": label != null ? `${id}-label` : void 0,
1865
2722
  "aria-required": required || void 0,
1866
2723
  className: "orynn-choice-group",
1867
- "data-orientation": orientation,
2724
+ "data-orientation": columns ? void 0 : orientation,
2725
+ "data-columns": columns ? "" : void 0,
2726
+ style: columns ? { "--orynn-choice-columns": columns } : void 0,
1868
2727
  children: options ? options.map((o) => /* @__PURE__ */ jsx(
1869
2728
  Radio,
1870
2729
  {
1871
2730
  value: o.value,
1872
- label: o.label,
2731
+ label: o.icon != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
2732
+ /* @__PURE__ */ jsx("span", { className: "orynn-choice__icon", children: o.icon }),
2733
+ o.label
2734
+ ] }) : o.label,
1873
2735
  description: o.description,
1874
2736
  disabled: o.disabled
1875
2737
  },
@@ -1896,12 +2758,17 @@ var Textarea = /* @__PURE__ */ forwardRef(
1896
2758
  floatingLabel,
1897
2759
  clearable,
1898
2760
  onClear,
2761
+ loading,
2762
+ endContent,
1899
2763
  id: idProp,
1900
2764
  name,
1901
2765
  className,
1902
2766
  value: valueProp,
1903
2767
  defaultValue,
1904
2768
  onChange,
2769
+ onEnter,
2770
+ submitOnEnter,
2771
+ onKeyDown,
1905
2772
  autoResize,
1906
2773
  minRows = 3,
1907
2774
  maxRows = 10,
@@ -1926,9 +2793,9 @@ var Textarea = /* @__PURE__ */ forwardRef(
1926
2793
  el.style.height = "auto";
1927
2794
  const cs = getComputedStyle(el);
1928
2795
  const line = Number.parseFloat(cs.lineHeight) || 20;
1929
- const pad2 = Number.parseFloat(cs.paddingTop) + Number.parseFloat(cs.paddingBottom);
1930
- const min = line * minRows + pad2;
1931
- const max = line * maxRows + pad2;
2796
+ const pad3 = Number.parseFloat(cs.paddingTop) + Number.parseFloat(cs.paddingBottom);
2797
+ const min = line * minRows + pad3;
2798
+ const max = line * maxRows + pad3;
1932
2799
  el.style.height = `${Math.min(Math.max(el.scrollHeight, min), max)}px`;
1933
2800
  el.style.overflowY = el.scrollHeight > max ? "auto" : "hidden";
1934
2801
  }, [autoResize, minRows, maxRows]);
@@ -1937,7 +2804,16 @@ var Textarea = /* @__PURE__ */ forwardRef(
1937
2804
  setValue(e.target.value);
1938
2805
  onChange?.(e.target.value, e);
1939
2806
  };
2807
+ const handleKeyDown = (e) => {
2808
+ if (e.key === "Enter" && !e.shiftKey && (onEnter || submitOnEnter)) {
2809
+ if (submitOnEnter) e.preventDefault();
2810
+ onEnter?.(value);
2811
+ }
2812
+ onKeyDown?.(e);
2813
+ };
1940
2814
  const hasValue = value.length > 0;
2815
+ const countMax = showCount && typeof showCount === "object" ? showCount.max ?? maxLength : maxLength;
2816
+ const countNode = showCount && typeof showCount === "object" && showCount.formatter ? showCount.formatter(value.length, countMax) : `${value.length}${countMax ? `/${countMax}` : ""}`;
1941
2817
  return /* @__PURE__ */ jsx(
1942
2818
  Field,
1943
2819
  {
@@ -1962,20 +2838,24 @@ var Textarea = /* @__PURE__ */ forwardRef(
1962
2838
  valid,
1963
2839
  active: hasValue,
1964
2840
  floatingLabel: floatingLabel ? /* @__PURE__ */ jsx("label", { className: "orynn-box__label", htmlFor: id, children: label }) : void 0,
1965
- right: clearable && hasValue && !disabled && !readOnly ? /* @__PURE__ */ jsx(
1966
- "button",
1967
- {
1968
- type: "button",
1969
- className: "orynn-box__iconbtn",
1970
- "aria-label": "Clear",
1971
- tabIndex: -1,
1972
- onClick: () => {
1973
- setValue("");
1974
- onClear?.();
1975
- },
1976
- children: "\u2715"
1977
- }
1978
- ) : void 0,
2841
+ right: clearable && hasValue || loading || endContent != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
2842
+ clearable && hasValue && !disabled && !readOnly && /* @__PURE__ */ jsx(
2843
+ "button",
2844
+ {
2845
+ type: "button",
2846
+ className: "orynn-box__iconbtn",
2847
+ "aria-label": "Clear",
2848
+ tabIndex: -1,
2849
+ onClick: () => {
2850
+ setValue("");
2851
+ onClear?.();
2852
+ },
2853
+ children: /* @__PURE__ */ jsx(XIcon, {})
2854
+ }
2855
+ ),
2856
+ loading && /* @__PURE__ */ jsx(SpinnerIcon, {}),
2857
+ endContent
2858
+ ] }) : void 0,
1979
2859
  children: [
1980
2860
  /* @__PURE__ */ jsx(
1981
2861
  "textarea",
@@ -1995,13 +2875,11 @@ var Textarea = /* @__PURE__ */ forwardRef(
1995
2875
  placeholder: floatingLabel ? void 0 : placeholder,
1996
2876
  "aria-invalid": invalid || void 0,
1997
2877
  "aria-describedby": describedById,
1998
- onChange: handleChange
2878
+ onChange: handleChange,
2879
+ onKeyDown: handleKeyDown
1999
2880
  }
2000
2881
  ),
2001
- showCount && /* @__PURE__ */ jsxs("span", { className: "orynn-box__footer", children: [
2002
- value.length,
2003
- maxLength ? `/${maxLength}` : ""
2004
- ] })
2882
+ showCount && /* @__PURE__ */ jsx("span", { className: "orynn-box__footer", children: countNode })
2005
2883
  ]
2006
2884
  }
2007
2885
  )
@@ -2058,24 +2936,63 @@ function registerField(type, registration) {
2058
2936
 
2059
2937
  // src/layout/resolve-columns.ts
2060
2938
  var DEFAULT_COLUMNS = { desktop: 3, tablet: 2, mobile: 1 };
2939
+ var DEFAULT_MIN_COL = 192;
2061
2940
  var clampCount = (n, fallback) => typeof n === "number" && Number.isFinite(n) && n >= 1 ? Math.floor(n) : fallback;
2062
- function resolveColumns(partial) {
2941
+ function resolveColumns(input) {
2942
+ if (typeof input === "number") {
2943
+ const n = clampCount(input, DEFAULT_COLUMNS.desktop);
2944
+ return { desktop: n, tablet: n, mobile: n };
2945
+ }
2063
2946
  return {
2064
- desktop: clampCount(partial?.desktop, DEFAULT_COLUMNS.desktop),
2065
- tablet: clampCount(partial?.tablet, DEFAULT_COLUMNS.tablet),
2066
- mobile: clampCount(partial?.mobile, DEFAULT_COLUMNS.mobile)
2947
+ desktop: clampCount(input?.desktop, DEFAULT_COLUMNS.desktop),
2948
+ tablet: clampCount(input?.tablet, DEFAULT_COLUMNS.tablet),
2949
+ mobile: clampCount(input?.mobile, DEFAULT_COLUMNS.mobile)
2067
2950
  };
2068
2951
  }
2952
+ function resolveGrid(width, cols, minColWidth = DEFAULT_MIN_COL) {
2953
+ const { desktop: d, tablet: t, mobile: m } = cols;
2954
+ if (!width || !Number.isFinite(width) || width <= 0) {
2955
+ return { count: d, tier: "desktop" };
2956
+ }
2957
+ const per = minColWidth > 0 ? minColWidth : DEFAULT_MIN_COL;
2958
+ const maxFit = Math.max(1, Math.floor(width / per));
2959
+ if (d === t && t === m) {
2960
+ const count = Math.min(d, maxFit);
2961
+ return { count, tier: count >= d ? "desktop" : count > 1 ? "tablet" : "mobile" };
2962
+ }
2963
+ if (maxFit >= d) return { count: d, tier: "desktop" };
2964
+ if (t < d && maxFit >= t) return { count: t, tier: "tablet" };
2965
+ if (maxFit >= m) return { count: m, tier: "mobile" };
2966
+ return { count: Math.min(m, maxFit), tier: "mobile" };
2967
+ }
2069
2968
  function clampSpan(span, columnCount) {
2070
2969
  const n = clampCount(span, 1);
2071
2970
  return Math.min(n, Math.max(columnCount, 1));
2072
2971
  }
2972
+ function pickSpan(layout, tier) {
2973
+ if (!layout) return 1;
2974
+ const v = layout[tier] ?? layout.desktop ?? layout.tablet ?? layout.mobile;
2975
+ return typeof v === "number" && Number.isFinite(v) && v >= 1 ? Math.floor(v) : 1;
2976
+ }
2073
2977
 
2074
2978
  // src/core/config/normalize.ts
2075
2979
  function normalizeGap(gap) {
2076
2980
  if (gap == null) return void 0;
2077
2981
  return typeof gap === "number" ? `${gap}px` : gap;
2078
2982
  }
2983
+ function normalizeMinColWidth(value) {
2984
+ if (typeof value === "number") return value > 0 ? value : DEFAULT_MIN_COL;
2985
+ if (typeof value === "string") {
2986
+ const n = Number.parseFloat(value);
2987
+ if (!Number.isFinite(n) || n <= 0) return DEFAULT_MIN_COL;
2988
+ if (/rem|em/i.test(value)) {
2989
+ const root = typeof document !== "undefined" ? Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16 : 16;
2990
+ return n * root;
2991
+ }
2992
+ return n;
2993
+ }
2994
+ return DEFAULT_MIN_COL;
2995
+ }
2079
2996
  function normalizeConfig(config, registry2) {
2080
2997
  invariant(
2081
2998
  config != null && typeof config === "object" && Array.isArray(config.fields),
@@ -2109,18 +3026,52 @@ function normalizeConfig(config, registry2) {
2109
3026
  return {
2110
3027
  fields: config.fields,
2111
3028
  columns: resolveColumns(config.columns),
3029
+ minColWidth: normalizeMinColWidth(config.minColumnWidth),
2112
3030
  gap: normalizeGap(config.gap),
2113
3031
  legend: config.legend,
2114
3032
  id: config.id
2115
3033
  };
2116
3034
  }
2117
3035
 
3036
+ // src/core/config/conditions.ts
3037
+ function evalCondition(when, values) {
3038
+ if (when == null) return true;
3039
+ if (typeof when === "function") return Boolean(when(values));
3040
+ const actual = values[when.field];
3041
+ if ("eq" in when) return actual === when.eq;
3042
+ if ("ne" in when) return actual !== when.ne;
3043
+ if ("in" in when) return Array.isArray(when.in) && when.in.includes(actual);
3044
+ if ("notIn" in when) return Array.isArray(when.notIn) && !when.notIn.includes(actual);
3045
+ if ("truthy" in when) return Boolean(actual) === Boolean(when.truthy);
3046
+ return Boolean(actual);
3047
+ }
3048
+
2118
3049
  // src/core/validation/messages.ts
2119
3050
  var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
3051
+ var dateStr = (p) => {
3052
+ const d = p instanceof Date ? p : new Date(String(p));
3053
+ return Number.isNaN(d.getTime()) ? String(p) : d.toLocaleDateString();
3054
+ };
2120
3055
  var defaultMessages = {
2121
3056
  required: ({ label }) => `${label} is required`,
2122
3057
  minLength: ({ param, label }) => `${label} must be at least ${plural(param, "character")}`,
2123
- maxLength: ({ param, label }) => `${label} must be at most ${plural(param, "character")}`
3058
+ maxLength: ({ param, label }) => `${label} must be at most ${plural(param, "character")}`,
3059
+ minItems: ({ param, label }) => `Select at least ${plural(param, "option")} for ${label}`,
3060
+ maxItems: ({ param, label }) => `Select at most ${plural(param, "option")} for ${label}`,
3061
+ min: ({ param, label }) => `${label} must be at least ${param}`,
3062
+ max: ({ param, label }) => `${label} must be at most ${param}`,
3063
+ integer: ({ label }) => `${label} must be a whole number`,
3064
+ minDate: ({ param, label }) => `${label} must be on or after ${dateStr(param)}`,
3065
+ maxDate: ({ param, label }) => `${label} must be on or before ${dateStr(param)}`,
3066
+ minRangeDays: ({ param, label }) => `${label} must span at least ${plural(param, "day")}`,
3067
+ maxRangeDays: ({ param, label }) => `${label} must span at most ${plural(param, "day")}`,
3068
+ email: ({ label }) => `${label} must be a valid email address`,
3069
+ url: ({ label }) => `${label} must be a valid URL`,
3070
+ pattern: ({ label }) => `${label} is not in the expected format`,
3071
+ oneOf: ({ label }) => `${label} is not an allowed value`,
3072
+ equals: ({ label }) => `${label} does not match`,
3073
+ validate: ({ label }) => `${label} is invalid`,
3074
+ schema: ({ label }) => `${label} is invalid`
2124
3075
  };
2125
3076
  var FALLBACK_LABEL = "This field";
2126
3077
  function resolveMessage(token, param, label, overrides) {
@@ -2134,6 +3085,50 @@ function resolveMessage(token, param, label, overrides) {
2134
3085
  // src/core/validation/rules.ts
2135
3086
  var isEmpty = (value) => value == null || value === "" || Array.isArray(value) && value.length === 0;
2136
3087
  var asLength = (value) => typeof value === "string" || Array.isArray(value) ? value.length : String(value ?? "").length;
3088
+ var toNumber = (value) => {
3089
+ if (typeof value === "number") return Number.isNaN(value) ? null : value;
3090
+ if (typeof value === "string" && value.trim() !== "") {
3091
+ const n = Number(value);
3092
+ return Number.isNaN(n) ? null : n;
3093
+ }
3094
+ return null;
3095
+ };
3096
+ var toTime = (value) => {
3097
+ if (value == null || value === "") return null;
3098
+ const d = value instanceof Date ? value : new Date(value);
3099
+ const t = d.getTime();
3100
+ return Number.isNaN(t) ? null : t;
3101
+ };
3102
+ var rangeSpanDays = (value) => {
3103
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3104
+ const { start, end } = value;
3105
+ const s = toTime(start);
3106
+ const e = toTime(end);
3107
+ if (s == null || e == null) return null;
3108
+ return Math.abs(Math.round((e - s) / 864e5)) + 1;
3109
+ };
3110
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3111
+ var URL_RE = /^(https?:\/\/)[^\s.]+\.\S{2,}$/i;
3112
+ function toRegExp(param) {
3113
+ if (param instanceof RegExp) return param;
3114
+ if (typeof param === "string") {
3115
+ try {
3116
+ return new RegExp(param);
3117
+ } catch {
3118
+ return null;
3119
+ }
3120
+ }
3121
+ if (param && typeof param === "object" && "value" in param) {
3122
+ const { value, flags } = param;
3123
+ if (value instanceof RegExp) return value;
3124
+ try {
3125
+ return new RegExp(value, flags);
3126
+ } catch {
3127
+ return null;
3128
+ }
3129
+ }
3130
+ return null;
3131
+ }
2137
3132
  var builtInRules = {
2138
3133
  required: (value, param) => {
2139
3134
  if (param === false) return null;
@@ -2146,6 +3141,84 @@ var builtInRules = {
2146
3141
  maxLength: (value, param) => {
2147
3142
  if (typeof param !== "number" || isEmpty(value)) return null;
2148
3143
  return asLength(value) > param ? "maxLength" : null;
3144
+ },
3145
+ minItems: (value, param) => {
3146
+ if (typeof param !== "number" || !Array.isArray(value)) return null;
3147
+ return value.length < param ? "minItems" : null;
3148
+ },
3149
+ maxItems: (value, param) => {
3150
+ if (typeof param !== "number" || !Array.isArray(value)) return null;
3151
+ return value.length > param ? "maxItems" : null;
3152
+ },
3153
+ email: (value, param) => {
3154
+ if (param === false || isEmpty(value)) return null;
3155
+ return EMAIL_RE.test(String(value)) ? null : "email";
3156
+ },
3157
+ url: (value, param) => {
3158
+ if (param === false || isEmpty(value)) return null;
3159
+ return URL_RE.test(String(value)) ? null : "url";
3160
+ },
3161
+ pattern: (value, param) => {
3162
+ if (isEmpty(value)) return null;
3163
+ const re = toRegExp(param);
3164
+ if (!re) return null;
3165
+ return re.test(String(value)) ? null : "pattern";
3166
+ },
3167
+ integer: (value, param) => {
3168
+ if (param === false || isEmpty(value)) return null;
3169
+ const n = toNumber(value);
3170
+ return n != null && Number.isInteger(n) ? null : "integer";
3171
+ },
3172
+ min: (value, param) => {
3173
+ if (typeof param !== "number" || isEmpty(value)) return null;
3174
+ const n = toNumber(value);
3175
+ return n != null && n < param ? "min" : null;
3176
+ },
3177
+ max: (value, param) => {
3178
+ if (typeof param !== "number" || isEmpty(value)) return null;
3179
+ const n = toNumber(value);
3180
+ return n != null && n > param ? "max" : null;
3181
+ },
3182
+ minDate: (value, param) => {
3183
+ if (isEmpty(value)) return null;
3184
+ const v = toTime(value);
3185
+ const p = toTime(param);
3186
+ return v != null && p != null && v < p ? "minDate" : null;
3187
+ },
3188
+ maxDate: (value, param) => {
3189
+ if (isEmpty(value)) return null;
3190
+ const v = toTime(value);
3191
+ const p = toTime(param);
3192
+ return v != null && p != null && v > p ? "maxDate" : null;
3193
+ },
3194
+ minRangeDays: (value, param) => {
3195
+ if (typeof param !== "number") return null;
3196
+ const span = rangeSpanDays(value);
3197
+ return span != null && span < param ? "minRangeDays" : null;
3198
+ },
3199
+ maxRangeDays: (value, param) => {
3200
+ if (typeof param !== "number") return null;
3201
+ const span = rangeSpanDays(value);
3202
+ return span != null && span > param ? "maxRangeDays" : null;
3203
+ },
3204
+ oneOf: (value, param) => {
3205
+ if (isEmpty(value) || !Array.isArray(param)) return null;
3206
+ return param.includes(value) ? null : "oneOf";
3207
+ },
3208
+ equals: (value, param, allValues) => {
3209
+ if (isEmpty(value)) return null;
3210
+ const target = param && typeof param === "object" && "field" in param ? allValues[param.field] : param;
3211
+ return value == target ? null : "equals";
3212
+ },
3213
+ validate: (value, param, allValues) => {
3214
+ if (typeof param !== "function") return null;
3215
+ const out = param(value, allValues);
3216
+ if (out instanceof Promise) {
3217
+ return out.then(
3218
+ (r) => typeof r === "string" ? r : r === false ? "validate" : null
3219
+ );
3220
+ }
3221
+ return typeof out === "string" ? out : out === false ? "validate" : null;
2149
3222
  }
2150
3223
  };
2151
3224
  var registry = new Map(Object.entries(builtInRules));
@@ -2159,12 +3232,12 @@ function getRule(name) {
2159
3232
  // src/core/validation/resolver.ts
2160
3233
  function createRuleResolver() {
2161
3234
  return (values, config) => {
2162
- const result = {};
3235
+ const pending = [];
2163
3236
  for (const field of config.fields) {
2164
3237
  const validation = field.validation;
2165
3238
  if (!validation) continue;
3239
+ if (!evalCondition(field.when, values)) continue;
2166
3240
  const value = values[field.name];
2167
- const errors = [];
2168
3241
  for (const key of Object.keys(validation)) {
2169
3242
  if (key === "messages") continue;
2170
3243
  const param = validation[key];
@@ -2176,16 +3249,34 @@ function createRuleResolver() {
2176
3249
  }
2177
3250
  continue;
2178
3251
  }
2179
- const token = rule(value, param, values);
2180
- if (token == null) continue;
2181
- errors.push({
3252
+ pending.push({
3253
+ field: field.name,
2182
3254
  rule: key,
2183
- message: resolveMessage(token, param, field.label, validation.messages)
3255
+ param,
3256
+ label: field.label,
3257
+ overrides: validation.messages,
3258
+ token: rule(value, param, values)
2184
3259
  });
2185
3260
  }
2186
- if (errors.length > 0) result[field.name] = errors;
2187
3261
  }
2188
- return result;
3262
+ const collect = (settled) => {
3263
+ const result = {};
3264
+ pending.forEach((p, i) => {
3265
+ const token = settled[i];
3266
+ if (token == null) return;
3267
+ const bucket = result[p.field] ?? [];
3268
+ bucket.push({
3269
+ rule: p.rule,
3270
+ message: resolveMessage(token, p.param, p.label, p.overrides)
3271
+ });
3272
+ result[p.field] = bucket;
3273
+ });
3274
+ return result;
3275
+ };
3276
+ if (pending.some((p) => p.token instanceof Promise)) {
3277
+ return Promise.all(pending.map((p) => Promise.resolve(p.token))).then(collect);
3278
+ }
3279
+ return collect(pending.map((p) => p.token));
2189
3280
  };
2190
3281
  }
2191
3282
  var defaultResolver = /* @__PURE__ */ createRuleResolver();
@@ -2417,23 +3508,43 @@ function useFieldsetState(config, options = {}) {
2417
3508
  getFieldProps
2418
3509
  };
2419
3510
  }
2420
- function FieldGrid({ columns, gap, className, children }) {
3511
+ var useBrowserLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
3512
+ var GridContext = createContext({ count: 1, tier: "desktop" });
3513
+ function FieldGrid({ columns, minColWidth, gap, className, children }) {
3514
+ const ref = useRef(null);
3515
+ const per = minColWidth && minColWidth > 0 ? minColWidth : DEFAULT_MIN_COL;
3516
+ const { desktop, tablet, mobile } = columns;
3517
+ const [grid, setGrid] = useState(() => resolveGrid(0, columns, per));
3518
+ useBrowserLayoutEffect(() => {
3519
+ const el = ref.current;
3520
+ if (!el) return;
3521
+ const cols = { desktop, tablet, mobile };
3522
+ const apply = (width) => {
3523
+ const next = resolveGrid(width, cols, per);
3524
+ setGrid((cur) => cur.count === next.count && cur.tier === next.tier ? cur : next);
3525
+ };
3526
+ apply(el.getBoundingClientRect().width);
3527
+ if (typeof ResizeObserver === "undefined") return;
3528
+ const ro = new ResizeObserver((entries) => {
3529
+ for (const entry of entries) {
3530
+ const box = entry.contentBoxSize?.[0];
3531
+ apply(box ? box.inlineSize : entry.contentRect.width);
3532
+ }
3533
+ });
3534
+ ro.observe(el);
3535
+ return () => ro.disconnect();
3536
+ }, [desktop, tablet, mobile, per]);
2421
3537
  const style = {
2422
- "--orynn-cols-desktop": columns.desktop,
2423
- "--orynn-cols-tablet": columns.tablet,
2424
- "--orynn-cols-mobile": columns.mobile,
3538
+ "--orynn-cols": grid.count,
2425
3539
  ...gap ? { "--orynn-grid-gap": gap } : {}
2426
3540
  };
2427
- return /* @__PURE__ */ jsx("div", { className: cx("orynn-grid", className), style, children: /* @__PURE__ */ jsx("div", { className: "orynn-grid__track", children }) });
3541
+ return /* @__PURE__ */ jsx("div", { ref, className: cx("orynn-grid", className), style, children: /* @__PURE__ */ jsx(GridContext.Provider, { value: grid, children: /* @__PURE__ */ jsx("div", { className: "orynn-grid__track", children }) }) });
2428
3542
  }
2429
- function FieldGridItem({ span, columns, children }) {
2430
- if (!span) return /* @__PURE__ */ jsx(Fragment, { children });
2431
- const style = {
2432
- "--orynn-span-desktop": clampSpan(span.desktop, columns.desktop),
2433
- "--orynn-span-tablet": clampSpan(span.tablet, columns.tablet),
2434
- "--orynn-span-mobile": clampSpan(span.mobile, columns.mobile)
2435
- };
2436
- return /* @__PURE__ */ jsx("div", { className: "orynn-grid__item", style, children });
3543
+ function FieldGridItem({ span, children }) {
3544
+ const { count, tier } = useContext(GridContext);
3545
+ const effective = span ? clampSpan(pickSpan(span, tier), count) : 1;
3546
+ if (effective <= 1) return /* @__PURE__ */ jsx(Fragment, { children });
3547
+ return /* @__PURE__ */ jsx("div", { className: "orynn-grid__item", style: { "--orynn-span": effective }, children });
2437
3548
  }
2438
3549
  var noop = () => {
2439
3550
  };
@@ -2443,6 +3554,7 @@ function FieldRendererImpl({ field, registry: registry2, slice, disabled }) {
2443
3554
  registration,
2444
3555
  `No control registered for field type "${field.type}" (field "${field.name}").`
2445
3556
  );
3557
+ const fallbackId = useId();
2446
3558
  if (!registration) return null;
2447
3559
  const Control = registration.component;
2448
3560
  const required = field.validation?.required === true;
@@ -2452,6 +3564,36 @@ function FieldRendererImpl({ field, registry: registry2, slice, disabled }) {
2452
3564
  const handleChange = (next) => {
2453
3565
  slice.setValue(registration.formatValue ? registration.formatValue(next) : next);
2454
3566
  };
3567
+ if (registration.selfContained) {
3568
+ return /* @__PURE__ */ jsx(
3569
+ "div",
3570
+ {
3571
+ style: { display: "contents" },
3572
+ onBlur: (e) => {
3573
+ if (!e.currentTarget.contains(e.relatedTarget)) slice.markTouched();
3574
+ },
3575
+ children: /* @__PURE__ */ jsx(
3576
+ Control,
3577
+ {
3578
+ id: fallbackId,
3579
+ name: field.name,
3580
+ value,
3581
+ onChange: handleChange,
3582
+ onBlur: slice.markTouched,
3583
+ onFocus: noop,
3584
+ disabled: isDisabled,
3585
+ readOnly: field.readOnly === true,
3586
+ required,
3587
+ invalid: messages.length > 0,
3588
+ label: field.label,
3589
+ description: field.description,
3590
+ errors: messages,
3591
+ config: field
3592
+ }
3593
+ )
3594
+ }
3595
+ );
3596
+ }
2455
3597
  return /* @__PURE__ */ jsx(
2456
3598
  Field,
2457
3599
  {
@@ -2484,90 +3626,290 @@ var FieldRenderer = /* @__PURE__ */ memo(
2484
3626
  FieldRendererImpl,
2485
3627
  (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
2486
3628
  );
2487
- var toStringValue = (value) => typeof value === "string" ? value : value == null ? "" : String(value);
2488
- var InputControl = ({
2489
- id,
2490
- name,
2491
- value,
2492
- onChange,
2493
- onBlur,
2494
- onFocus,
2495
- disabled,
2496
- readOnly,
2497
- required,
2498
- invalid,
2499
- describedById,
2500
- config
2501
- }) => {
2502
- const field = config;
3629
+ function FieldsetView({ state, registry: registry2, disabled, className, id }) {
3630
+ const activeRegistry = registry2 ?? defaultRegistry;
3631
+ const { columns, minColWidth, gap, legend, fields, id: configId } = state.config;
3632
+ const visible = fields.filter((f) => evalCondition(f.when, state.values));
3633
+ return /* @__PURE__ */ jsxs(
3634
+ "fieldset",
3635
+ {
3636
+ className: cx("orynn-fieldset", className),
3637
+ id: id ?? configId,
3638
+ disabled: disabled || void 0,
3639
+ children: [
3640
+ legend != null && /* @__PURE__ */ jsx("legend", { className: "orynn-fieldset__legend", children: legend }),
3641
+ /* @__PURE__ */ jsx(FieldGrid, { columns, minColWidth, gap, children: visible.map((field) => /* @__PURE__ */ jsx(FieldGridItem, { span: field.layout, children: /* @__PURE__ */ jsx(
3642
+ FieldRenderer,
3643
+ {
3644
+ field,
3645
+ registry: activeRegistry,
3646
+ slice: state.getFieldProps(field.name),
3647
+ disabled: disabled === true
3648
+ }
3649
+ ) }, field.name)) })
3650
+ ]
3651
+ }
3652
+ );
3653
+ }
3654
+ var str = (v) => typeof v === "string" ? v : v == null ? "" : String(v);
3655
+ var toISO = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
3656
+ function shell(p) {
3657
+ const c = p.config;
3658
+ return {
3659
+ id: p.id,
3660
+ name: p.name,
3661
+ label: p.label,
3662
+ description: p.description,
3663
+ error: p.errors,
3664
+ required: p.required,
3665
+ disabled: p.disabled,
3666
+ readOnly: p.readOnly,
3667
+ size: c.size,
3668
+ variant: c.variant,
3669
+ floatingLabel: c.floatingLabel
3670
+ };
3671
+ }
3672
+ var InputControl = (p) => {
3673
+ const c = p.config;
2503
3674
  return /* @__PURE__ */ jsx(
2504
- "input",
3675
+ Input,
2505
3676
  {
2506
- id,
2507
- name,
2508
- className: "orynn-input",
2509
- type: field.inputType ?? "text",
2510
- value: toStringValue(value),
2511
- placeholder: field.placeholder,
2512
- required,
2513
- disabled,
2514
- readOnly,
2515
- "aria-invalid": invalid || void 0,
2516
- "aria-describedby": describedById,
2517
- onChange: (event) => onChange(event.target.value, event),
2518
- onBlur,
2519
- onFocus,
2520
- ...field.props
3677
+ ...shell(p),
3678
+ type: c.inputType ?? "text",
3679
+ prefix: c.prefix,
3680
+ suffix: c.suffix,
3681
+ addonBefore: c.addonBefore,
3682
+ addonAfter: c.addonAfter,
3683
+ clearable: c.clearable,
3684
+ maxLength: c.maxLength,
3685
+ showCount: c.showCount,
3686
+ debounce: c.debounce,
3687
+ inputMode: c.inputMode,
3688
+ pattern: c.pattern,
3689
+ placeholder: c.placeholder,
3690
+ value: str(p.value),
3691
+ onChange: (v) => p.onChange(v),
3692
+ ...c.props
2521
3693
  }
2522
3694
  );
2523
3695
  };
2524
- var DropdownControl = ({
2525
- id,
2526
- name,
2527
- value,
2528
- onChange,
2529
- onBlur,
2530
- onFocus,
2531
- disabled,
2532
- required,
2533
- invalid,
2534
- describedById,
2535
- config
2536
- }) => {
2537
- const field = config;
2538
- return /* @__PURE__ */ jsxs(
2539
- "select",
3696
+ var TextareaControl = (p) => {
3697
+ const c = p.config;
3698
+ return /* @__PURE__ */ jsx(
3699
+ Textarea,
2540
3700
  {
2541
- id,
2542
- name,
2543
- className: "orynn-dropdown",
2544
- value: toStringValue(value),
2545
- required,
2546
- disabled,
2547
- "aria-invalid": invalid || void 0,
2548
- "aria-describedby": describedById,
2549
- onChange: (event) => onChange(event.target.value, event),
2550
- onBlur,
2551
- onFocus,
2552
- ...field.props,
2553
- children: [
2554
- field.placeholder != null && /* @__PURE__ */ jsx("option", { value: "", disabled: required, children: field.placeholder }),
2555
- field.options.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value))
2556
- ]
3701
+ ...shell(p),
3702
+ minRows: c.rows,
3703
+ maxRows: c.maxRows,
3704
+ autoResize: c.autoResize,
3705
+ resize: c.resize,
3706
+ submitOnEnter: c.submitOnEnter,
3707
+ maxLength: c.maxLength,
3708
+ showCount: c.showCount,
3709
+ placeholder: c.placeholder,
3710
+ value: str(p.value),
3711
+ onChange: (v) => p.onChange(v),
3712
+ ...c.props
2557
3713
  }
2558
3714
  );
2559
3715
  };
2560
- function registerBuiltins(registry2) {
2561
- if (!registry2.has("input")) {
2562
- registry2.register("input", { component: InputControl, defaultValue: "" });
3716
+ var NumberFieldControl = (p) => {
3717
+ const c = p.config;
3718
+ return /* @__PURE__ */ jsx(
3719
+ NumberField,
3720
+ {
3721
+ ...shell(p),
3722
+ min: c.min,
3723
+ max: c.max,
3724
+ step: c.step,
3725
+ precision: c.precision,
3726
+ decimalScale: c.decimalScale,
3727
+ currency: c.currency,
3728
+ style: c.style,
3729
+ locale: c.locale,
3730
+ prefix: c.prefix,
3731
+ suffix: c.suffix,
3732
+ thousandSeparator: c.thousandSeparator,
3733
+ allowNegative: c.allowNegative,
3734
+ clampBehavior: c.clampBehavior,
3735
+ hideControls: c.hideControls,
3736
+ placeholder: c.placeholder,
3737
+ value: typeof p.value === "number" ? p.value : null,
3738
+ onChange: (v) => p.onChange(v),
3739
+ ...c.props
3740
+ }
3741
+ );
3742
+ };
3743
+ var DropdownControl = (p) => {
3744
+ const c = p.config;
3745
+ const value = c.multiple ? Array.isArray(p.value) ? p.value : [] : Array.isArray(p.value) ? p.value[0] ?? "" : str(p.value);
3746
+ return /* @__PURE__ */ jsx(
3747
+ Dropdown,
3748
+ {
3749
+ ...shell(p),
3750
+ options: c.options,
3751
+ searchable: c.searchable,
3752
+ multiple: c.multiple,
3753
+ clearable: c.clearable,
3754
+ creatable: c.creatable,
3755
+ maxValues: c.maxValues,
3756
+ hidePickedOptions: c.hidePickedOptions,
3757
+ showSelectAll: c.showSelectAll,
3758
+ searchDebounce: c.searchDebounce,
3759
+ placeholder: c.placeholder,
3760
+ value,
3761
+ onChange: (v) => p.onChange(v ?? (c.multiple ? [] : "")),
3762
+ ...c.props
3763
+ }
3764
+ );
3765
+ };
3766
+ var DateControl = (p) => {
3767
+ const c = p.config;
3768
+ const toDate2 = (x) => x == null ? void 0 : x instanceof Date ? x : new Date(x);
3769
+ const mode = c.mode ?? "single";
3770
+ const common = {
3771
+ ...shell(p),
3772
+ format: c.format,
3773
+ precision: c.precision,
3774
+ minDate: toDate2(c.minDate),
3775
+ maxDate: toDate2(c.maxDate),
3776
+ showToday: c.showToday,
3777
+ clearable: c.clearable,
3778
+ placeholder: c.placeholder,
3779
+ presets: c.presets,
3780
+ ...c.props
3781
+ };
3782
+ if (mode === "range") {
3783
+ const v = p.value ?? {};
3784
+ return /* @__PURE__ */ jsx(
3785
+ DatePicker,
3786
+ {
3787
+ ...common,
3788
+ mode: "range",
3789
+ value: { start: v.start ?? null, end: v.end ?? null },
3790
+ onChange: (r) => p.onChange({
3791
+ start: r.start ? toISO(r.start) : "",
3792
+ end: r.end ? toISO(r.end) : ""
3793
+ })
3794
+ }
3795
+ );
2563
3796
  }
2564
- if (!registry2.has("dropdown")) {
2565
- registry2.register("dropdown", {
2566
- component: DropdownControl,
2567
- defaultValue: "",
2568
- validateConfig: (config) => Array.isArray(config.options) ? null : "a dropdown field requires an `options` array"
2569
- });
3797
+ if (mode === "multiple") {
3798
+ return /* @__PURE__ */ jsx(
3799
+ DatePicker,
3800
+ {
3801
+ ...common,
3802
+ mode: "multiple",
3803
+ value: Array.isArray(p.value) ? p.value : [],
3804
+ onChange: (arr) => p.onChange(arr.map(toISO))
3805
+ }
3806
+ );
2570
3807
  }
3808
+ return /* @__PURE__ */ jsx(
3809
+ DatePicker,
3810
+ {
3811
+ ...common,
3812
+ value: typeof p.value === "string" && p.value ? p.value : null,
3813
+ onChange: (d) => p.onChange(d instanceof Date ? toISO(d) : "")
3814
+ }
3815
+ );
3816
+ };
3817
+ var CheckboxControl = (p) => {
3818
+ const c = p.config;
3819
+ return /* @__PURE__ */ jsxs("div", { className: "orynn-field", children: [
3820
+ /* @__PURE__ */ jsx(
3821
+ Checkbox,
3822
+ {
3823
+ id: p.id,
3824
+ name: p.name,
3825
+ label: c.checkboxLabel ?? c.label,
3826
+ description: c.description,
3827
+ checked: Boolean(p.value),
3828
+ onChange: (v) => p.onChange(v),
3829
+ disabled: p.disabled,
3830
+ required: p.required,
3831
+ invalid: (p.errors?.length ?? 0) > 0,
3832
+ ...c.props
3833
+ }
3834
+ ),
3835
+ p.errors?.[0] && /* @__PURE__ */ jsx("p", { className: "orynn-field__error", role: "alert", children: p.errors[0] })
3836
+ ] });
3837
+ };
3838
+ var CheckboxGroupControl = (p) => {
3839
+ const c = p.config;
3840
+ return /* @__PURE__ */ jsx(
3841
+ CheckboxGroup,
3842
+ {
3843
+ id: p.id,
3844
+ name: p.name,
3845
+ label: p.label,
3846
+ description: p.description,
3847
+ error: p.errors,
3848
+ required: p.required,
3849
+ disabled: p.disabled,
3850
+ options: c.options.map((o) => ({
3851
+ label: o.label,
3852
+ value: o.value,
3853
+ description: o.description,
3854
+ icon: o.icon,
3855
+ disabled: o.disabled
3856
+ })),
3857
+ orientation: c.orientation,
3858
+ columns: c.columns,
3859
+ showSelectAll: c.showSelectAll,
3860
+ min: c.min,
3861
+ max: c.max,
3862
+ value: Array.isArray(p.value) ? p.value : [],
3863
+ onChange: (v) => p.onChange(v),
3864
+ ...c.props
3865
+ }
3866
+ );
3867
+ };
3868
+ var RadioControl = (p) => {
3869
+ const c = p.config;
3870
+ return /* @__PURE__ */ jsx(
3871
+ RadioGroup,
3872
+ {
3873
+ id: p.id,
3874
+ name: p.name,
3875
+ label: p.label,
3876
+ description: p.description,
3877
+ error: p.errors,
3878
+ required: p.required,
3879
+ disabled: p.disabled,
3880
+ options: c.options.map((o) => ({
3881
+ label: o.label,
3882
+ value: o.value,
3883
+ description: o.description,
3884
+ icon: o.icon,
3885
+ disabled: o.disabled
3886
+ })),
3887
+ orientation: c.orientation,
3888
+ columns: c.columns,
3889
+ variant: c.radioVariant,
3890
+ value: typeof p.value === "string" && p.value ? p.value : null,
3891
+ onChange: (v) => p.onChange(v),
3892
+ ...c.props
3893
+ }
3894
+ );
3895
+ };
3896
+ var needsOptions = (config) => Array.isArray(config.options) ? null : "requires an `options` array";
3897
+ function registerBuiltins(registry2) {
3898
+ const add = (type, component, extra = {}) => {
3899
+ if (!registry2.has(type)) {
3900
+ registry2.register(type, { component, selfContained: true, defaultValue: "", ...extra });
3901
+ }
3902
+ };
3903
+ add("input", InputControl);
3904
+ add("textarea", TextareaControl);
3905
+ add("number", NumberFieldControl, {
3906
+ parseValue: (raw) => typeof raw === "number" ? raw : raw === "" || raw == null ? null : Number(raw)
3907
+ });
3908
+ add("dropdown", DropdownControl, { validateConfig: needsOptions });
3909
+ add("date", DateControl);
3910
+ add("checkbox", CheckboxControl, { defaultValue: false });
3911
+ add("checkbox-group", CheckboxGroupControl, { defaultValue: [], validateConfig: needsOptions });
3912
+ add("radio", RadioControl, { validateConfig: needsOptions });
2571
3913
  }
2572
3914
  var seeded = false;
2573
3915
  function ensureBuiltins(defaultRegistry2) {
@@ -2601,95 +3943,857 @@ function Fieldset(props) {
2601
3943
  ...validateOn ? { validateOn } : {}
2602
3944
  };
2603
3945
  const fs = useFieldsetState(config, options);
2604
- const { columns, gap, legend, fields, id: configId } = fs.config;
2605
- return /* @__PURE__ */ jsxs(
2606
- "fieldset",
3946
+ return /* @__PURE__ */ jsx(
3947
+ FieldsetView,
2607
3948
  {
2608
- className: cx("orynn-fieldset", className),
2609
- id: id ?? configId,
2610
- disabled: disabled || void 0,
2611
- children: [
2612
- legend != null && /* @__PURE__ */ jsx("legend", { className: "orynn-fieldset__legend", children: legend }),
2613
- /* @__PURE__ */ jsx(FieldGrid, { columns, gap, children: fields.map((field) => /* @__PURE__ */ jsx(FieldGridItem, { span: field.layout, columns, children: /* @__PURE__ */ jsx(
2614
- FieldRenderer,
2615
- {
2616
- field,
2617
- registry: activeRegistry,
2618
- slice: fs.getFieldProps(field.name),
2619
- disabled: disabled === true
2620
- }
2621
- ) }, field.name)) })
2622
- ]
3949
+ state: fs,
3950
+ registry: activeRegistry,
3951
+ disabled: disabled === true,
3952
+ className,
3953
+ id
2623
3954
  }
2624
3955
  );
2625
3956
  }
2626
-
2627
- // src/theme/theme.ts
2628
- var RADIUS_KEYWORDS = /* @__PURE__ */ new Set(["none", "sm", "md", "lg", "pill"]);
2629
- var len = (v) => typeof v === "number" ? `${v}px` : v;
2630
- function contrastFor(color) {
2631
- const m = /^#?([\da-f]{3}|[\da-f]{6})$/i.exec(color.trim());
2632
- if (!m?.[1]) return "#ffffff";
2633
- const hex = m[1].length === 3 ? m[1].replace(/./g, "$&$&") : m[1];
2634
- const n = Number.parseInt(hex, 16);
2635
- const toLin = (v) => {
2636
- const c = v / 255;
2637
- return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
2638
- };
2639
- const lum = 0.2126 * toLin(n >> 16 & 255) + 0.7152 * toLin(n >> 8 & 255) + 0.0722 * toLin(n & 255);
2640
- return lum > 0.42 ? "#0b0d12" : "#ffffff";
2641
- }
2642
- function normalizeTheme(input) {
2643
- return typeof input === "string" ? { preset: input } : input ?? {};
2644
- }
2645
- function resolveThemeVars(config) {
2646
- const vars = {};
2647
- if (config.accent) {
2648
- vars["--orynn-color-accent"] = config.accent;
2649
- vars["--orynn-color-accent-hover"] = `color-mix(in srgb, ${config.accent} 86%, #000)`;
2650
- vars["--orynn-color-accent-contrast"] = config.accentContrast ?? contrastFor(config.accent);
2651
- }
2652
- if (config.ring) vars["--orynn-color-focus-ring"] = config.ring;
2653
- if (config.font) vars["--orynn-font-family"] = config.font;
2654
- if (config.fontSize != null) vars["--orynn-font-size"] = len(config.fontSize);
2655
- if (config.controlWidth != null) vars["--orynn-control-width"] = len(config.controlWidth);
2656
- if (config.radius != null) {
2657
- const r = config.radius;
2658
- if (r === "md") vars["--orynn-radius"] = "10px";
2659
- else if (typeof r === "string" && RADIUS_KEYWORDS.has(r))
2660
- vars["--orynn-radius"] = `var(--orynn-radius-${r})`;
2661
- else vars["--orynn-radius"] = len(r);
2662
- }
2663
- Object.assign(vars, config.vars ?? {});
2664
- return vars;
3957
+ function Form(props) {
3958
+ const {
3959
+ config,
3960
+ value,
3961
+ defaultValue,
3962
+ onChange,
3963
+ onSubmit,
3964
+ onInvalid,
3965
+ resolver,
3966
+ validateOn,
3967
+ disabled,
3968
+ children,
3969
+ className,
3970
+ ...rest
3971
+ } = props;
3972
+ ensureBuiltins(defaultRegistry);
3973
+ const fs = useFieldsetState(config, {
3974
+ registry: defaultRegistry,
3975
+ ...value !== void 0 ? { value } : {},
3976
+ ...defaultValue !== void 0 ? { defaultValue } : {},
3977
+ ...onChange ? { onChange } : {},
3978
+ ...resolver ? { resolver } : {},
3979
+ ...validateOn ? { validateOn } : {}
3980
+ });
3981
+ return /* @__PURE__ */ jsxs(
3982
+ "form",
3983
+ {
3984
+ ...rest,
3985
+ className: cx("orynn-form", className),
3986
+ noValidate: true,
3987
+ onSubmit: fs.handleSubmit(
3988
+ (values) => onSubmit(values, fs),
3989
+ (errors) => onInvalid?.(errors)
3990
+ ),
3991
+ children: [
3992
+ /* @__PURE__ */ jsx(FieldsetView, { state: fs, disabled }),
3993
+ typeof children === "function" ? children(fs) : children
3994
+ ]
3995
+ }
3996
+ );
2665
3997
  }
2666
- var OrynnContext = createContext({
2667
- preset: "default",
2668
- density: "comfortable",
2669
- config: {}
3998
+
3999
+ // src/primitives/shared/classes.ts
4000
+ var mod = (base2, key, fallback) => key === fallback ? void 0 : `${base2}--${key}`;
4001
+ function buttonClasses(variant = "default", size = "default") {
4002
+ return cx(
4003
+ "orynn-button",
4004
+ mod("orynn-button", variant, "default"),
4005
+ mod("orynn-button", size, "default")
4006
+ );
4007
+ }
4008
+ function badgeClasses(variant = "default") {
4009
+ return cx("orynn-badge", mod("orynn-badge", variant, "default"));
4010
+ }
4011
+ var Button = /* @__PURE__ */ forwardRef(function Button2({
4012
+ className,
4013
+ variant = "default",
4014
+ size = "default",
4015
+ asChild = false,
4016
+ type,
4017
+ loading = false,
4018
+ loadingText,
4019
+ leftIcon,
4020
+ rightIcon,
4021
+ fullWidth = false,
4022
+ disabled,
4023
+ children,
4024
+ ...props
4025
+ }, ref) {
4026
+ const shared = {
4027
+ "data-slot": "button",
4028
+ "data-variant": variant,
4029
+ "data-size": size,
4030
+ "data-loading": loading || void 0,
4031
+ "data-full-width": fullWidth || void 0,
4032
+ className: cx(buttonClasses(variant, size), className),
4033
+ ...props
4034
+ };
4035
+ if (asChild) {
4036
+ return /* @__PURE__ */ jsx(Slot.Root, { ref, "aria-busy": loading || void 0, ...shared, children });
4037
+ }
4038
+ return /* @__PURE__ */ jsxs(
4039
+ "button",
4040
+ {
4041
+ ref,
4042
+ type: type ?? "button",
4043
+ disabled: disabled || loading,
4044
+ "aria-busy": loading || void 0,
4045
+ ...shared,
4046
+ children: [
4047
+ loading ? /* @__PURE__ */ jsx(SpinnerIcon, { className: "orynn-button__spinner" }) : leftIcon,
4048
+ loading && loadingText != null ? loadingText : children,
4049
+ !loading && rightIcon
4050
+ ]
4051
+ }
4052
+ );
2670
4053
  });
2671
- function OrynnProvider({ theme, as = "div", className, children }) {
2672
- const config = useMemo(() => normalizeTheme(theme), [theme]);
2673
- const preset = config.preset ?? "default";
2674
- const density = config.density ?? "comfortable";
2675
- const style = useMemo(() => resolveThemeVars(config), [config]);
2676
- const ctx = useMemo(
2677
- () => ({ preset, density, config }),
2678
- [preset, density, config]
4054
+ var Label = /* @__PURE__ */ forwardRef(function Label2({ className, required, optional, children, ...props }, ref) {
4055
+ return /* @__PURE__ */ jsxs(
4056
+ Label$1.Root,
4057
+ {
4058
+ ref,
4059
+ "data-slot": "label",
4060
+ className: cx("orynn-label", className),
4061
+ ...props,
4062
+ children: [
4063
+ children,
4064
+ required && /* @__PURE__ */ jsx("span", { className: "orynn-label__required", "aria-hidden": "true", children: "*" }),
4065
+ !required && optional && /* @__PURE__ */ jsx("span", { className: "orynn-label__optional", children: optional === true ? "(optional)" : optional })
4066
+ ]
4067
+ }
2679
4068
  );
2680
- const dataProps = {
2681
- "data-orynn-theme": preset === "default" ? void 0 : preset,
2682
- "data-orynn-density": density === "comfortable" ? void 0 : density
4069
+ });
4070
+ var Card = /* @__PURE__ */ forwardRef(function Card2({ className, asChild = false, interactive = false, ...props }, ref) {
4071
+ const shared = {
4072
+ "data-slot": "card",
4073
+ "data-interactive": interactive || void 0,
4074
+ className: cx("orynn-card", className),
4075
+ ...props
4076
+ };
4077
+ return asChild ? /* @__PURE__ */ jsx(Slot.Root, { ref, ...shared }) : /* @__PURE__ */ jsx("div", { ref, ...shared });
4078
+ });
4079
+ var CardHeader = /* @__PURE__ */ forwardRef(function CardHeader2({ className, ...props }, ref) {
4080
+ return /* @__PURE__ */ jsx(
4081
+ "div",
4082
+ {
4083
+ ref,
4084
+ "data-slot": "card-header",
4085
+ className: cx("orynn-card__header", className),
4086
+ ...props
4087
+ }
4088
+ );
4089
+ });
4090
+ var CardTitle = /* @__PURE__ */ forwardRef(function CardTitle2({ className, ...props }, ref) {
4091
+ return /* @__PURE__ */ jsx(
4092
+ "div",
4093
+ {
4094
+ ref,
4095
+ "data-slot": "card-title",
4096
+ className: cx("orynn-card__title", className),
4097
+ ...props
4098
+ }
4099
+ );
4100
+ });
4101
+ var CardDescription = /* @__PURE__ */ forwardRef(
4102
+ function CardDescription2({ className, ...props }, ref) {
4103
+ return /* @__PURE__ */ jsx(
4104
+ "div",
4105
+ {
4106
+ ref,
4107
+ "data-slot": "card-description",
4108
+ className: cx("orynn-card__description", className),
4109
+ ...props
4110
+ }
4111
+ );
4112
+ }
4113
+ );
4114
+ var CardAction = /* @__PURE__ */ forwardRef(function CardAction2({ className, ...props }, ref) {
4115
+ return /* @__PURE__ */ jsx(
4116
+ "div",
4117
+ {
4118
+ ref,
4119
+ "data-slot": "card-action",
4120
+ className: cx("orynn-card__action", className),
4121
+ ...props
4122
+ }
4123
+ );
4124
+ });
4125
+ var CardContent = /* @__PURE__ */ forwardRef(
4126
+ function CardContent2({ className, ...props }, ref) {
4127
+ return /* @__PURE__ */ jsx(
4128
+ "div",
4129
+ {
4130
+ ref,
4131
+ "data-slot": "card-content",
4132
+ className: cx("orynn-card__content", className),
4133
+ ...props
4134
+ }
4135
+ );
4136
+ }
4137
+ );
4138
+ var CardFooter = /* @__PURE__ */ forwardRef(function CardFooter2({ className, ...props }, ref) {
4139
+ return /* @__PURE__ */ jsx(
4140
+ "div",
4141
+ {
4142
+ ref,
4143
+ "data-slot": "card-footer",
4144
+ className: cx("orynn-card__footer", className),
4145
+ ...props
4146
+ }
4147
+ );
4148
+ });
4149
+ var Badge = /* @__PURE__ */ forwardRef(function Badge2({
4150
+ className,
4151
+ variant = "default",
4152
+ size = "md",
4153
+ dot = false,
4154
+ icon,
4155
+ removable = false,
4156
+ onRemove,
4157
+ asChild = false,
4158
+ children,
4159
+ ...props
4160
+ }, ref) {
4161
+ const shared = {
4162
+ "data-slot": "badge",
4163
+ "data-variant": variant,
4164
+ "data-size": size,
4165
+ className: cx(badgeClasses(variant), className),
4166
+ ...props
2683
4167
  };
2684
- return /* @__PURE__ */ jsx(OrynnContext.Provider, { value: ctx, children: as === "contents" ? /* @__PURE__ */ jsx("div", { style: { display: "contents" }, ...dataProps, children }) : as === "span" ? /* @__PURE__ */ jsx("span", { className: cx("orynn-root", className), style, ...dataProps, children }) : /* @__PURE__ */ jsx("div", { className: cx("orynn-root", className), style, ...dataProps, children }) });
4168
+ if (asChild) {
4169
+ return /* @__PURE__ */ jsx(Slot.Root, { ref, ...shared, children });
4170
+ }
4171
+ return /* @__PURE__ */ jsxs("span", { ref, ...shared, children: [
4172
+ dot && /* @__PURE__ */ jsx("span", { className: "orynn-badge__dot", "aria-hidden": "true" }),
4173
+ icon,
4174
+ children,
4175
+ removable && /* @__PURE__ */ jsx(
4176
+ "button",
4177
+ {
4178
+ type: "button",
4179
+ className: "orynn-badge__remove",
4180
+ "aria-label": "Remove",
4181
+ onClick: onRemove,
4182
+ children: /* @__PURE__ */ jsx(XIcon, {})
4183
+ }
4184
+ )
4185
+ ] });
4186
+ });
4187
+ var Alert = /* @__PURE__ */ forwardRef(function Alert2({ className, variant = "default", icon, title, dismissible, onDismiss, children, ...props }, ref) {
4188
+ return /* @__PURE__ */ jsxs(
4189
+ "div",
4190
+ {
4191
+ ref,
4192
+ "data-slot": "alert",
4193
+ "data-variant": variant,
4194
+ "data-dismissible": dismissible || void 0,
4195
+ role: "alert",
4196
+ className: cx("orynn-alert", variant !== "default" && `orynn-alert--${variant}`, className),
4197
+ ...props,
4198
+ children: [
4199
+ icon,
4200
+ title != null && /* @__PURE__ */ jsx(AlertTitle, { children: title }),
4201
+ children,
4202
+ dismissible && /* @__PURE__ */ jsx(
4203
+ "button",
4204
+ {
4205
+ type: "button",
4206
+ className: "orynn-alert__dismiss",
4207
+ "aria-label": "Dismiss",
4208
+ onClick: onDismiss,
4209
+ children: /* @__PURE__ */ jsx(XIcon, {})
4210
+ }
4211
+ )
4212
+ ]
4213
+ }
4214
+ );
4215
+ });
4216
+ var AlertTitle = /* @__PURE__ */ forwardRef(
4217
+ function AlertTitle2({ className, ...props }, ref) {
4218
+ return /* @__PURE__ */ jsx(
4219
+ "div",
4220
+ {
4221
+ ref,
4222
+ "data-slot": "alert-title",
4223
+ className: cx("orynn-alert__title", className),
4224
+ ...props
4225
+ }
4226
+ );
4227
+ }
4228
+ );
4229
+ var AlertDescription = /* @__PURE__ */ forwardRef(
4230
+ function AlertDescription2({ className, ...props }, ref) {
4231
+ return /* @__PURE__ */ jsx(
4232
+ "div",
4233
+ {
4234
+ ref,
4235
+ "data-slot": "alert-description",
4236
+ className: cx("orynn-alert__description", className),
4237
+ ...props
4238
+ }
4239
+ );
4240
+ }
4241
+ );
4242
+ var Separator = /* @__PURE__ */ forwardRef(
4243
+ function Separator2({
4244
+ className,
4245
+ orientation = "horizontal",
4246
+ decorative = true,
4247
+ variant = "solid",
4248
+ children,
4249
+ ...props
4250
+ }, ref) {
4251
+ if (children != null && orientation === "horizontal") {
4252
+ return /* @__PURE__ */ jsx(
4253
+ "div",
4254
+ {
4255
+ ref,
4256
+ role: "separator",
4257
+ "aria-orientation": "horizontal",
4258
+ "data-slot": "separator",
4259
+ "data-orientation": "horizontal",
4260
+ "data-variant": variant,
4261
+ className: cx("orynn-separator", "orynn-separator--labelled", className),
4262
+ ...props,
4263
+ children: /* @__PURE__ */ jsx("span", { className: "orynn-separator__label", children })
4264
+ }
4265
+ );
4266
+ }
4267
+ return /* @__PURE__ */ jsx(
4268
+ Separator$1.Root,
4269
+ {
4270
+ ref,
4271
+ "data-slot": "separator",
4272
+ "data-variant": variant,
4273
+ orientation,
4274
+ decorative,
4275
+ className: cx("orynn-separator", className),
4276
+ ...props
4277
+ }
4278
+ );
4279
+ }
4280
+ );
4281
+ var Table = /* @__PURE__ */ forwardRef(function Table2({ className, size, dense, ...props }, ref) {
4282
+ return /* @__PURE__ */ jsx("div", { "data-slot": "table-container", className: "orynn-table__wrap", children: /* @__PURE__ */ jsx(
4283
+ "table",
4284
+ {
4285
+ ref,
4286
+ "data-slot": "table",
4287
+ "data-size": dense ? "sm" : size,
4288
+ className: cx("orynn-table", className),
4289
+ ...props
4290
+ }
4291
+ ) });
4292
+ });
4293
+ var TableHeader = /* @__PURE__ */ forwardRef(
4294
+ function TableHeader2({ className, sticky, ...props }, ref) {
4295
+ return /* @__PURE__ */ jsx(
4296
+ "thead",
4297
+ {
4298
+ ref,
4299
+ "data-slot": "table-header",
4300
+ "data-sticky": sticky || void 0,
4301
+ className: cx("orynn-table__header", className),
4302
+ ...props
4303
+ }
4304
+ );
4305
+ }
4306
+ );
4307
+ var TableBody = /* @__PURE__ */ forwardRef(function TableBody2({ className, ...props }, ref) {
4308
+ return /* @__PURE__ */ jsx(
4309
+ "tbody",
4310
+ {
4311
+ ref,
4312
+ "data-slot": "table-body",
4313
+ className: cx("orynn-table__body", className),
4314
+ ...props
4315
+ }
4316
+ );
4317
+ });
4318
+ var TableFooter = /* @__PURE__ */ forwardRef(function TableFooter2({ className, ...props }, ref) {
4319
+ return /* @__PURE__ */ jsx(
4320
+ "tfoot",
4321
+ {
4322
+ ref,
4323
+ "data-slot": "table-footer",
4324
+ className: cx("orynn-table__footer", className),
4325
+ ...props
4326
+ }
4327
+ );
4328
+ });
4329
+ var TableRow = /* @__PURE__ */ forwardRef(
4330
+ function TableRow2({ className, selected, ...props }, ref) {
4331
+ return /* @__PURE__ */ jsx(
4332
+ "tr",
4333
+ {
4334
+ ref,
4335
+ "data-slot": "table-row",
4336
+ "data-state": selected ? "selected" : void 0,
4337
+ className: cx("orynn-table__row", className),
4338
+ ...props
4339
+ }
4340
+ );
4341
+ }
4342
+ );
4343
+ var TableHead = /* @__PURE__ */ forwardRef(
4344
+ function TableHead2({ className, ...props }, ref) {
4345
+ return /* @__PURE__ */ jsx(
4346
+ "th",
4347
+ {
4348
+ ref,
4349
+ "data-slot": "table-head",
4350
+ className: cx("orynn-table__head", className),
4351
+ ...props
4352
+ }
4353
+ );
4354
+ }
4355
+ );
4356
+ var TableCell = /* @__PURE__ */ forwardRef(
4357
+ function TableCell2({ className, ...props }, ref) {
4358
+ return /* @__PURE__ */ jsx(
4359
+ "td",
4360
+ {
4361
+ ref,
4362
+ "data-slot": "table-cell",
4363
+ className: cx("orynn-table__cell", className),
4364
+ ...props
4365
+ }
4366
+ );
4367
+ }
4368
+ );
4369
+ var TableCaption = /* @__PURE__ */ forwardRef(function TableCaption2({ className, ...props }, ref) {
4370
+ return /* @__PURE__ */ jsx(
4371
+ "caption",
4372
+ {
4373
+ ref,
4374
+ "data-slot": "table-caption",
4375
+ className: cx("orynn-table__caption", className),
4376
+ ...props
4377
+ }
4378
+ );
4379
+ });
4380
+ var Switch = /* @__PURE__ */ forwardRef(function Switch2({
4381
+ className,
4382
+ size = "default",
4383
+ label,
4384
+ description,
4385
+ labelPlacement = "end",
4386
+ onLabel,
4387
+ offLabel,
4388
+ thumbIcon,
4389
+ id: idProp,
4390
+ ...props
4391
+ }, ref) {
4392
+ const reactId = useId();
4393
+ const id = idProp ?? reactId;
4394
+ const control = /* @__PURE__ */ jsxs(
4395
+ Switch$1.Root,
4396
+ {
4397
+ ref,
4398
+ id,
4399
+ "data-slot": "switch",
4400
+ "data-size": size,
4401
+ className: cx("orynn-switch", className),
4402
+ ...props,
4403
+ children: [
4404
+ offLabel != null && /* @__PURE__ */ jsx("span", { className: "orynn-switch__text orynn-switch__text--off", "aria-hidden": "true", children: offLabel }),
4405
+ onLabel != null && /* @__PURE__ */ jsx("span", { className: "orynn-switch__text orynn-switch__text--on", "aria-hidden": "true", children: onLabel }),
4406
+ /* @__PURE__ */ jsx(Switch$1.Thumb, { "data-slot": "switch-thumb", className: "orynn-switch__thumb", children: thumbIcon })
4407
+ ]
4408
+ }
4409
+ );
4410
+ if (label == null && description == null) return control;
4411
+ return /* @__PURE__ */ jsxs(
4412
+ "label",
4413
+ {
4414
+ className: "orynn-switch-field",
4415
+ "data-slot": "switch-field",
4416
+ "data-placement": labelPlacement,
4417
+ htmlFor: id,
4418
+ children: [
4419
+ control,
4420
+ /* @__PURE__ */ jsxs("span", { className: "orynn-switch-field__text", children: [
4421
+ /* @__PURE__ */ jsx("span", { className: "orynn-switch-field__label", children: label }),
4422
+ description != null && /* @__PURE__ */ jsx("span", { className: "orynn-switch-field__description", children: description })
4423
+ ] })
4424
+ ]
4425
+ }
4426
+ );
4427
+ });
4428
+ var Tabs = /* @__PURE__ */ forwardRef(function Tabs2({ className, variant = "pill", size = "md", fitted = false, ...props }, ref) {
4429
+ return /* @__PURE__ */ jsx(
4430
+ Tabs$1.Root,
4431
+ {
4432
+ ref,
4433
+ "data-slot": "tabs",
4434
+ "data-variant": variant,
4435
+ "data-size": size,
4436
+ "data-fitted": fitted || void 0,
4437
+ className: cx("orynn-tabs", className),
4438
+ ...props
4439
+ }
4440
+ );
4441
+ });
4442
+ var TabsList = /* @__PURE__ */ forwardRef(function TabsList2({ className, ...props }, ref) {
4443
+ return /* @__PURE__ */ jsx(
4444
+ Tabs$1.List,
4445
+ {
4446
+ ref,
4447
+ "data-slot": "tabs-list",
4448
+ className: cx("orynn-tabs__list", className),
4449
+ ...props
4450
+ }
4451
+ );
4452
+ });
4453
+ var TabsTrigger = /* @__PURE__ */ forwardRef(function TabsTrigger2({ className, ...props }, ref) {
4454
+ return /* @__PURE__ */ jsx(
4455
+ Tabs$1.Trigger,
4456
+ {
4457
+ ref,
4458
+ "data-slot": "tabs-trigger",
4459
+ className: cx("orynn-tabs__trigger", className),
4460
+ ...props
4461
+ }
4462
+ );
4463
+ });
4464
+ var TabsContent = /* @__PURE__ */ forwardRef(function TabsContent2({ className, ...props }, ref) {
4465
+ return /* @__PURE__ */ jsx(
4466
+ Tabs$1.Content,
4467
+ {
4468
+ ref,
4469
+ "data-slot": "tabs-content",
4470
+ className: cx("orynn-tabs__content", className),
4471
+ ...props
4472
+ }
4473
+ );
4474
+ });
4475
+ var Dialog = (props) => /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
4476
+ var DialogTrigger = (props) => /* @__PURE__ */ jsx(Dialog$1.Trigger, { "data-slot": "dialog-trigger", ...props });
4477
+ var DialogClose = (props) => /* @__PURE__ */ jsx(Dialog$1.Close, { "data-slot": "dialog-close", ...props });
4478
+ var DialogPortal = Dialog$1.Portal;
4479
+ var DialogContent = /* @__PURE__ */ forwardRef(
4480
+ function DialogContent2({ className, children, showCloseButton = true, size = "lg", scrollable, container, ...props }, ref) {
4481
+ const themed = usePortalContainer();
4482
+ return /* @__PURE__ */ jsxs(Dialog$1.Portal, { container: container ?? themed ?? void 0, children: [
4483
+ /* @__PURE__ */ jsx(
4484
+ Dialog$1.Overlay,
4485
+ {
4486
+ "data-slot": "dialog-overlay",
4487
+ className: "orynn-overlay orynn-dialog__overlay"
4488
+ }
4489
+ ),
4490
+ /* @__PURE__ */ jsxs(
4491
+ Dialog$1.Content,
4492
+ {
4493
+ ref,
4494
+ "data-slot": "dialog-content",
4495
+ "data-size": size,
4496
+ "data-scrollable": scrollable || void 0,
4497
+ className: cx("orynn-dialog__content", className),
4498
+ ...props,
4499
+ children: [
4500
+ children,
4501
+ showCloseButton && /* @__PURE__ */ jsx(
4502
+ Dialog$1.Close,
4503
+ {
4504
+ "data-slot": "dialog-close",
4505
+ className: "orynn-dialog__close",
4506
+ "aria-label": "Close",
4507
+ children: /* @__PURE__ */ jsx(XIcon, {})
4508
+ }
4509
+ )
4510
+ ]
4511
+ }
4512
+ )
4513
+ ] });
4514
+ }
4515
+ );
4516
+ function DialogHeader({ className, ...props }) {
4517
+ return /* @__PURE__ */ jsx("div", { "data-slot": "dialog-header", className: cx("orynn-dialog__header", className), ...props });
2685
4518
  }
2686
- function useOrynnTheme() {
2687
- return useContext(OrynnContext);
4519
+ function DialogFooter({ className, ...props }) {
4520
+ return /* @__PURE__ */ jsx("div", { "data-slot": "dialog-footer", className: cx("orynn-dialog__footer", className), ...props });
4521
+ }
4522
+ var DialogTitle = /* @__PURE__ */ forwardRef(function DialogTitle2({ className, ...props }, ref) {
4523
+ return /* @__PURE__ */ jsx(
4524
+ Dialog$1.Title,
4525
+ {
4526
+ ref,
4527
+ "data-slot": "dialog-title",
4528
+ className: cx("orynn-dialog__title", className),
4529
+ ...props
4530
+ }
4531
+ );
4532
+ });
4533
+ var DialogDescription = /* @__PURE__ */ forwardRef(function DialogDescription2({ className, ...props }, ref) {
4534
+ return /* @__PURE__ */ jsx(
4535
+ Dialog$1.Description,
4536
+ {
4537
+ ref,
4538
+ "data-slot": "dialog-description",
4539
+ className: cx("orynn-dialog__description", className),
4540
+ ...props
4541
+ }
4542
+ );
4543
+ });
4544
+ var Popover2 = (props) => /* @__PURE__ */ jsx(Popover$1.Root, { "data-slot": "popover", ...props });
4545
+ var PopoverTrigger = (props) => /* @__PURE__ */ jsx(Popover$1.Trigger, { "data-slot": "popover-trigger", ...props });
4546
+ var PopoverAnchor = (props) => /* @__PURE__ */ jsx(Popover$1.Anchor, { "data-slot": "popover-anchor", ...props });
4547
+ var PopoverClose = Popover$1.Close;
4548
+ var PopoverArrow = (props) => /* @__PURE__ */ jsx(Popover$1.Arrow, { "data-slot": "popover-arrow", className: "orynn-popover__arrow", ...props });
4549
+ var PopoverContent = /* @__PURE__ */ forwardRef(
4550
+ function PopoverContent2({ className, align = "center", sideOffset = 4, arrow = false, container, children, ...props }, ref) {
4551
+ const themed = usePortalContainer();
4552
+ return /* @__PURE__ */ jsx(Popover$1.Portal, { container: container ?? themed ?? void 0, children: /* @__PURE__ */ jsxs(
4553
+ Popover$1.Content,
4554
+ {
4555
+ ref,
4556
+ "data-slot": "popover-content",
4557
+ align,
4558
+ sideOffset,
4559
+ className: cx("orynn-content orynn-popover__content", className),
4560
+ ...props,
4561
+ children: [
4562
+ children,
4563
+ arrow && /* @__PURE__ */ jsx(PopoverArrow, {})
4564
+ ]
4565
+ }
4566
+ ) });
4567
+ }
4568
+ );
4569
+ function TooltipProvider({
4570
+ delayDuration = 0,
4571
+ ...props
4572
+ }) {
4573
+ return /* @__PURE__ */ jsx(
4574
+ Tooltip$1.Provider,
4575
+ {
4576
+ "data-slot": "tooltip-provider",
4577
+ delayDuration,
4578
+ ...props
4579
+ }
4580
+ );
4581
+ }
4582
+ function Tooltip({
4583
+ delayDuration = 0,
4584
+ skipDelayDuration,
4585
+ disableHoverableContent,
4586
+ disableProvider = false,
4587
+ ...props
4588
+ }) {
4589
+ const root = /* @__PURE__ */ jsx(
4590
+ Tooltip$1.Root,
4591
+ {
4592
+ "data-slot": "tooltip",
4593
+ delayDuration,
4594
+ disableHoverableContent,
4595
+ ...props
4596
+ }
4597
+ );
4598
+ if (disableProvider) return root;
4599
+ return /* @__PURE__ */ jsx(
4600
+ TooltipProvider,
4601
+ {
4602
+ delayDuration,
4603
+ skipDelayDuration,
4604
+ disableHoverableContent,
4605
+ children: root
4606
+ }
4607
+ );
4608
+ }
4609
+ var TooltipTrigger = (props) => /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
4610
+ var TooltipContent = /* @__PURE__ */ forwardRef(
4611
+ function TooltipContent2({ className, sideOffset = 0, arrow = true, arrowSize = 11, container, children, ...props }, ref) {
4612
+ const themed = usePortalContainer();
4613
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { container: container ?? themed ?? void 0, children: /* @__PURE__ */ jsxs(
4614
+ Tooltip$1.Content,
4615
+ {
4616
+ ref,
4617
+ "data-slot": "tooltip-content",
4618
+ sideOffset,
4619
+ className: cx("orynn-content orynn-tooltip__content", className),
4620
+ ...props,
4621
+ children: [
4622
+ children,
4623
+ arrow && /* @__PURE__ */ jsx(
4624
+ Tooltip$1.Arrow,
4625
+ {
4626
+ width: arrowSize,
4627
+ height: Math.round(arrowSize / 2.2),
4628
+ className: "orynn-tooltip__arrow"
4629
+ }
4630
+ )
4631
+ ]
4632
+ }
4633
+ ) });
4634
+ }
4635
+ );
4636
+ var DropdownMenu = (props) => /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
4637
+ var DropdownMenuTrigger = (props) => /* @__PURE__ */ jsx(DropdownMenu$1.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
4638
+ var DropdownMenuGroup = DropdownMenu$1.Group;
4639
+ var DropdownMenuPortal = DropdownMenu$1.Portal;
4640
+ var DropdownMenuSub = DropdownMenu$1.Sub;
4641
+ var DropdownMenuRadioGroup = DropdownMenu$1.RadioGroup;
4642
+ var DropdownMenuContent = /* @__PURE__ */ forwardRef(function DropdownMenuContent2({ className, sideOffset = 4, container, ...props }, ref) {
4643
+ const themed = usePortalContainer();
4644
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { container: container ?? themed ?? void 0, children: /* @__PURE__ */ jsx(
4645
+ DropdownMenu$1.Content,
4646
+ {
4647
+ ref,
4648
+ "data-slot": "dropdown-menu-content",
4649
+ sideOffset,
4650
+ className: cx("orynn-content orynn-menu__content", className),
4651
+ ...props
4652
+ }
4653
+ ) });
4654
+ });
4655
+ var DropdownMenuItem = /* @__PURE__ */ forwardRef(
4656
+ function DropdownMenuItem2({ className, inset, variant = "default", icon, shortcut, children, ...props }, ref) {
4657
+ return /* @__PURE__ */ jsxs(
4658
+ DropdownMenu$1.Item,
4659
+ {
4660
+ ref,
4661
+ "data-slot": "dropdown-menu-item",
4662
+ "data-inset": inset || void 0,
4663
+ "data-variant": variant,
4664
+ className: cx("orynn-menu__item", className),
4665
+ ...props,
4666
+ children: [
4667
+ icon,
4668
+ children,
4669
+ shortcut != null && /* @__PURE__ */ jsx("span", { className: "orynn-menu__shortcut", children: shortcut })
4670
+ ]
4671
+ }
4672
+ );
4673
+ }
4674
+ );
4675
+ var DropdownMenuCheckboxItem = /* @__PURE__ */ forwardRef(function DropdownMenuCheckboxItem2({ className, children, checked, ...props }, ref) {
4676
+ return /* @__PURE__ */ jsxs(
4677
+ DropdownMenu$1.CheckboxItem,
4678
+ {
4679
+ ref,
4680
+ "data-slot": "dropdown-menu-checkbox-item",
4681
+ className: cx("orynn-menu__item", "orynn-menu__item--check", className),
4682
+ checked,
4683
+ ...props,
4684
+ children: [
4685
+ /* @__PURE__ */ jsx("span", { className: "orynn-menu__indicator", children: /* @__PURE__ */ jsx(DropdownMenu$1.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) }) }),
4686
+ children
4687
+ ]
4688
+ }
4689
+ );
4690
+ });
4691
+ var DropdownMenuRadioItem = /* @__PURE__ */ forwardRef(function DropdownMenuRadioItem2({ className, children, ...props }, ref) {
4692
+ return /* @__PURE__ */ jsxs(
4693
+ DropdownMenu$1.RadioItem,
4694
+ {
4695
+ ref,
4696
+ "data-slot": "dropdown-menu-radio-item",
4697
+ className: cx("orynn-menu__item", "orynn-menu__item--check", className),
4698
+ ...props,
4699
+ children: [
4700
+ /* @__PURE__ */ jsx("span", { className: "orynn-menu__indicator", children: /* @__PURE__ */ jsx(DropdownMenu$1.ItemIndicator, { children: /* @__PURE__ */ jsx(CircleIcon, { className: "orynn-menu__dot" }) }) }),
4701
+ children
4702
+ ]
4703
+ }
4704
+ );
4705
+ });
4706
+ var DropdownMenuLabel = /* @__PURE__ */ forwardRef(
4707
+ function DropdownMenuLabel2({ className, inset, ...props }, ref) {
4708
+ return /* @__PURE__ */ jsx(
4709
+ DropdownMenu$1.Label,
4710
+ {
4711
+ ref,
4712
+ "data-slot": "dropdown-menu-label",
4713
+ "data-inset": inset || void 0,
4714
+ className: cx("orynn-menu__label", className),
4715
+ ...props
4716
+ }
4717
+ );
4718
+ }
4719
+ );
4720
+ var DropdownMenuSeparator = /* @__PURE__ */ forwardRef(function DropdownMenuSeparator2({ className, ...props }, ref) {
4721
+ return /* @__PURE__ */ jsx(
4722
+ DropdownMenu$1.Separator,
4723
+ {
4724
+ ref,
4725
+ "data-slot": "dropdown-menu-separator",
4726
+ className: cx("orynn-menu__separator", className),
4727
+ ...props
4728
+ }
4729
+ );
4730
+ });
4731
+ function DropdownMenuShortcut({ className, ...props }) {
4732
+ return /* @__PURE__ */ jsx(
4733
+ "span",
4734
+ {
4735
+ "data-slot": "dropdown-menu-shortcut",
4736
+ className: cx("orynn-menu__shortcut", className),
4737
+ ...props
4738
+ }
4739
+ );
2688
4740
  }
4741
+ var DropdownMenuSubTrigger = /* @__PURE__ */ forwardRef(function DropdownMenuSubTrigger2({ className, inset, children, ...props }, ref) {
4742
+ return /* @__PURE__ */ jsxs(
4743
+ DropdownMenu$1.SubTrigger,
4744
+ {
4745
+ ref,
4746
+ "data-slot": "dropdown-menu-sub-trigger",
4747
+ "data-inset": inset || void 0,
4748
+ className: cx("orynn-menu__item", "orynn-menu__sub-trigger", className),
4749
+ ...props,
4750
+ children: [
4751
+ children,
4752
+ /* @__PURE__ */ jsx(ChevronRightIcon, { className: "orynn-menu__sub-chevron" })
4753
+ ]
4754
+ }
4755
+ );
4756
+ });
4757
+ var DropdownMenuSubContent = /* @__PURE__ */ forwardRef(function DropdownMenuSubContent2({ className, ...props }, ref) {
4758
+ return /* @__PURE__ */ jsx(
4759
+ DropdownMenu$1.SubContent,
4760
+ {
4761
+ ref,
4762
+ "data-slot": "dropdown-menu-sub-content",
4763
+ className: cx("orynn-content", "orynn-menu__content", "orynn-menu__sub-content", className),
4764
+ ...props
4765
+ }
4766
+ );
4767
+ });
2689
4768
 
2690
4769
  // src/types/validation.ts
2691
4770
  var FORM_ERROR_KEY = "$form";
2692
4771
 
2693
- export { Checkbox, CheckboxGroup, DatePicker, Dropdown, FORM_ERROR_KEY, Field, FieldBox, Fieldset, Input, NumberField, OrynnProvider, Radio, RadioGroup, Dropdown as Select, Textarea, createRegistry, createRuleResolver, defaultRegistry, defaultResolver, normalizeConfig, registerField, registerRule, resolveThemeVars, useFieldsetState, useOrynnTheme };
4772
+ // src/core/validation/standard-schema.ts
4773
+ function pathToField(path) {
4774
+ if (!path || path.length === 0) return { field: FORM_ERROR_KEY, rest: "" };
4775
+ const seg = (p) => String(typeof p === "object" && p !== null && "key" in p ? p.key : p);
4776
+ const parts = path.map(seg);
4777
+ return { field: parts[0] ?? FORM_ERROR_KEY, rest: parts.slice(1).join(".") };
4778
+ }
4779
+ function standardSchemaResolver(schema) {
4780
+ return (values) => {
4781
+ const out = schema["~standard"].validate(values);
4782
+ const toResult = (r) => {
4783
+ const result = {};
4784
+ if (!("issues" in r) || !r.issues) return result;
4785
+ for (const issue of r.issues) {
4786
+ const { field, rest } = pathToField(issue.path);
4787
+ const bucket = result[field] ?? [];
4788
+ bucket.push({ rule: "schema", message: issue.message, ...rest ? { path: rest } : {} });
4789
+ result[field] = bucket;
4790
+ }
4791
+ return result;
4792
+ };
4793
+ return out instanceof Promise ? out.then(toResult) : toResult(out);
4794
+ };
4795
+ }
4796
+
4797
+ export { Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CheckboxGroup, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogPortal, DialogTitle, DialogTrigger, Dropdown, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FORM_ERROR_KEY, Field, FieldBox, Fieldset, FieldsetView, Form, Input, Label, NumberField, OrynnProvider, Popover2 as Popover, PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverTrigger, Radio, RadioGroup, Dropdown as Select, Separator, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeClasses, builtInRules, buttonClasses, createRegistry, createRuleResolver, defaultRegistry, defaultResolver, normalizeConfig, registerField, registerRule, resolveThemeVars, standardSchemaResolver, useFieldsetState, useOrynnTheme, usePortalContainer };
2694
4798
  //# sourceMappingURL=index.js.map
2695
4799
  //# sourceMappingURL=index.js.map