orynn 0.2.0 → 0.6.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
- import { createContext, forwardRef, useContext, useId, useRef, useEffect, useCallback, useMemo, useState, useImperativeHandle, useLayoutEffect, useReducer, memo } from 'react';
1
+ import { createContext, forwardRef, useContext, useId, useRef, useEffect, useCallback, useMemo, useState, useImperativeHandle, useLayoutEffect, useReducer, useSyncExternalStore, memo, isValidElement } 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, Accordion as Accordion$1, Slot, Slider as Slider$1, Progress as Progress$1, Toast, 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",
@@ -454,14 +621,17 @@ function Popover({
454
621
  left: pos.left,
455
622
  top: pos.top,
456
623
  width: matchWidth ? pos.width : void 0,
457
- minWidth: pos.width,
624
+ // Only pin the min-width to the anchor when we're matching it. With
625
+ // `matchWidth={false}` the panel sizes to its own content, so a wide
626
+ // trigger doesn't stretch it across the screen.
627
+ minWidth: matchWidth ? pos.width : void 0,
458
628
  maxHeight: pos.maxHeight,
459
629
  transform: pos.placement === "top" ? "translateY(-100%)" : void 0
460
630
  },
461
631
  children
462
632
  }
463
633
  ),
464
- document.body
634
+ container
465
635
  );
466
636
  }
467
637
 
@@ -486,27 +656,82 @@ var clampDate = (d, min, max) => {
486
656
  return d;
487
657
  };
488
658
  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]);
659
+ 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;
660
+ function formatDate(d, fmt = "yyyy-MM-dd", locale) {
661
+ if (typeof fmt !== "string") return new Intl.DateTimeFormat(locale, fmt).format(d);
662
+ const h12 = d.getHours() % 12 || 12;
663
+ const map = {
664
+ yyyy: String(d.getFullYear()),
665
+ yy: pad(d.getFullYear() % 100),
666
+ MMMM: new Intl.DateTimeFormat(locale, { month: "long" }).format(d),
667
+ MMM: new Intl.DateTimeFormat(locale, { month: "short" }).format(d),
668
+ MM: pad(d.getMonth() + 1),
669
+ M: String(d.getMonth() + 1),
670
+ dd: pad(d.getDate()),
671
+ d: String(d.getDate()),
672
+ EEEE: new Intl.DateTimeFormat(locale, { weekday: "long" }).format(d),
673
+ EEE: new Intl.DateTimeFormat(locale, { weekday: "short" }).format(d),
674
+ HH: pad(d.getHours()),
675
+ H: String(d.getHours()),
676
+ hh: pad(h12),
677
+ h: String(h12),
678
+ mm: pad(d.getMinutes()),
679
+ m: String(d.getMinutes()),
680
+ ss: pad(d.getSeconds()),
681
+ s: String(d.getSeconds()),
682
+ aa: d.getHours() < 12 ? "am" : "pm",
683
+ a: d.getHours() < 12 ? "am" : "pm",
684
+ A: d.getHours() < 12 ? "AM" : "PM"
685
+ };
686
+ return fmt.replace(FORMAT_TOKENS, (t) => map[t] ?? t);
687
+ }
688
+ function matchMonthName(input, locale) {
689
+ for (let m = 0; m < 12; m += 1) {
690
+ for (const style of ["long", "short"]) {
691
+ const name = new Intl.DateTimeFormat(locale, { month: style }).format(new Date(2021, m, 1));
692
+ const i = input.toLowerCase().indexOf(name.toLowerCase());
693
+ if (i !== -1) return { month: m, rest: input.slice(0, i) + input.slice(i + name.length) };
694
+ }
695
+ }
696
+ return null;
697
+ }
698
+ function parseDate(input, fmt = "yyyy-MM-dd", locale) {
699
+ const pattern = typeof fmt === "string" ? fmt : "yyyy-MM-dd";
700
+ let text = input;
701
+ let monthFromName;
702
+ if (/MMM/.test(pattern)) {
703
+ const hit = matchMonthName(text, locale);
704
+ if (hit) {
705
+ monthFromName = hit.month;
706
+ text = hit.rest;
707
+ }
708
+ }
709
+ const nums = text.match(/\d+/g);
710
+ if (!nums && monthFromName === void 0) return null;
711
+ 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
712
  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);
713
+ let mo = monthFromName;
714
+ let day;
715
+ let hh = 0;
716
+ let mm = 0;
717
+ let ss = 0;
718
+ let ni = 0;
719
+ for (const tok of order) {
720
+ if ((tok === "MM" || tok === "M") && monthFromName !== void 0) continue;
721
+ const n = Number((nums ?? [])[ni]);
722
+ ni += 1;
723
+ if (Number.isNaN(n)) continue;
724
+ if (tok === "yyyy" || tok === "yy") y = n < 100 ? 2e3 + n : n;
725
+ else if (tok === "MM" || tok === "M") mo = n - 1;
726
+ else if (tok === "dd" || tok === "d") day = n;
727
+ else if (tok === "HH" || tok === "H" || tok === "hh" || tok === "h") hh = n;
728
+ else if (tok === "mm" || tok === "m") mm = n;
729
+ else if (tok === "ss" || tok === "s") ss = n;
730
+ }
731
+ if (/pm/i.test(input) && hh < 12) hh += 12;
732
+ if (/am/i.test(input) && hh === 12) hh = 0;
733
+ if (y == null && day == null && mo == null) return null;
734
+ const d = new Date(y ?? (/* @__PURE__ */ new Date()).getFullYear(), mo ?? 0, day ?? 1, hh, mm, ss);
510
735
  return Number.isNaN(d.getTime()) ? null : d;
511
736
  }
512
737
  function toDate(v) {
@@ -515,11 +740,14 @@ function toDate(v) {
515
740
  const d = new Date(v);
516
741
  return Number.isNaN(d.getTime()) ? null : startOfDay(d);
517
742
  }
518
- function monthMatrix(viewYear, viewMonth, firstDayOfWeek) {
743
+ function monthMatrix(viewYear, viewMonth, firstDayOfWeek, fixedWeeks = true) {
519
744
  const first = new Date(viewYear, viewMonth, 1);
520
745
  const offset = (first.getDay() - firstDayOfWeek + 7) % 7;
521
746
  const start = addDays(first, -offset);
522
- return Array.from({ length: 42 }, (_, i) => addDays(start, i));
747
+ if (fixedWeeks) return Array.from({ length: 42 }, (_, i) => addDays(start, i));
748
+ const last = new Date(viewYear, viewMonth + 1, 0);
749
+ const used = Math.ceil((offset + last.getDate()) / 7);
750
+ return Array.from({ length: used * 7 }, (_, i) => addDays(start, i));
523
751
  }
524
752
  function weekdayLabels(firstDayOfWeek, locale) {
525
753
  const fmt = new Intl.DateTimeFormat(locale, { weekday: "short" });
@@ -528,6 +756,10 @@ function weekdayLabels(firstDayOfWeek, locale) {
528
756
  (_, i) => fmt.format(new Date(2023, 0, 1 + (firstDayOfWeek + i) % 7))
529
757
  );
530
758
  }
759
+ function decadeGrid(year) {
760
+ const start = Math.floor(year / 10) * 10;
761
+ return { start, years: Array.from({ length: 12 }, (_, i) => start - 1 + i) };
762
+ }
531
763
  function isoWeek(d) {
532
764
  const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
533
765
  const dayNum = (date.getUTCDay() + 6) % 7;
@@ -536,9 +768,40 @@ function isoWeek(d) {
536
768
  const diff = date.getTime() - firstThursday.getTime();
537
769
  return 1 + Math.round(diff / (7 * 24 * 3600 * 1e3));
538
770
  }
771
+
772
+ // src/controls/DatePicker/calendar-range.ts
773
+ function normalizeRange(r) {
774
+ if (r.start && r.end && startOfDay(r.start) > startOfDay(r.end)) {
775
+ return { start: r.end, end: r.start };
776
+ }
777
+ return r;
778
+ }
779
+ function isRangeComplete(r) {
780
+ return !!r && !!r.start && !!r.end;
781
+ }
782
+ function isWithinRange(d, r) {
783
+ if (!isRangeComplete(r)) return false;
784
+ const t = startOfDay(d).getTime();
785
+ return t > startOfDay(r.start).getTime() && t < startOfDay(r.end).getTime();
786
+ }
787
+ var isRangeStart = (d, r) => !!r && isSameDay(d, r.start);
788
+ var isRangeEnd = (d, r) => !!r && isSameDay(d, r.end);
789
+ function previewRange(anchor, hover) {
790
+ if (!anchor || !hover) return null;
791
+ return normalizeRange({ start: anchor, end: hover });
792
+ }
793
+ function nextRange(current, d) {
794
+ if (!current.start || current.start && current.end) return { start: startOfDay(d), end: null };
795
+ return normalizeRange({ start: current.start, end: startOfDay(d) });
796
+ }
539
797
  function Calendar({
540
798
  value,
541
799
  onSelect,
800
+ mode = "single",
801
+ selectedDates,
802
+ range: range2,
803
+ previewRange: previewRange2,
804
+ onHoverDate,
542
805
  minDate,
543
806
  maxDate,
544
807
  isDisabled,
@@ -548,16 +811,36 @@ function Calendar({
548
811
  showToday,
549
812
  showClear,
550
813
  onClear,
551
- autoFocus
814
+ autoFocus,
815
+ view: viewProp,
816
+ defaultView,
817
+ onViewChange,
818
+ precision = "day",
819
+ fixedWeeks = true,
820
+ defaultMonth,
821
+ numberOfMonths = 1,
822
+ className,
823
+ style,
824
+ fillWidth
552
825
  }) {
826
+ const panels = Math.max(1, Math.floor(numberOfMonths));
553
827
  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() });
828
+ const anchor = value ?? (mode === "range" ? range2?.start ?? range2?.end ?? null : null) ?? (mode === "multiple" ? selectedDates?.[0] ?? null : null) ?? defaultMonth ?? null;
829
+ const initial = anchor ?? clampDate(today, minDate, maxDate);
830
+ const [nav, setNav] = useState({ y: initial.getFullYear(), m: initial.getMonth() });
556
831
  const [focused, setFocused] = useState(initial);
832
+ const [innerView, setInnerView] = useState(
833
+ defaultView ?? (precision === "day" ? "day" : precision)
834
+ );
835
+ const view = viewProp ?? innerView;
836
+ const setView = (v) => {
837
+ if (viewProp === void 0) setInnerView(v);
838
+ onViewChange?.(v);
839
+ };
557
840
  const gridRef = useRef(null);
558
841
  useEffect(() => {
559
842
  if (value) {
560
- setView({ y: value.getFullYear(), m: value.getMonth() });
843
+ setNav({ y: value.getFullYear(), m: value.getMonth() });
561
844
  setFocused(value);
562
845
  }
563
846
  }, [value]);
@@ -566,94 +849,118 @@ function Calendar({
566
849
  gridRef.current?.querySelector('[data-focused="true"]')?.focus();
567
850
  }
568
851
  }, [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
852
  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() });
853
+ const monthOutOfRange = (y, m) => {
854
+ const first = new Date(y, m, 1);
855
+ const last = new Date(y, m + 1, 0);
856
+ return maxDate != null && first > startOfDay(maxDate) || minDate != null && last < startOfDay(minDate);
857
+ };
858
+ const yearOutOfRange = (y) => maxDate != null && new Date(y, 0, 1) > startOfDay(maxDate) || minDate != null && new Date(y, 11, 31) < startOfDay(minDate);
859
+ const focusGrid = () => {
579
860
  requestAnimationFrame(() => {
580
861
  gridRef.current?.querySelector('[data-focused="true"]')?.focus();
581
862
  });
582
863
  };
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);
864
+ const navIndex = nav.y * 12 + nav.m;
865
+ const goto = (d) => {
866
+ const c = clampDate(d, minDate, maxDate);
867
+ setFocused(c);
868
+ const ci = c.getFullYear() * 12 + c.getMonth();
869
+ if (ci < navIndex || ci > navIndex + panels - 1) {
870
+ setNav({ y: c.getFullYear(), m: c.getMonth() });
871
+ }
872
+ focusGrid();
602
873
  };
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(
874
+ const canDrillUp = view !== "year";
875
+ const dayTitle = () => {
876
+ const first = new Date(nav.y, nav.m, 1);
877
+ if (panels <= 1) {
878
+ return new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(first);
879
+ }
880
+ const last = new Date(nav.y, nav.m + panels - 1, 1);
881
+ const f = new Intl.DateTimeFormat(locale, { month: "short" }).format(first);
882
+ const l = new Intl.DateTimeFormat(locale, { month: "short", year: "numeric" }).format(last);
883
+ return first.getFullYear() === last.getFullYear() ? `${f} \u2013 ${l}` : `${f} ${first.getFullYear()} \u2013 ${l}`;
884
+ };
885
+ const titleText = view === "day" ? dayTitle() : view === "month" ? String(nav.y) : (() => {
886
+ const g = decadeGrid(nav.y);
887
+ return `${g.start} \u2013 ${g.start + 9}`;
888
+ })();
889
+ const step = (dir) => {
890
+ if (view === "day") setNav((v) => ({ ...addMonthNav(v, dir) }));
891
+ else if (view === "month") setNav((v) => ({ ...v, y: v.y + dir }));
892
+ else setNav((v) => ({ ...v, y: v.y + dir * 10 }));
893
+ };
894
+ const header = /* @__PURE__ */ jsxs("div", { className: "orynn-calendar__header", children: [
895
+ /* @__PURE__ */ jsx(
896
+ "button",
897
+ {
898
+ type: "button",
899
+ className: "orynn-calendar__nav",
900
+ "aria-label": view === "day" ? "Previous month" : view === "month" ? "Previous year" : "Previous decade",
901
+ onClick: () => step(-1),
902
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
903
+ }
904
+ ),
905
+ /* @__PURE__ */ jsx(
906
+ "button",
907
+ {
908
+ type: "button",
909
+ className: "orynn-calendar__title",
910
+ "aria-live": "polite",
911
+ disabled: !canDrillUp,
912
+ onClick: () => {
913
+ if (!canDrillUp) return;
914
+ setView(view === "day" ? "month" : "year");
915
+ },
916
+ children: titleText
917
+ }
918
+ ),
919
+ /* @__PURE__ */ jsx(
920
+ "button",
921
+ {
922
+ type: "button",
923
+ className: "orynn-calendar__nav",
924
+ "aria-label": view === "day" ? "Next month" : view === "month" ? "Next year" : "Next decade",
925
+ onClick: () => step(1),
926
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
927
+ }
928
+ )
929
+ ] });
930
+ const dayGrid = (monthOffset = 0) => {
931
+ const panelMonth = new Date(nav.y, nav.m + monthOffset, 1);
932
+ const py = panelMonth.getFullYear();
933
+ const pm = panelMonth.getMonth();
934
+ const cells = monthMatrix(py, pm, firstDayOfWeek, fixedWeeks);
935
+ const weeks = cells.length / 7;
936
+ const weekdays = weekdayLabels(firstDayOfWeek, locale);
937
+ const monthName = new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(
938
+ panelMonth
939
+ );
940
+ const onKeyDown = (e) => {
941
+ const k = e.key;
942
+ let next = null;
943
+ if (k === "ArrowLeft") next = addDays(focused, -1);
944
+ else if (k === "ArrowRight") next = addDays(focused, 1);
945
+ else if (k === "ArrowUp") next = addDays(focused, -7);
946
+ else if (k === "ArrowDown") next = addDays(focused, 7);
947
+ else if (k === "Home")
948
+ next = addDays(focused, -((focused.getDay() - firstDayOfWeek + 7) % 7));
949
+ else if (k === "End")
950
+ next = addDays(focused, 6 - (focused.getDay() - firstDayOfWeek + 7) % 7);
951
+ else if (k === "PageUp") next = addMonths(focused, e.shiftKey ? -12 : -1);
952
+ else if (k === "PageDown") next = addMonths(focused, e.shiftKey ? 12 : 1);
953
+ else if (k === "Enter" || k === " ") {
954
+ e.preventDefault();
955
+ if (!outOfRange(focused)) onSelect(focused);
956
+ return;
957
+ } else return;
958
+ e.preventDefault();
959
+ goto(next);
960
+ };
961
+ return /* @__PURE__ */ jsxs(
654
962
  "table",
655
963
  {
656
- ref: gridRef,
657
964
  className: "orynn-calendar__grid",
658
965
  role: "grid",
659
966
  "aria-label": monthName,
@@ -663,16 +970,29 @@ function Calendar({
663
970
  showWeekNumbers && /* @__PURE__ */ jsx("th", { "aria-hidden": "true" }),
664
971
  weekdays.map((w) => /* @__PURE__ */ jsx("th", { scope: "col", children: w }, w))
665
972
  ] }) }),
666
- /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: 6 }, (_, week) => {
973
+ /* @__PURE__ */ jsx("tbody", { onMouseLeave: onHoverDate ? () => onHoverDate(null) : void 0, children: Array.from({ length: weeks }, (_, week) => {
667
974
  const row = cells.slice(week * 7, week * 7 + 7);
668
975
  const firstOfRow = row[0];
669
976
  return /* @__PURE__ */ jsxs("tr", { children: [
670
977
  showWeekNumbers && /* @__PURE__ */ jsx("td", { className: "orynn-calendar__weeknum", children: isoWeek(firstOfRow) }),
671
978
  row.map((d) => {
672
- const inMonth = d.getMonth() === view.m;
979
+ const inMonth = d.getMonth() === pm;
673
980
  const disabled = outOfRange(d);
674
- const selected = isSameDay(d, value);
675
981
  const isFocused = isSameDay(d, focused);
982
+ let selected;
983
+ let rangeStart = false;
984
+ let rangeEnd = false;
985
+ let inRange = false;
986
+ if (mode === "multiple") {
987
+ selected = (selectedDates ?? []).some((x) => isSameDay(x, d));
988
+ } else if (mode === "range") {
989
+ rangeStart = isRangeStart(d, range2) || !!previewRange2 && isRangeStart(d, previewRange2);
990
+ rangeEnd = isRangeEnd(d, range2) || !!previewRange2 && isRangeEnd(d, previewRange2);
991
+ inRange = isWithinRange(d, range2) || !!previewRange2 && isWithinRange(d, previewRange2);
992
+ selected = rangeStart || rangeEnd;
993
+ } else {
994
+ selected = isSameDay(d, value);
995
+ }
676
996
  return /* @__PURE__ */ jsx("td", { role: "gridcell", "aria-selected": selected, children: /* @__PURE__ */ jsx(
677
997
  "button",
678
998
  {
@@ -683,10 +1003,15 @@ function Calendar({
683
1003
  "data-outside": !inMonth || void 0,
684
1004
  "data-today": isSameDay(d, today) || void 0,
685
1005
  "data-selected": selected || void 0,
1006
+ "data-range-start": rangeStart || void 0,
1007
+ "data-range-end": rangeEnd || void 0,
1008
+ "data-in-range": inRange || void 0,
686
1009
  disabled,
687
1010
  "aria-label": new Intl.DateTimeFormat(locale, { dateStyle: "full" }).format(
688
1011
  d
689
1012
  ),
1013
+ onMouseEnter: onHoverDate ? () => onHoverDate(d) : void 0,
1014
+ onFocus: onHoverDate ? () => onHoverDate(d) : void 0,
690
1015
  onClick: () => {
691
1016
  setFocused(d);
692
1017
  onSelect(d);
@@ -698,25 +1023,189 @@ function Calendar({
698
1023
  ] }, firstOfRow.toISOString());
699
1024
  }) })
700
1025
  ]
1026
+ },
1027
+ `${py}-${pm}`
1028
+ );
1029
+ };
1030
+ const monthGrid = () => {
1031
+ const names = Array.from(
1032
+ { length: 12 },
1033
+ (_, i) => new Intl.DateTimeFormat(locale, { month: "short" }).format(new Date(2021, i, 1))
1034
+ );
1035
+ const onKeyDown = (e) => {
1036
+ let m = nav.m;
1037
+ if (e.key === "ArrowLeft") m -= 1;
1038
+ else if (e.key === "ArrowRight") m += 1;
1039
+ else if (e.key === "ArrowUp") m -= 3;
1040
+ else if (e.key === "ArrowDown") m += 3;
1041
+ else if (e.key === "Enter" || e.key === " ") {
1042
+ e.preventDefault();
1043
+ pickMonth(nav.m);
1044
+ return;
1045
+ } else return;
1046
+ e.preventDefault();
1047
+ const d = addMonths(new Date(nav.y, nav.m, 1), m - nav.m);
1048
+ setNav({ y: d.getFullYear(), m: d.getMonth() });
1049
+ focusGrid();
1050
+ };
1051
+ return /* @__PURE__ */ jsx(
1052
+ "div",
1053
+ {
1054
+ className: "orynn-calendar__pickgrid",
1055
+ role: "grid",
1056
+ "aria-label": `Months of ${nav.y}`,
1057
+ onKeyDown,
1058
+ 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) => {
1059
+ const i = rowStart + j;
1060
+ const isFocused = i === nav.m;
1061
+ const selected = value != null && value.getFullYear() === nav.y && value.getMonth() === i;
1062
+ return /* @__PURE__ */ jsx(
1063
+ "button",
1064
+ {
1065
+ type: "button",
1066
+ className: "orynn-calendar__cell",
1067
+ tabIndex: isFocused ? 0 : -1,
1068
+ "data-focused": isFocused || void 0,
1069
+ "data-selected": selected || void 0,
1070
+ disabled: monthOutOfRange(nav.y, i),
1071
+ onClick: () => pickMonth(i),
1072
+ children: name
1073
+ },
1074
+ name
1075
+ );
1076
+ }) }, rowStart))
701
1077
  }
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
- ] });
1078
+ );
1079
+ };
1080
+ const pickMonth = (m) => {
1081
+ if (monthOutOfRange(nav.y, m)) return;
1082
+ setNav((v) => ({ ...v, m }));
1083
+ if (precision === "month") {
1084
+ onSelect(new Date(nav.y, m, 1));
1085
+ } else {
1086
+ setView("day");
1087
+ setFocused(clampDate(new Date(nav.y, m, 1), minDate, maxDate));
1088
+ focusGrid();
1089
+ }
1090
+ };
1091
+ const yearGrid = () => {
1092
+ const g = decadeGrid(nav.y);
1093
+ const onKeyDown = (e) => {
1094
+ let y = nav.y;
1095
+ if (e.key === "ArrowLeft") y -= 1;
1096
+ else if (e.key === "ArrowRight") y += 1;
1097
+ else if (e.key === "ArrowUp") y -= 3;
1098
+ else if (e.key === "ArrowDown") y += 3;
1099
+ else if (e.key === "Enter" || e.key === " ") {
1100
+ e.preventDefault();
1101
+ pickYear(nav.y);
1102
+ return;
1103
+ } else return;
1104
+ e.preventDefault();
1105
+ setNav((v) => ({ ...v, y }));
1106
+ focusGrid();
1107
+ };
1108
+ return /* @__PURE__ */ jsx(
1109
+ "div",
1110
+ {
1111
+ className: "orynn-calendar__pickgrid",
1112
+ role: "grid",
1113
+ "aria-label": `Years ${g.start}\u2013${g.start + 9}`,
1114
+ onKeyDown,
1115
+ 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) => {
1116
+ const isFocused = y === nav.y;
1117
+ const selected = value != null && value.getFullYear() === y;
1118
+ return /* @__PURE__ */ jsx(
1119
+ "button",
1120
+ {
1121
+ type: "button",
1122
+ className: "orynn-calendar__cell",
1123
+ tabIndex: isFocused ? 0 : -1,
1124
+ "data-focused": isFocused || void 0,
1125
+ "data-outside": y < g.start || y > g.start + 9 || void 0,
1126
+ "data-selected": selected || void 0,
1127
+ disabled: yearOutOfRange(y),
1128
+ onClick: () => pickYear(y),
1129
+ children: y
1130
+ },
1131
+ y
1132
+ );
1133
+ }) }, rowStart))
1134
+ }
1135
+ );
1136
+ };
1137
+ const pickYear = (y) => {
1138
+ if (yearOutOfRange(y)) return;
1139
+ setNav((v) => ({ ...v, y }));
1140
+ if (precision === "year") {
1141
+ onSelect(new Date(y, 0, 1));
1142
+ } else {
1143
+ setView("month");
1144
+ focusGrid();
1145
+ }
1146
+ };
1147
+ const dayView = panels <= 1 ? dayGrid() : /* @__PURE__ */ jsx("div", { className: "orynn-calendar__panels", children: Array.from({ length: panels }, (_, i) => dayGrid(i)) });
1148
+ return /* @__PURE__ */ jsxs(
1149
+ "div",
1150
+ {
1151
+ className: cx("orynn-calendar", className),
1152
+ style,
1153
+ ref: gridRef,
1154
+ "data-view": view,
1155
+ "data-months": panels > 1 ? panels : void 0,
1156
+ "data-fill": fillWidth || void 0,
1157
+ children: [
1158
+ header,
1159
+ " ",
1160
+ view === "day" ? dayView : view === "month" ? monthGrid() : yearGrid(),
1161
+ (showToday || showClear) && /* @__PURE__ */ jsxs("div", { className: "orynn-calendar__footer", children: [
1162
+ showToday ? /* @__PURE__ */ jsx(
1163
+ "button",
1164
+ {
1165
+ type: "button",
1166
+ className: "orynn-calendar__action",
1167
+ disabled: outOfRange(today),
1168
+ onClick: () => onSelect(today),
1169
+ children: "Today"
1170
+ }
1171
+ ) : /* @__PURE__ */ jsx("span", {}),
1172
+ showClear && /* @__PURE__ */ jsx("button", { type: "button", className: "orynn-calendar__action", onClick: onClear, children: "Clear" })
1173
+ ] })
1174
+ ]
1175
+ }
1176
+ );
1177
+ }
1178
+ function addMonthNav(v, dir) {
1179
+ const total = v.y * 12 + v.m + dir;
1180
+ return { y: Math.floor(total / 12), m: (total % 12 + 12) % 12 };
1181
+ }
1182
+
1183
+ // src/controls/DatePicker/time-utils.ts
1184
+ var timeParts = (d) => ({
1185
+ h: d.getHours(),
1186
+ m: d.getMinutes(),
1187
+ s: d.getSeconds()
1188
+ });
1189
+ function withTime(date, time) {
1190
+ const r = new Date(date);
1191
+ r.setHours(time.h, time.m, time.s, 0);
1192
+ return r;
1193
+ }
1194
+ function minuteOptions(step) {
1195
+ const s = Math.max(1, Math.min(30, Math.floor(step)));
1196
+ const out = [];
1197
+ for (let m = 0; m < 60; m += s) out.push(m);
1198
+ return out;
717
1199
  }
1200
+ var pad2 = (n) => String(n).padStart(2, "0");
1201
+ var normDates = (v) => Array.isArray(v) ? v.map((x) => toDate(x)).filter(Boolean) : [];
1202
+ var normRange = (v) => {
1203
+ const r = v ?? {};
1204
+ return { start: toDate(r.start ?? null), end: toDate(r.end ?? null) };
1205
+ };
718
1206
  var DatePicker = /* @__PURE__ */ forwardRef(
719
- function DatePicker2(props, ref) {
1207
+ function DatePicker2(propsIn, ref) {
1208
+ const props = propsIn;
720
1209
  const {
721
1210
  label,
722
1211
  description,
@@ -734,53 +1223,132 @@ var DatePicker = /* @__PURE__ */ forwardRef(
734
1223
  id: idProp,
735
1224
  name,
736
1225
  className,
1226
+ mode = "single",
737
1227
  value: valueProp,
738
1228
  defaultValue,
739
1229
  onChange,
740
- format = "yyyy-MM-dd",
1230
+ format = "dd-MMM-yyyy",
741
1231
  minDate,
742
1232
  maxDate,
743
1233
  disabledDates,
1234
+ isDateUnavailable,
744
1235
  firstDayOfWeek = 0,
745
1236
  locale,
746
1237
  showWeekNumbers,
747
1238
  showToday = true,
748
1239
  showClear,
1240
+ defaultView,
1241
+ precision = "day",
1242
+ fixedWeeks = true,
1243
+ numberOfMonths,
1244
+ calendarWidth,
1245
+ showTime = false,
1246
+ timeStep = 5,
1247
+ defaultMonth,
1248
+ presets,
749
1249
  inline = false,
750
1250
  allowInput = true,
751
- closeOnSelect = true,
1251
+ closeOnSelect: closeOnSelectProp,
1252
+ open: openProp,
1253
+ defaultOpen = false,
1254
+ onOpenChange,
752
1255
  placeholder
753
1256
  } = props;
754
1257
  const reactId = useId();
755
1258
  const id = idProp ?? reactId;
756
1259
  const panelId = `${id}-cal`;
1260
+ const withTimePicker = showTime && mode === "single";
1261
+ const closeOnSelect = closeOnSelectProp ?? !withTimePicker;
1262
+ const fmt = withTimePicker && format === "dd-MMM-yyyy" ? "dd-MMM-yyyy HH:mm" : format;
757
1263
  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);
1264
+ const [innerRaw, setInnerRaw] = useState(() => defaultValue ?? null);
1265
+ const raw = isControlled ? valueProp : innerRaw;
1266
+ const toDateKeepTime = (v) => {
1267
+ if (v == null) return null;
1268
+ const d = new Date(v);
1269
+ return Number.isNaN(d.getTime()) ? null : d;
1270
+ };
1271
+ const curDate = mode === "single" ? withTimePicker ? toDateKeepTime(raw) : toDate(raw) : null;
1272
+ const curRange = mode === "range" ? normRange(raw) : { start: null, end: null };
1273
+ const curDates = mode === "multiple" ? normDates(raw) : [];
1274
+ const commit = (next) => {
1275
+ if (!isControlled) {
1276
+ setInnerRaw(next);
1277
+ }
1278
+ onChange?.(next);
1279
+ };
1280
+ const openIsControlled = openProp !== void 0;
1281
+ const [innerOpen, setInnerOpen] = useState(defaultOpen);
1282
+ const open = openIsControlled ? openProp : innerOpen;
1283
+ const setOpen = (next) => {
1284
+ if (!openIsControlled) setInnerOpen(next);
1285
+ onOpenChange?.(next);
1286
+ };
761
1287
  const [gridFocus, setGridFocus] = useState(false);
762
- const [text, setText] = useState(() => value ? formatDate(value, format, locale) : "");
1288
+ const [hoverDate, setHoverDate] = useState(null);
1289
+ const displayText = () => {
1290
+ if (mode === "single") return curDate ? formatDate(curDate, fmt, locale) : "";
1291
+ if (mode === "multiple") {
1292
+ if (curDates.length === 0) return "";
1293
+ if (curDates.length === 1) return formatDate(curDates[0], fmt, locale);
1294
+ return `${curDates.length} selected`;
1295
+ }
1296
+ if (!curRange.start && !curRange.end) return "";
1297
+ const s = curRange.start ? formatDate(curRange.start, fmt, locale) : "\u2026";
1298
+ const e = curRange.end ? formatDate(curRange.end, fmt, locale) : "\u2026";
1299
+ return `${s} \u2013 ${e}`;
1300
+ };
1301
+ const [text, setText] = useState(displayText);
763
1302
  const boxRef = useRef(null);
764
1303
  const inputRef = useRef(null);
1304
+ const skipFocusOpen = useRef(false);
1305
+ const valueKey = [
1306
+ mode,
1307
+ typeof fmt === "string" ? fmt : JSON.stringify(fmt),
1308
+ locale ?? "",
1309
+ curDate?.getTime() ?? "",
1310
+ curRange.start?.getTime() ?? "",
1311
+ curRange.end?.getTime() ?? "",
1312
+ curDates.map((d) => d.getTime()).join(",")
1313
+ ].join("|");
765
1314
  useEffect(() => {
766
- setText(value ? formatDate(value, format, locale) : "");
767
- }, [value, format, locale]);
1315
+ setText(displayText());
1316
+ }, [valueKey]);
768
1317
  const isDisabledDate = (d) => {
1318
+ if (isDateUnavailable?.(d)) return true;
769
1319
  if (Array.isArray(disabledDates))
770
1320
  return disabledDates.some((x) => isSameDay(startOfDay(x), d));
771
1321
  return Boolean(disabledDates?.(d));
772
1322
  };
773
- const commit = (d) => {
774
- if (!isControlled) setInner(d);
775
- onChange?.(d);
1323
+ const closeAndRefocus = () => {
1324
+ setOpen(false);
1325
+ setGridFocus(false);
1326
+ skipFocusOpen.current = true;
1327
+ inputRef.current?.focus();
776
1328
  };
777
1329
  const select = (d) => {
778
1330
  if (isDisabledDate(d)) return;
779
- commit(startOfDay(d));
780
- if (closeOnSelect) {
781
- setOpen(false);
782
- inputRef.current?.focus();
1331
+ const day = startOfDay(d);
1332
+ if (mode === "single") {
1333
+ const picked = withTimePicker ? withTime(day, curDate ? timeParts(curDate) : { h: 0, m: 0, s: 0 }) : day;
1334
+ commit(picked);
1335
+ if (closeOnSelect) closeAndRefocus();
1336
+ return;
783
1337
  }
1338
+ if (mode === "multiple") {
1339
+ const has = curDates.some((x) => isSameDay(x, day));
1340
+ commit(has ? curDates.filter((x) => !isSameDay(x, day)) : [...curDates, day]);
1341
+ return;
1342
+ }
1343
+ const next = nextRange(curRange, day);
1344
+ commit(next);
1345
+ if (isRangeComplete(next) && closeOnSelect) closeAndRefocus();
1346
+ };
1347
+ const clearValue = () => {
1348
+ if (mode === "single") commit(null);
1349
+ else if (mode === "multiple") commit([]);
1350
+ else commit({ start: null, end: null });
1351
+ onClear?.();
784
1352
  };
785
1353
  const onKeyDown = (e) => {
786
1354
  if (disabled || readOnly) return;
@@ -791,31 +1359,103 @@ var DatePicker = /* @__PURE__ */ forwardRef(
791
1359
  } else if (e.key === "Escape" && open) {
792
1360
  e.preventDefault();
793
1361
  setOpen(false);
794
- } else if (e.key === "Enter") {
795
- const parsed = parseDate(text, format);
1362
+ } else if (e.key === "Enter" && mode === "single") {
1363
+ const parsed = parseDate(text, fmt, locale);
796
1364
  if (parsed && !isDisabledDate(startOfDay(parsed))) select(parsed);
797
1365
  }
798
1366
  };
799
- const cal = /* @__PURE__ */ jsx(
1367
+ const preview = mode === "range" && curRange.start && !curRange.end ? previewRange(curRange.start, hoverDate) : null;
1368
+ const applyPreset = (p) => {
1369
+ const resolved = p.getValue ? p.getValue() : p.value;
1370
+ if (resolved == null) return;
1371
+ commit(resolved);
1372
+ if (closeOnSelect && !inline) closeAndRefocus();
1373
+ };
1374
+ const setTimeField = (part, n) => {
1375
+ const base3 = curDate ?? startOfDay(/* @__PURE__ */ new Date());
1376
+ const t = timeParts(base3);
1377
+ commit(withTime(base3, { ...t, [part]: n, s: 0 }));
1378
+ };
1379
+ const timeColumn = withTimePicker ? /* @__PURE__ */ jsxs("div", { className: "orynn-datepicker__time", children: [
1380
+ /* @__PURE__ */ jsx("span", { className: "orynn-datepicker__time-label", children: "Time" }),
1381
+ /* @__PURE__ */ jsxs("div", { className: "orynn-datepicker__time-fields", children: [
1382
+ /* @__PURE__ */ jsx(
1383
+ "select",
1384
+ {
1385
+ "aria-label": "Hour",
1386
+ className: "orynn-datepicker__time-select",
1387
+ value: curDate ? curDate.getHours() : 0,
1388
+ onChange: (e) => setTimeField("h", Number(e.target.value)),
1389
+ children: Array.from({ length: 24 }, (_, h) => /* @__PURE__ */ jsx("option", { value: h, children: pad2(h) }, h))
1390
+ }
1391
+ ),
1392
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: ":" }),
1393
+ /* @__PURE__ */ jsx(
1394
+ "select",
1395
+ {
1396
+ "aria-label": "Minute",
1397
+ className: "orynn-datepicker__time-select",
1398
+ value: curDate ? curDate.getMinutes() - curDate.getMinutes() % timeStep : 0,
1399
+ onChange: (e) => setTimeField("m", Number(e.target.value)),
1400
+ children: minuteOptions(timeStep).map((m) => /* @__PURE__ */ jsx("option", { value: m, children: pad2(m) }, m))
1401
+ }
1402
+ )
1403
+ ] })
1404
+ ] }) : null;
1405
+ const calendarStyle = calendarWidth != null ? {
1406
+ "--orynn-calendar-min-width": typeof calendarWidth === "number" ? `${calendarWidth}px` : calendarWidth
1407
+ } : void 0;
1408
+ const hasExtras = (numberOfMonths ?? 1) > 1 || (presets?.length ?? 0) > 0 || withTimePicker;
1409
+ const fillWidth = inline || !hasExtras;
1410
+ const calendar = /* @__PURE__ */ jsx(
800
1411
  Calendar,
801
1412
  {
802
- value,
1413
+ value: curDate,
803
1414
  onSelect: select,
1415
+ style: calendarStyle,
1416
+ fillWidth,
1417
+ mode,
1418
+ selectedDates: mode === "multiple" ? curDates : void 0,
1419
+ range: mode === "range" ? curRange : void 0,
1420
+ previewRange: preview,
1421
+ onHoverDate: mode === "range" ? setHoverDate : void 0,
804
1422
  minDate,
805
1423
  maxDate,
806
1424
  isDisabled: isDisabledDate,
807
1425
  firstDayOfWeek,
808
1426
  locale,
809
1427
  showWeekNumbers,
810
- showToday,
1428
+ showToday: showToday && mode === "single",
811
1429
  showClear,
1430
+ defaultView,
1431
+ precision,
1432
+ fixedWeeks,
1433
+ numberOfMonths,
1434
+ defaultMonth,
812
1435
  onClear: () => {
813
- commit(null);
1436
+ clearValue();
814
1437
  setOpen(false);
815
1438
  },
816
1439
  autoFocus: open && gridFocus || inline
817
1440
  }
818
1441
  );
1442
+ const body = timeColumn ? /* @__PURE__ */ jsxs("div", { className: "orynn-datepicker__withtime", children: [
1443
+ calendar,
1444
+ timeColumn
1445
+ ] }) : calendar;
1446
+ const cal = presets && presets.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "orynn-datepicker__panel", children: [
1447
+ /* @__PURE__ */ jsx("div", { className: "orynn-datepicker__presets", children: presets.map((p) => /* @__PURE__ */ jsx(
1448
+ "button",
1449
+ {
1450
+ type: "button",
1451
+ className: "orynn-datepicker__preset",
1452
+ onClick: () => applyPreset(p),
1453
+ children: p.label
1454
+ },
1455
+ p.label
1456
+ )) }),
1457
+ body
1458
+ ] }) : body;
819
1459
  if (inline) {
820
1460
  return /* @__PURE__ */ jsx(
821
1461
  Field,
@@ -832,7 +1472,8 @@ var DatePicker = /* @__PURE__ */ forwardRef(
832
1472
  }
833
1473
  );
834
1474
  }
835
- const hasValue = value != null;
1475
+ const hasValue = mode === "single" ? curDate != null : mode === "multiple" ? curDates.length > 0 : Boolean(curRange.start || curRange.end);
1476
+ const typingAllowed = mode === "single" && allowInput && !readOnly;
836
1477
  return /* @__PURE__ */ jsx(
837
1478
  Field,
838
1479
  {
@@ -867,10 +1508,7 @@ var DatePicker = /* @__PURE__ */ forwardRef(
867
1508
  className: "orynn-box__iconbtn",
868
1509
  "aria-label": "Clear",
869
1510
  tabIndex: -1,
870
- onClick: () => {
871
- commit(null);
872
- onClear?.();
873
- },
1511
+ onClick: clearValue,
874
1512
  children: /* @__PURE__ */ jsx(XIcon, {})
875
1513
  }
876
1514
  ),
@@ -886,7 +1524,7 @@ var DatePicker = /* @__PURE__ */ forwardRef(
886
1524
  onClick: () => {
887
1525
  if (disabled || readOnly) return;
888
1526
  setGridFocus(true);
889
- setOpen((o) => !o);
1527
+ setOpen(!open);
890
1528
  },
891
1529
  children: /* @__PURE__ */ jsx(CalendarIcon, {})
892
1530
  }
@@ -905,7 +1543,7 @@ var DatePicker = /* @__PURE__ */ forwardRef(
905
1543
  name,
906
1544
  className: "orynn-box__field",
907
1545
  type: "text",
908
- inputMode: "numeric",
1546
+ inputMode: typingAllowed ? "numeric" : void 0,
909
1547
  autoComplete: "off",
910
1548
  role: "combobox",
911
1549
  "aria-expanded": open,
@@ -915,19 +1553,26 @@ var DatePicker = /* @__PURE__ */ forwardRef(
915
1553
  "aria-describedby": describedById,
916
1554
  value: text,
917
1555
  disabled,
918
- readOnly: !allowInput || readOnly,
1556
+ readOnly: !typingAllowed,
919
1557
  required,
920
- placeholder: floatingLabel ? void 0 : placeholder ?? format.toLowerCase(),
1558
+ placeholder: floatingLabel ? void 0 : placeholder ?? (typeof fmt === "string" ? fmt.toLowerCase() : void 0),
921
1559
  onChange: (e) => setText(e.target.value),
922
1560
  onKeyDown,
923
- onFocus: () => setOpen(true),
1561
+ onFocus: () => {
1562
+ if (skipFocusOpen.current) {
1563
+ skipFocusOpen.current = false;
1564
+ return;
1565
+ }
1566
+ setOpen(true);
1567
+ },
924
1568
  onBlur: () => {
925
- const parsed = parseDate(text, format);
1569
+ if (mode !== "single") return;
1570
+ const parsed = parseDate(text, fmt, locale);
926
1571
  if (parsed && !isDisabledDate(startOfDay(parsed))) {
927
1572
  commit(startOfDay(parsed));
928
- setText(formatDate(startOfDay(parsed), format, locale));
1573
+ setText(formatDate(startOfDay(parsed), fmt, locale));
929
1574
  } else {
930
- setText(value ? formatDate(value, format, locale) : "");
1575
+ setText(curDate ? formatDate(curDate, fmt, locale) : "");
931
1576
  }
932
1577
  }
933
1578
  }
@@ -943,7 +1588,7 @@ var DatePicker = /* @__PURE__ */ forwardRef(
943
1588
  setOpen(false);
944
1589
  setGridFocus(false);
945
1590
  },
946
- matchWidth: false,
1591
+ matchWidth: fillWidth,
947
1592
  id: panelId,
948
1593
  role: "dialog",
949
1594
  children: cal
@@ -954,7 +1599,13 @@ var DatePicker = /* @__PURE__ */ forwardRef(
954
1599
  );
955
1600
  }
956
1601
  );
1602
+ var CREATE_VALUE = "\0orynn-create";
957
1603
  var toArray = (v) => v == null ? [] : Array.isArray(v) ? v : [v];
1604
+ var isGrouped = (o) => {
1605
+ const first = o[0];
1606
+ return first != null && "items" in first && Array.isArray(first.items);
1607
+ };
1608
+ var flattenOptions = (o) => isGrouped(o) ? o.flatMap((g) => g.items.map((it) => ({ ...it, group: it.group ?? g.group }))) : o;
958
1609
  function Highlight({ text, query }) {
959
1610
  if (!query) return /* @__PURE__ */ jsx(Fragment, { children: text });
960
1611
  const i = text.toLowerCase().indexOf(query.toLowerCase());
@@ -996,52 +1647,103 @@ var Dropdown = /* @__PURE__ */ forwardRef(
996
1647
  highlightMatch,
997
1648
  renderOption,
998
1649
  renderValue,
1650
+ renderGroupLabel,
999
1651
  placeholder,
1000
1652
  emptyMessage = "No results",
1653
+ loadingMessage = "Loading\u2026",
1001
1654
  closeOnSelect = !multiple,
1002
1655
  maxSelectedLabels,
1656
+ maxValues,
1657
+ hidePickedOptions = false,
1658
+ showSelectAll = false,
1003
1659
  native = false,
1004
- startContent
1660
+ startContent,
1661
+ creatable = false,
1662
+ onCreate,
1663
+ isValidNewOption,
1664
+ formatCreateLabel,
1665
+ createPosition = "last",
1666
+ onSearch,
1667
+ searchDebounce = 0,
1668
+ open: openProp,
1669
+ defaultOpen = false,
1670
+ onOpenChange,
1671
+ maxHeight
1005
1672
  } = props;
1006
1673
  const reactId = useId();
1007
1674
  const id = idProp ?? reactId;
1008
1675
  const listId = `${id}-listbox`;
1009
- const isControlled = valueProp !== void 0;
1010
- const [inner, setInner] = useState(() => toArray(defaultValue));
1011
- const selectedValues = isControlled ? toArray(valueProp) : inner;
1676
+ const baseOptions = useMemo(() => flattenOptions(options), [options]);
1677
+ const [createdOptions, setCreatedOptions] = useState([]);
1678
+ const allOptions = useMemo(
1679
+ () => [...baseOptions, ...createdOptions],
1680
+ [baseOptions, createdOptions]
1681
+ );
1682
+ const [selectedValues, setSelectedValues] = useControllableState({
1683
+ value: valueProp === void 0 ? void 0 : toArray(valueProp),
1684
+ defaultValue: toArray(defaultValue),
1685
+ onChange: void 0
1686
+ });
1012
1687
  const commit = (next) => {
1013
- if (!isControlled) setInner(next);
1688
+ setSelectedValues(next);
1014
1689
  onChange?.(multiple ? next : next[0] ?? null);
1015
1690
  };
1016
- const [open, setOpen] = useState(false);
1691
+ const openIsControlled = openProp !== void 0;
1692
+ const [innerOpen, setInnerOpen] = useState(defaultOpen);
1693
+ const open = openIsControlled ? openProp : innerOpen;
1694
+ const setOpen = (next) => {
1695
+ if (!openIsControlled) setInnerOpen(next);
1696
+ onOpenChange?.(next);
1697
+ };
1017
1698
  const [query, setQuery] = useState("");
1018
1699
  const [activeIndex, setActiveIndex] = useState(0);
1019
1700
  const boxRef = useRef(null);
1020
1701
  const inputRef = useRef(null);
1021
1702
  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);
1703
+ const byValue = useMemo(() => new Map(allOptions.map((o) => [o.value, o])), [allOptions]);
1704
+ const selectedOptions = selectedValues.map((v) => byValue.get(v) ?? { label: v, value: v }).filter(Boolean);
1705
+ useEffect(() => {
1706
+ if (!onSearch) return;
1707
+ const t = setTimeout(() => onSearch(query), searchDebounce);
1708
+ return () => clearTimeout(t);
1709
+ }, [query, onSearch, searchDebounce]);
1024
1710
  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]);
1711
+ let list = allOptions;
1712
+ if (searchable && query && !onSearch) {
1713
+ const q = query.toLowerCase();
1714
+ const match = filterFn ?? ((o) => filterMode === "startsWith" ? o.label.toLowerCase().startsWith(q) : o.label.toLowerCase().includes(q));
1715
+ list = list.filter((o) => match(o, query));
1716
+ }
1717
+ if (multiple && hidePickedOptions) {
1718
+ list = list.filter((o) => !selectedValues.includes(o.value));
1719
+ }
1720
+ return list;
1721
+ }, [
1722
+ allOptions,
1723
+ searchable,
1724
+ query,
1725
+ onSearch,
1726
+ filterMode,
1727
+ filterFn,
1728
+ multiple,
1729
+ hidePickedOptions,
1730
+ selectedValues
1731
+ ]);
1732
+ const showCreateRow = creatable && searchable && query.trim().length > 0 && (isValidNewOption ? isValidNewOption(query.trim(), allOptions) : !allOptions.some((o) => o.label.toLowerCase() === query.trim().toLowerCase()));
1733
+ const createRow = { value: CREATE_VALUE, label: query.trim() };
1734
+ const selectableOptions = showCreateRow ? createPosition === "first" ? [createRow, ...filtered] : [...filtered, createRow] : filtered;
1030
1735
  const rows = useMemo(() => {
1031
1736
  const out = [];
1032
1737
  let seenGroup;
1033
- let optionIndex = 0;
1034
- for (const o of filtered) {
1035
- if (o.group && o.group !== seenGroup) {
1738
+ selectableOptions.forEach((o, optionIndex) => {
1739
+ if (o.value !== CREATE_VALUE && o.group && o.group !== seenGroup) {
1036
1740
  seenGroup = o.group;
1037
1741
  out.push({ type: "group", label: o.group });
1038
1742
  }
1039
1743
  out.push({ type: "option", option: o, optionIndex });
1040
- optionIndex += 1;
1041
- }
1744
+ });
1042
1745
  return out;
1043
- }, [filtered]);
1044
- const selectableOptions = filtered;
1746
+ }, [selectableOptions]);
1045
1747
  useEffect(() => {
1046
1748
  setActiveIndex((i) => Math.min(Math.max(0, i), Math.max(0, selectableOptions.length - 1)));
1047
1749
  }, [selectableOptions.length]);
@@ -1062,10 +1764,34 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1062
1764
  setActiveIndex(firstRelevantIndex());
1063
1765
  setOpen(true);
1064
1766
  };
1767
+ const handleCreate = (raw) => {
1768
+ const input = raw.trim();
1769
+ if (!input) return;
1770
+ onCreate?.(input);
1771
+ setCreatedOptions(
1772
+ (prev) => prev.some((o) => o.value === input) || baseOptions.some((o) => o.value === input) ? prev : [...prev, { label: input, value: input }]
1773
+ );
1774
+ if (multiple) {
1775
+ if (maxValues != null && selectedValues.length >= maxValues) return;
1776
+ commit([...selectedValues, input]);
1777
+ } else {
1778
+ commit([input]);
1779
+ }
1780
+ setQuery("");
1781
+ if (closeOnSelect) {
1782
+ setOpen(false);
1783
+ inputRef.current?.focus();
1784
+ }
1785
+ };
1065
1786
  const pick = (opt) => {
1787
+ if (opt.value === CREATE_VALUE) {
1788
+ handleCreate(opt.label);
1789
+ return;
1790
+ }
1066
1791
  if (opt.disabled) return;
1067
1792
  if (multiple) {
1068
1793
  const has = selectedValues.includes(opt.value);
1794
+ if (!has && maxValues != null && selectedValues.length >= maxValues) return;
1069
1795
  commit(
1070
1796
  has ? selectedValues.filter((v) => v !== opt.value) : [...selectedValues, opt.value]
1071
1797
  );
@@ -1083,6 +1809,16 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1083
1809
  onClear?.();
1084
1810
  setQuery("");
1085
1811
  };
1812
+ const selectAllTargets = filtered.filter((o) => !o.disabled).map((o) => o.value);
1813
+ const allSelected = selectAllTargets.length > 0 && selectAllTargets.every((v) => selectedValues.includes(v));
1814
+ const toggleSelectAll = () => {
1815
+ if (allSelected) {
1816
+ commit(selectedValues.filter((v) => !selectAllTargets.includes(v)));
1817
+ } else {
1818
+ const merged = Array.from(/* @__PURE__ */ new Set([...selectedValues, ...selectAllTargets]));
1819
+ commit(maxValues != null ? merged.slice(0, maxValues) : merged);
1820
+ }
1821
+ };
1086
1822
  const onKeyDown = (e) => {
1087
1823
  if (disabled || readOnly) return;
1088
1824
  switch (e.key) {
@@ -1147,6 +1883,7 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1147
1883
  };
1148
1884
  const hasValue = selectedValues.length > 0;
1149
1885
  const doHighlight = highlightMatch ?? searchable;
1886
+ const isLoadingEmpty = Boolean(loading) && selectableOptions.length === 0;
1150
1887
  if (native && !multiple && !searchable) {
1151
1888
  return /* @__PURE__ */ jsx(
1152
1889
  Field,
@@ -1173,7 +1910,7 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1173
1910
  onChange: (e) => commit(e.target.value ? [e.target.value] : []),
1174
1911
  children: [
1175
1912
  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))
1913
+ baseOptions.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value))
1177
1914
  ]
1178
1915
  }
1179
1916
  )
@@ -1260,7 +1997,6 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1260
1997
  {
1261
1998
  ...ref ? { ref: setRefs(ref, inputRef) } : { ref: inputRef },
1262
1999
  id,
1263
- name,
1264
2000
  className: "orynn-select__search",
1265
2001
  role: "combobox",
1266
2002
  "aria-expanded": open,
@@ -1272,7 +2008,11 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1272
2008
  autoComplete: "off",
1273
2009
  readOnly: !searchable || readOnly,
1274
2010
  disabled,
1275
- placeholder: !hasValue && (searchable || !floatingLabel) ? placeholder : void 0,
2011
+ placeholder: (
2012
+ // only the search input shows the placeholder; when not
2013
+ // searchable the sibling span owns it (avoids a double render)
2014
+ !hasValue && searchable ? placeholder : void 0
2015
+ ),
1276
2016
  value: query,
1277
2017
  size: 1,
1278
2018
  onChange: (e) => {
@@ -1282,7 +2022,8 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1282
2022
  onKeyDown,
1283
2023
  onFocus: openPanel
1284
2024
  }
1285
- )
2025
+ ),
2026
+ 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
2027
  ] })
1287
2028
  }
1288
2029
  ),
@@ -1293,44 +2034,61 @@ var Dropdown = /* @__PURE__ */ forwardRef(
1293
2034
  anchorRef: boxRef,
1294
2035
  onClose: () => setOpen(false),
1295
2036
  popoverRef: listRef,
1296
- children: /* @__PURE__ */ jsx(
2037
+ maxHeight,
2038
+ children: /* @__PURE__ */ jsxs(
1297
2039
  "div",
1298
2040
  {
1299
2041
  className: "orynn-listbox",
1300
2042
  id: listId,
1301
2043
  role: "listbox",
1302
2044
  "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
2045
+ children: [
2046
+ multiple && showSelectAll && selectAllTargets.length > 0 && /* @__PURE__ */ jsx("button", { type: "button", className: "orynn-listbox__action", onClick: toggleSelectAll, children: allSelected ? "Clear all" : "Select all" }),
2047
+ isLoadingEmpty ? /* @__PURE__ */ jsx("div", { className: "orynn-listbox__empty", children: loadingMessage }) : selectableOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "orynn-listbox__empty", children: emptyMessage }) : rows.map(
2048
+ (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(
2049
+ "div",
2050
+ {
2051
+ id: `${id}-opt-${CREATE_VALUE}`,
2052
+ role: "option",
2053
+ "aria-selected": false,
2054
+ className: "orynn-option orynn-option--create",
2055
+ "data-active": row.optionIndex === activeIndex || void 0,
2056
+ onMouseEnter: () => setActiveIndex(row.optionIndex),
2057
+ onClick: () => pick(row.option),
2058
+ 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}"` }) })
2059
+ },
2060
+ CREATE_VALUE
2061
+ ) : /* @__PURE__ */ jsx(
2062
+ "div",
2063
+ {
2064
+ id: `${id}-opt-${row.option.value}`,
2065
+ role: "option",
2066
+ "aria-selected": selectedValues.includes(row.option.value),
2067
+ "aria-disabled": row.option.disabled || void 0,
2068
+ className: "orynn-option",
2069
+ "data-active": row.optionIndex === activeIndex || void 0,
2070
+ "data-selected": selectedValues.includes(row.option.value) || void 0,
2071
+ "data-disabled": row.option.disabled || void 0,
2072
+ onMouseEnter: () => setActiveIndex(row.optionIndex),
2073
+ onClick: () => pick(row.option),
2074
+ children: renderOption ? renderOption(row.option, {
2075
+ selected: selectedValues.includes(row.option.value),
2076
+ active: row.optionIndex === activeIndex,
2077
+ disabled: Boolean(row.option.disabled),
2078
+ query
2079
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2080
+ row.option.icon,
2081
+ /* @__PURE__ */ jsxs("span", { className: "orynn-option__body", children: [
2082
+ /* @__PURE__ */ jsx("span", { className: "orynn-option__label", children: doHighlight ? /* @__PURE__ */ jsx(Highlight, { text: row.option.label, query }) : row.option.label }),
2083
+ row.option.description != null && /* @__PURE__ */ jsx("span", { className: "orynn-option__desc", children: row.option.description })
2084
+ ] }),
2085
+ selectedValues.includes(row.option.value) && /* @__PURE__ */ jsx(CheckIcon, { className: "orynn-option__check" })
2086
+ ] })
2087
+ },
2088
+ row.option.value
2089
+ )
1332
2090
  )
1333
- )
2091
+ ]
1334
2092
  }
1335
2093
  )
1336
2094
  }
@@ -1378,8 +2136,14 @@ var Input = /* @__PURE__ */ forwardRef(
1378
2136
  type = "text",
1379
2137
  prefix,
1380
2138
  suffix,
2139
+ addonBefore,
2140
+ addonAfter,
1381
2141
  passwordToggle,
2142
+ passwordVisible,
2143
+ onPasswordVisibleChange,
2144
+ visibilityToggleIcon,
1382
2145
  showCount,
2146
+ debounce,
1383
2147
  maxLength,
1384
2148
  placeholder,
1385
2149
  ...rest
@@ -1391,14 +2155,32 @@ var Input = /* @__PURE__ */ forwardRef(
1391
2155
  defaultValue: defaultValue ?? "",
1392
2156
  onChange: void 0
1393
2157
  });
1394
- const [reveal, setReveal] = useState(false);
2158
+ const revealIsControlled = passwordVisible !== void 0;
2159
+ const [innerReveal, setInnerReveal] = useState(false);
2160
+ const reveal = revealIsControlled ? passwordVisible : innerReveal;
2161
+ const setReveal = (next) => {
2162
+ if (!revealIsControlled) setInnerReveal(next);
2163
+ onPasswordVisibleChange?.(next);
2164
+ };
2165
+ const debounceTimer = useRef();
2166
+ useEffect(() => () => clearTimeout(debounceTimer.current), []);
2167
+ const emitChange = (v, e) => {
2168
+ if (debounce && debounce > 0) {
2169
+ clearTimeout(debounceTimer.current);
2170
+ debounceTimer.current = setTimeout(() => onChange?.(v, e), debounce);
2171
+ } else {
2172
+ onChange?.(v, e);
2173
+ }
2174
+ };
1395
2175
  const showToggle = (passwordToggle ?? type === "password") && !disabled && !readOnly;
1396
2176
  const effectiveType = type === "password" && reveal ? "text" : type;
1397
2177
  const hasValue = value.length > 0;
1398
2178
  const showClear = clearable && hasValue && !disabled && !readOnly;
2179
+ const countMax = showCount && typeof showCount === "object" ? showCount.max ?? maxLength : maxLength;
2180
+ const countNode = showCount && typeof showCount === "object" && showCount.formatter ? showCount.formatter(value.length, countMax) : `${value.length}${countMax ? `/${countMax}` : ""}`;
1399
2181
  const handleChange = (e) => {
1400
2182
  setValue(e.target.value);
1401
- onChange?.(e.target.value, e);
2183
+ emitChange(e.target.value, e);
1402
2184
  };
1403
2185
  const handleKeyDown = (e) => {
1404
2186
  if (e.key === "Enter") onEnter?.(value);
@@ -1430,16 +2212,15 @@ var Input = /* @__PURE__ */ forwardRef(
1430
2212
  invalid,
1431
2213
  valid,
1432
2214
  active: hasValue,
2215
+ addonBefore,
2216
+ addonAfter,
1433
2217
  left: startContent != null || prefix != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
1434
2218
  startContent,
1435
2219
  prefix != null && /* @__PURE__ */ jsx("span", { className: "orynn-box__adornment--affix", children: prefix })
1436
2220
  ] }) : void 0,
1437
2221
  right: /* @__PURE__ */ jsxs(Fragment, { children: [
1438
2222
  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
- ] }),
2223
+ showCount && /* @__PURE__ */ jsx("span", { className: "orynn-box__adornment--affix", children: countNode }),
1443
2224
  showClear && /* @__PURE__ */ jsx(
1444
2225
  "button",
1445
2226
  {
@@ -1459,8 +2240,8 @@ var Input = /* @__PURE__ */ forwardRef(
1459
2240
  "aria-label": reveal ? "Hide" : "Show",
1460
2241
  "aria-pressed": reveal,
1461
2242
  tabIndex: -1,
1462
- onClick: () => setReveal((v) => !v),
1463
- children: reveal ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {})
2243
+ onClick: () => setReveal(!reveal),
2244
+ children: visibilityToggleIcon ? visibilityToggleIcon(reveal) : reveal ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {})
1464
2245
  }
1465
2246
  ),
1466
2247
  loading && /* @__PURE__ */ jsx(SpinnerIcon, {}),
@@ -1497,6 +2278,11 @@ var Input = /* @__PURE__ */ forwardRef(
1497
2278
  );
1498
2279
  var clamp = (n, min, max) => Math.min(max ?? Number.POSITIVE_INFINITY, Math.max(min ?? Number.NEGATIVE_INFINITY, n));
1499
2280
  var round = (n, p) => p == null ? n : Math.round(n * 10 ** p) / 10 ** p;
2281
+ var SENTINEL_GROUP = "[[g]]";
2282
+ var SENTINEL_DECIMAL = "[[d]]";
2283
+ function swapSeparators(out, seps, thousandSeparator, decimalSeparator) {
2284
+ 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);
2285
+ }
1500
2286
  var NumberField = /* @__PURE__ */ forwardRef(
1501
2287
  function NumberField2(props, ref) {
1502
2288
  const {
@@ -1519,19 +2305,37 @@ var NumberField = /* @__PURE__ */ forwardRef(
1519
2305
  value: valueProp,
1520
2306
  defaultValue = null,
1521
2307
  onChange,
2308
+ onValueChange,
1522
2309
  min,
1523
2310
  max,
1524
2311
  step = 1,
1525
2312
  shiftStep,
2313
+ smallStep,
1526
2314
  precision,
2315
+ decimalScale,
2316
+ fixedDecimalScale,
1527
2317
  buttons = "stacked",
2318
+ hideControls,
1528
2319
  grouping,
2320
+ thousandSeparator,
2321
+ decimalSeparator,
1529
2322
  locale,
2323
+ style,
1530
2324
  currency,
2325
+ unit,
2326
+ formatOptions,
2327
+ format: formatFn,
2328
+ parse: parseFn,
1531
2329
  prefix,
1532
2330
  suffix,
1533
- clampOnBlur = true,
2331
+ allowNegative = true,
2332
+ clampBehavior,
2333
+ clampOnBlur,
2334
+ emptyValue = null,
1534
2335
  allowMouseWheel,
2336
+ isWheelDisabled,
2337
+ stepHoldDelay = 300,
2338
+ stepHoldInterval = 60,
1535
2339
  placeholder,
1536
2340
  ...rest
1537
2341
  } = props;
@@ -1542,15 +2346,45 @@ var NumberField = /* @__PURE__ */ forwardRef(
1542
2346
  defaultValue,
1543
2347
  onChange: void 0
1544
2348
  });
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
- );
2349
+ const resolvedStyle = style ?? (currency ? "currency" : "decimal");
2350
+ const scale = decimalScale ?? precision;
2351
+ const clampMode = clampBehavior ?? (clampOnBlur === false ? "none" : "blur");
2352
+ const effMin = allowNegative ? min : Math.max(0, min ?? 0);
2353
+ const showButtons = hideControls ? false : buttons;
2354
+ const wheelEnabled = isWheelDisabled != null ? !isWheelDisabled : Boolean(allowMouseWheel);
2355
+ const fmt = useMemo(() => {
2356
+ if (formatOptions) return new Intl.NumberFormat(locale, formatOptions);
2357
+ const opts = {
2358
+ useGrouping: grouping ?? (thousandSeparator != null || resolvedStyle === "currency")
2359
+ };
2360
+ if (resolvedStyle === "currency") {
2361
+ opts.style = "currency";
2362
+ opts.currency = currency ?? "USD";
2363
+ } else if (resolvedStyle === "percent") {
2364
+ opts.style = "percent";
2365
+ } else if (resolvedStyle === "unit" && unit) {
2366
+ opts.style = "unit";
2367
+ opts.unit = unit;
2368
+ }
2369
+ if (scale != null) {
2370
+ opts.maximumFractionDigits = scale;
2371
+ if (fixedDecimalScale) opts.minimumFractionDigits = scale;
2372
+ } else {
2373
+ opts.maximumFractionDigits = resolvedStyle === "currency" ? 2 : 20;
2374
+ opts.minimumFractionDigits = resolvedStyle === "currency" ? 2 : 0;
2375
+ }
2376
+ return new Intl.NumberFormat(locale, opts);
2377
+ }, [
2378
+ locale,
2379
+ formatOptions,
2380
+ grouping,
2381
+ thousandSeparator,
2382
+ resolvedStyle,
2383
+ currency,
2384
+ unit,
2385
+ scale,
2386
+ fixedDecimalScale
2387
+ ]);
1554
2388
  const seps = useMemo(() => {
1555
2389
  const parts = new Intl.NumberFormat(locale).formatToParts(11111.1);
1556
2390
  return {
@@ -1559,23 +2393,47 @@ var NumberField = /* @__PURE__ */ forwardRef(
1559
2393
  };
1560
2394
  }, [locale]);
1561
2395
  const display = useCallback(
1562
- (n) => n == null || Number.isNaN(n) ? "" : `${prefix ?? ""}${fmt.format(n)}${suffix ?? ""}`,
1563
- [fmt, prefix, suffix]
2396
+ (n) => {
2397
+ if (n == null || Number.isNaN(n)) return "";
2398
+ if (formatFn) return formatFn(n);
2399
+ let out = fmt.format(n);
2400
+ if (thousandSeparator != null || decimalSeparator != null) {
2401
+ out = swapSeparators(out, seps, thousandSeparator, decimalSeparator);
2402
+ }
2403
+ return `${prefix ?? ""}${out}${suffix ?? ""}`;
2404
+ },
2405
+ [fmt, formatFn, prefix, suffix, seps, thousandSeparator, decimalSeparator]
1564
2406
  );
1565
2407
  const parse = useCallback(
1566
2408
  (raw) => {
2409
+ if (parseFn) return parseFn(raw);
1567
2410
  let s = raw;
1568
2411
  if (prefix) s = s.split(prefix).join("");
1569
2412
  if (suffix) s = s.split(suffix).join("");
1570
- s = s.split(seps.group).join("").split(seps.decimal).join(".");
2413
+ const grp = thousandSeparator ?? seps.group;
2414
+ const dec = decimalSeparator ?? seps.decimal;
2415
+ s = s.split(grp).join("").split(dec).join(".");
1571
2416
  s = s.replace(/[^\d.\-]/g, "");
2417
+ if (!allowNegative) s = s.replace(/-/g, "");
1572
2418
  if (s === "" || s === "-" || s === ".") return null;
1573
2419
  const first = s.indexOf(".");
1574
2420
  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;
2421
+ let n = Number(s);
2422
+ if (Number.isNaN(n)) return null;
2423
+ if (resolvedStyle === "percent" && !formatFn) n = n / 100;
2424
+ return n;
1577
2425
  },
1578
- [prefix, suffix, seps]
2426
+ [
2427
+ parseFn,
2428
+ prefix,
2429
+ suffix,
2430
+ seps,
2431
+ thousandSeparator,
2432
+ decimalSeparator,
2433
+ allowNegative,
2434
+ resolvedStyle,
2435
+ formatFn
2436
+ ]
1579
2437
  );
1580
2438
  const [text, setText] = useState(() => display(value));
1581
2439
  const [editing, setEditing] = useState(false);
@@ -1585,65 +2443,76 @@ var NumberField = /* @__PURE__ */ forwardRef(
1585
2443
  const commit = (n) => {
1586
2444
  setValue(n);
1587
2445
  onChange?.(n);
2446
+ onValueChange?.({ value: n, formatted: display(n) });
1588
2447
  };
1589
2448
  const setNumber = (n) => {
1590
2449
  commit(n);
1591
2450
  setText(display(n));
1592
2451
  };
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));
2452
+ const bump = (dir, mode = "n") => {
2453
+ const amount = mode === "l" ? shiftStep ?? step * 10 : mode === "s" ? smallStep ?? step / 10 : step;
2454
+ const base3 = value ?? (dir > 0 ? effMin ?? 0 : max ?? 0);
2455
+ let next = round(base3 + amount * dir, scale);
2456
+ if (clampMode !== "none") next = clamp(next, effMin, max);
2457
+ else if (!allowNegative && next < 0) next = 0;
2458
+ setNumber(next);
1597
2459
  };
1598
2460
  const innerRef = useRef(null);
1599
2461
  useEffect(() => {
1600
2462
  const el = innerRef.current;
1601
- if (!el || !allowMouseWheel) return;
2463
+ if (!el || !wheelEnabled) return;
1602
2464
  const onWheel = (e) => {
1603
2465
  if (document.activeElement !== el) return;
1604
2466
  e.preventDefault();
1605
- bump(e.deltaY < 0 ? 1 : -1, e.shiftKey);
2467
+ bump(e.deltaY < 0 ? 1 : -1, e.shiftKey ? "l" : e.altKey ? "s" : "n");
1606
2468
  };
1607
2469
  el.addEventListener("wheel", onWheel, { passive: false });
1608
2470
  return () => el.removeEventListener("wheel", onWheel);
1609
2471
  });
1610
2472
  const onKeyDown = (e) => {
1611
2473
  if (disabled || readOnly) return;
2474
+ const mode = e.shiftKey ? "l" : e.altKey ? "s" : "n";
1612
2475
  if (e.key === "ArrowUp") {
1613
2476
  e.preventDefault();
1614
- bump(1, e.shiftKey);
2477
+ bump(1, mode);
1615
2478
  } else if (e.key === "ArrowDown") {
1616
2479
  e.preventDefault();
1617
- bump(-1, e.shiftKey);
2480
+ bump(-1, mode);
1618
2481
  } else if (e.key === "PageUp") {
1619
2482
  e.preventDefault();
1620
- bump(1, true);
2483
+ bump(1, "l");
1621
2484
  } else if (e.key === "PageDown") {
1622
2485
  e.preventDefault();
1623
- bump(-1, true);
1624
- } else if (e.key === "Home" && min != null) {
2486
+ bump(-1, "l");
2487
+ } else if (e.key === "Home" && effMin != null) {
1625
2488
  e.preventDefault();
1626
- setNumber(min);
2489
+ setNumber(effMin);
1627
2490
  } else if (e.key === "End" && max != null) {
1628
2491
  e.preventDefault();
1629
2492
  setNumber(max);
1630
2493
  }
1631
2494
  };
1632
2495
  const holdTimer = useRef();
2496
+ const holdDelay = useRef();
1633
2497
  const startHold = (dir) => {
1634
2498
  bump(dir);
1635
2499
  let count = 0;
1636
- holdTimer.current = setInterval(() => {
1637
- count += 1;
1638
- bump(dir, count > 6);
1639
- }, 110);
2500
+ holdDelay.current = setTimeout(() => {
2501
+ holdTimer.current = setInterval(() => {
2502
+ count += 1;
2503
+ bump(dir, count > 6 ? "l" : "n");
2504
+ }, stepHoldInterval);
2505
+ }, stepHoldDelay);
1640
2506
  };
1641
- const stopHold = () => clearInterval(holdTimer.current);
1642
- useEffect(() => () => clearInterval(holdTimer.current), []);
1643
- const StepButtons = buttons && /* @__PURE__ */ jsxs(
2507
+ const stopHold = () => {
2508
+ clearTimeout(holdDelay.current);
2509
+ clearInterval(holdTimer.current);
2510
+ };
2511
+ useEffect(() => stopHold, []);
2512
+ const StepButtons = showButtons && /* @__PURE__ */ jsxs(
1644
2513
  "div",
1645
2514
  {
1646
- className: `orynn-number__buttons${buttons === "horizontal" ? " orynn-number__buttons--horizontal" : ""}`,
2515
+ className: `orynn-number__buttons${showButtons === "horizontal" ? " orynn-number__buttons--horizontal" : ""}`,
1647
2516
  children: [
1648
2517
  /* @__PURE__ */ jsx(
1649
2518
  "button",
@@ -1656,7 +2525,7 @@ var NumberField = /* @__PURE__ */ forwardRef(
1656
2525
  onPointerDown: () => startHold(1),
1657
2526
  onPointerUp: stopHold,
1658
2527
  onPointerLeave: stopHold,
1659
- children: /* @__PURE__ */ jsx(PlusIcon, {})
2528
+ children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "orynn-number__caret" })
1660
2529
  }
1661
2530
  ),
1662
2531
  /* @__PURE__ */ jsx(
@@ -1666,11 +2535,11 @@ var NumberField = /* @__PURE__ */ forwardRef(
1666
2535
  className: "orynn-number__btn",
1667
2536
  "aria-label": "Decrement",
1668
2537
  tabIndex: -1,
1669
- disabled: disabled || readOnly || min != null && (value ?? 0) <= min,
2538
+ disabled: disabled || readOnly || effMin != null && (value ?? 0) <= effMin,
1670
2539
  onPointerDown: () => startHold(-1),
1671
2540
  onPointerUp: stopHold,
1672
2541
  onPointerLeave: stopHold,
1673
- children: /* @__PURE__ */ jsx(MinusIcon, {})
2542
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "orynn-number__caret" })
1674
2543
  }
1675
2544
  )
1676
2545
  ]
@@ -1721,7 +2590,7 @@ var NumberField = /* @__PURE__ */ forwardRef(
1721
2590
  inputMode: "decimal",
1722
2591
  role: "spinbutton",
1723
2592
  "aria-valuenow": value ?? void 0,
1724
- "aria-valuemin": min,
2593
+ "aria-valuemin": effMin,
1725
2594
  "aria-valuemax": max,
1726
2595
  value: text,
1727
2596
  disabled,
@@ -1733,13 +2602,16 @@ var NumberField = /* @__PURE__ */ forwardRef(
1733
2602
  onFocus: () => setEditing(true),
1734
2603
  onChange: (e) => {
1735
2604
  setText(e.target.value);
1736
- commit(parse(e.target.value));
2605
+ const parsed = parse(e.target.value);
2606
+ commit(
2607
+ parsed == null ? null : clampMode === "strict" ? clamp(parsed, effMin, max) : parsed
2608
+ );
1737
2609
  },
1738
2610
  onKeyDown,
1739
2611
  onBlur: () => {
1740
2612
  setEditing(false);
1741
2613
  const parsed = parse(text);
1742
- const final = parsed == null ? null : round(clampOnBlur ? clamp(parsed, min, max) : parsed, precision);
2614
+ const final = parsed == null ? emptyValue : round(clampMode !== "none" ? clamp(parsed, effMin, max) : parsed, scale);
1743
2615
  if (final !== value) commit(final);
1744
2616
  setText(display(final));
1745
2617
  }
@@ -1752,7 +2624,7 @@ var NumberField = /* @__PURE__ */ forwardRef(
1752
2624
  }
1753
2625
  );
1754
2626
  var RadioGroupContext = createContext(null);
1755
- var sizePx2 = { sm: "0.95rem", md: "1.125rem", lg: "1.35rem" };
2627
+ var sizePx2 = { sm: "0.875rem", md: "1rem", lg: "1.125rem" };
1756
2628
  var Radio = /* @__PURE__ */ forwardRef(
1757
2629
  function Radio2(props, ref) {
1758
2630
  const group = useContext(RadioGroupContext);
@@ -1829,6 +2701,7 @@ function RadioGroup(props) {
1829
2701
  defaultValue = null,
1830
2702
  onChange,
1831
2703
  orientation = "vertical",
2704
+ columns,
1832
2705
  options,
1833
2706
  children,
1834
2707
  className,
@@ -1864,12 +2737,17 @@ function RadioGroup(props) {
1864
2737
  "aria-labelledby": label != null ? `${id}-label` : void 0,
1865
2738
  "aria-required": required || void 0,
1866
2739
  className: "orynn-choice-group",
1867
- "data-orientation": orientation,
2740
+ "data-orientation": columns ? void 0 : orientation,
2741
+ "data-columns": columns ? "" : void 0,
2742
+ style: columns ? { "--orynn-choice-columns": columns } : void 0,
1868
2743
  children: options ? options.map((o) => /* @__PURE__ */ jsx(
1869
2744
  Radio,
1870
2745
  {
1871
2746
  value: o.value,
1872
- label: o.label,
2747
+ label: o.icon != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
2748
+ /* @__PURE__ */ jsx("span", { className: "orynn-choice__icon", children: o.icon }),
2749
+ o.label
2750
+ ] }) : o.label,
1873
2751
  description: o.description,
1874
2752
  disabled: o.disabled
1875
2753
  },
@@ -1896,12 +2774,17 @@ var Textarea = /* @__PURE__ */ forwardRef(
1896
2774
  floatingLabel,
1897
2775
  clearable,
1898
2776
  onClear,
2777
+ loading,
2778
+ endContent,
1899
2779
  id: idProp,
1900
2780
  name,
1901
2781
  className,
1902
2782
  value: valueProp,
1903
2783
  defaultValue,
1904
2784
  onChange,
2785
+ onEnter,
2786
+ submitOnEnter,
2787
+ onKeyDown,
1905
2788
  autoResize,
1906
2789
  minRows = 3,
1907
2790
  maxRows = 10,
@@ -1926,9 +2809,9 @@ var Textarea = /* @__PURE__ */ forwardRef(
1926
2809
  el.style.height = "auto";
1927
2810
  const cs = getComputedStyle(el);
1928
2811
  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;
2812
+ const pad3 = Number.parseFloat(cs.paddingTop) + Number.parseFloat(cs.paddingBottom);
2813
+ const min = line * minRows + pad3;
2814
+ const max = line * maxRows + pad3;
1932
2815
  el.style.height = `${Math.min(Math.max(el.scrollHeight, min), max)}px`;
1933
2816
  el.style.overflowY = el.scrollHeight > max ? "auto" : "hidden";
1934
2817
  }, [autoResize, minRows, maxRows]);
@@ -1937,7 +2820,16 @@ var Textarea = /* @__PURE__ */ forwardRef(
1937
2820
  setValue(e.target.value);
1938
2821
  onChange?.(e.target.value, e);
1939
2822
  };
2823
+ const handleKeyDown = (e) => {
2824
+ if (e.key === "Enter" && !e.shiftKey && (onEnter || submitOnEnter)) {
2825
+ if (submitOnEnter) e.preventDefault();
2826
+ onEnter?.(value);
2827
+ }
2828
+ onKeyDown?.(e);
2829
+ };
1940
2830
  const hasValue = value.length > 0;
2831
+ const countMax = showCount && typeof showCount === "object" ? showCount.max ?? maxLength : maxLength;
2832
+ const countNode = showCount && typeof showCount === "object" && showCount.formatter ? showCount.formatter(value.length, countMax) : `${value.length}${countMax ? `/${countMax}` : ""}`;
1941
2833
  return /* @__PURE__ */ jsx(
1942
2834
  Field,
1943
2835
  {
@@ -1962,20 +2854,24 @@ var Textarea = /* @__PURE__ */ forwardRef(
1962
2854
  valid,
1963
2855
  active: hasValue,
1964
2856
  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,
2857
+ right: clearable && hasValue || loading || endContent != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
2858
+ clearable && hasValue && !disabled && !readOnly && /* @__PURE__ */ jsx(
2859
+ "button",
2860
+ {
2861
+ type: "button",
2862
+ className: "orynn-box__iconbtn",
2863
+ "aria-label": "Clear",
2864
+ tabIndex: -1,
2865
+ onClick: () => {
2866
+ setValue("");
2867
+ onClear?.();
2868
+ },
2869
+ children: /* @__PURE__ */ jsx(XIcon, {})
2870
+ }
2871
+ ),
2872
+ loading && /* @__PURE__ */ jsx(SpinnerIcon, {}),
2873
+ endContent
2874
+ ] }) : void 0,
1979
2875
  children: [
1980
2876
  /* @__PURE__ */ jsx(
1981
2877
  "textarea",
@@ -1995,13 +2891,11 @@ var Textarea = /* @__PURE__ */ forwardRef(
1995
2891
  placeholder: floatingLabel ? void 0 : placeholder,
1996
2892
  "aria-invalid": invalid || void 0,
1997
2893
  "aria-describedby": describedById,
1998
- onChange: handleChange
2894
+ onChange: handleChange,
2895
+ onKeyDown: handleKeyDown
1999
2896
  }
2000
2897
  ),
2001
- showCount && /* @__PURE__ */ jsxs("span", { className: "orynn-box__footer", children: [
2002
- value.length,
2003
- maxLength ? `/${maxLength}` : ""
2004
- ] })
2898
+ showCount && /* @__PURE__ */ jsx("span", { className: "orynn-box__footer", children: countNode })
2005
2899
  ]
2006
2900
  }
2007
2901
  )
@@ -2058,24 +2952,63 @@ function registerField(type, registration) {
2058
2952
 
2059
2953
  // src/layout/resolve-columns.ts
2060
2954
  var DEFAULT_COLUMNS = { desktop: 3, tablet: 2, mobile: 1 };
2955
+ var DEFAULT_MIN_COL = 192;
2061
2956
  var clampCount = (n, fallback) => typeof n === "number" && Number.isFinite(n) && n >= 1 ? Math.floor(n) : fallback;
2062
- function resolveColumns(partial) {
2957
+ function resolveColumns(input) {
2958
+ if (typeof input === "number") {
2959
+ const n = clampCount(input, DEFAULT_COLUMNS.desktop);
2960
+ return { desktop: n, tablet: n, mobile: n };
2961
+ }
2063
2962
  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)
2963
+ desktop: clampCount(input?.desktop, DEFAULT_COLUMNS.desktop),
2964
+ tablet: clampCount(input?.tablet, DEFAULT_COLUMNS.tablet),
2965
+ mobile: clampCount(input?.mobile, DEFAULT_COLUMNS.mobile)
2067
2966
  };
2068
2967
  }
2968
+ function resolveGrid(width, cols, minColWidth = DEFAULT_MIN_COL) {
2969
+ const { desktop: d, tablet: t, mobile: m } = cols;
2970
+ if (!width || !Number.isFinite(width) || width <= 0) {
2971
+ return { count: d, tier: "desktop" };
2972
+ }
2973
+ const per = minColWidth > 0 ? minColWidth : DEFAULT_MIN_COL;
2974
+ const maxFit = Math.max(1, Math.floor(width / per));
2975
+ if (d === t && t === m) {
2976
+ const count = Math.min(d, maxFit);
2977
+ return { count, tier: count >= d ? "desktop" : count > 1 ? "tablet" : "mobile" };
2978
+ }
2979
+ if (maxFit >= d) return { count: d, tier: "desktop" };
2980
+ if (t < d && maxFit >= t) return { count: t, tier: "tablet" };
2981
+ if (maxFit >= m) return { count: m, tier: "mobile" };
2982
+ return { count: Math.min(m, maxFit), tier: "mobile" };
2983
+ }
2069
2984
  function clampSpan(span, columnCount) {
2070
2985
  const n = clampCount(span, 1);
2071
2986
  return Math.min(n, Math.max(columnCount, 1));
2072
2987
  }
2988
+ function pickSpan(layout, tier) {
2989
+ if (!layout) return 1;
2990
+ const v = layout[tier] ?? layout.desktop ?? layout.tablet ?? layout.mobile;
2991
+ return typeof v === "number" && Number.isFinite(v) && v >= 1 ? Math.floor(v) : 1;
2992
+ }
2073
2993
 
2074
2994
  // src/core/config/normalize.ts
2075
2995
  function normalizeGap(gap) {
2076
2996
  if (gap == null) return void 0;
2077
2997
  return typeof gap === "number" ? `${gap}px` : gap;
2078
2998
  }
2999
+ function normalizeMinColWidth(value) {
3000
+ if (typeof value === "number") return value > 0 ? value : DEFAULT_MIN_COL;
3001
+ if (typeof value === "string") {
3002
+ const n = Number.parseFloat(value);
3003
+ if (!Number.isFinite(n) || n <= 0) return DEFAULT_MIN_COL;
3004
+ if (/rem|em/i.test(value)) {
3005
+ const root = typeof document !== "undefined" ? Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16 : 16;
3006
+ return n * root;
3007
+ }
3008
+ return n;
3009
+ }
3010
+ return DEFAULT_MIN_COL;
3011
+ }
2079
3012
  function normalizeConfig(config, registry2) {
2080
3013
  invariant(
2081
3014
  config != null && typeof config === "object" && Array.isArray(config.fields),
@@ -2109,18 +3042,52 @@ function normalizeConfig(config, registry2) {
2109
3042
  return {
2110
3043
  fields: config.fields,
2111
3044
  columns: resolveColumns(config.columns),
3045
+ minColWidth: normalizeMinColWidth(config.minColumnWidth),
2112
3046
  gap: normalizeGap(config.gap),
2113
3047
  legend: config.legend,
2114
3048
  id: config.id
2115
3049
  };
2116
3050
  }
2117
3051
 
3052
+ // src/core/config/conditions.ts
3053
+ function evalCondition(when, values) {
3054
+ if (when == null) return true;
3055
+ if (typeof when === "function") return Boolean(when(values));
3056
+ const actual = values[when.field];
3057
+ if ("eq" in when) return actual === when.eq;
3058
+ if ("ne" in when) return actual !== when.ne;
3059
+ if ("in" in when) return Array.isArray(when.in) && when.in.includes(actual);
3060
+ if ("notIn" in when) return Array.isArray(when.notIn) && !when.notIn.includes(actual);
3061
+ if ("truthy" in when) return Boolean(actual) === Boolean(when.truthy);
3062
+ return Boolean(actual);
3063
+ }
3064
+
2118
3065
  // src/core/validation/messages.ts
2119
3066
  var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
3067
+ var dateStr = (p) => {
3068
+ const d = p instanceof Date ? p : new Date(String(p));
3069
+ return Number.isNaN(d.getTime()) ? String(p) : d.toLocaleDateString();
3070
+ };
2120
3071
  var defaultMessages = {
2121
3072
  required: ({ label }) => `${label} is required`,
2122
3073
  minLength: ({ param, label }) => `${label} must be at least ${plural(param, "character")}`,
2123
- maxLength: ({ param, label }) => `${label} must be at most ${plural(param, "character")}`
3074
+ maxLength: ({ param, label }) => `${label} must be at most ${plural(param, "character")}`,
3075
+ minItems: ({ param, label }) => `Select at least ${plural(param, "option")} for ${label}`,
3076
+ maxItems: ({ param, label }) => `Select at most ${plural(param, "option")} for ${label}`,
3077
+ min: ({ param, label }) => `${label} must be at least ${param}`,
3078
+ max: ({ param, label }) => `${label} must be at most ${param}`,
3079
+ integer: ({ label }) => `${label} must be a whole number`,
3080
+ minDate: ({ param, label }) => `${label} must be on or after ${dateStr(param)}`,
3081
+ maxDate: ({ param, label }) => `${label} must be on or before ${dateStr(param)}`,
3082
+ minRangeDays: ({ param, label }) => `${label} must span at least ${plural(param, "day")}`,
3083
+ maxRangeDays: ({ param, label }) => `${label} must span at most ${plural(param, "day")}`,
3084
+ email: ({ label }) => `${label} must be a valid email address`,
3085
+ url: ({ label }) => `${label} must be a valid URL`,
3086
+ pattern: ({ label }) => `${label} is not in the expected format`,
3087
+ oneOf: ({ label }) => `${label} is not an allowed value`,
3088
+ equals: ({ label }) => `${label} does not match`,
3089
+ validate: ({ label }) => `${label} is invalid`,
3090
+ schema: ({ label }) => `${label} is invalid`
2124
3091
  };
2125
3092
  var FALLBACK_LABEL = "This field";
2126
3093
  function resolveMessage(token, param, label, overrides) {
@@ -2134,6 +3101,50 @@ function resolveMessage(token, param, label, overrides) {
2134
3101
  // src/core/validation/rules.ts
2135
3102
  var isEmpty = (value) => value == null || value === "" || Array.isArray(value) && value.length === 0;
2136
3103
  var asLength = (value) => typeof value === "string" || Array.isArray(value) ? value.length : String(value ?? "").length;
3104
+ var toNumber = (value) => {
3105
+ if (typeof value === "number") return Number.isNaN(value) ? null : value;
3106
+ if (typeof value === "string" && value.trim() !== "") {
3107
+ const n = Number(value);
3108
+ return Number.isNaN(n) ? null : n;
3109
+ }
3110
+ return null;
3111
+ };
3112
+ var toTime = (value) => {
3113
+ if (value == null || value === "") return null;
3114
+ const d = value instanceof Date ? value : new Date(value);
3115
+ const t = d.getTime();
3116
+ return Number.isNaN(t) ? null : t;
3117
+ };
3118
+ var rangeSpanDays = (value) => {
3119
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3120
+ const { start, end } = value;
3121
+ const s = toTime(start);
3122
+ const e = toTime(end);
3123
+ if (s == null || e == null) return null;
3124
+ return Math.abs(Math.round((e - s) / 864e5)) + 1;
3125
+ };
3126
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3127
+ var URL_RE = /^(https?:\/\/)[^\s.]+\.\S{2,}$/i;
3128
+ function toRegExp(param) {
3129
+ if (param instanceof RegExp) return param;
3130
+ if (typeof param === "string") {
3131
+ try {
3132
+ return new RegExp(param);
3133
+ } catch {
3134
+ return null;
3135
+ }
3136
+ }
3137
+ if (param && typeof param === "object" && "value" in param) {
3138
+ const { value, flags } = param;
3139
+ if (value instanceof RegExp) return value;
3140
+ try {
3141
+ return new RegExp(value, flags);
3142
+ } catch {
3143
+ return null;
3144
+ }
3145
+ }
3146
+ return null;
3147
+ }
2137
3148
  var builtInRules = {
2138
3149
  required: (value, param) => {
2139
3150
  if (param === false) return null;
@@ -2146,6 +3157,84 @@ var builtInRules = {
2146
3157
  maxLength: (value, param) => {
2147
3158
  if (typeof param !== "number" || isEmpty(value)) return null;
2148
3159
  return asLength(value) > param ? "maxLength" : null;
3160
+ },
3161
+ minItems: (value, param) => {
3162
+ if (typeof param !== "number" || !Array.isArray(value)) return null;
3163
+ return value.length < param ? "minItems" : null;
3164
+ },
3165
+ maxItems: (value, param) => {
3166
+ if (typeof param !== "number" || !Array.isArray(value)) return null;
3167
+ return value.length > param ? "maxItems" : null;
3168
+ },
3169
+ email: (value, param) => {
3170
+ if (param === false || isEmpty(value)) return null;
3171
+ return EMAIL_RE.test(String(value)) ? null : "email";
3172
+ },
3173
+ url: (value, param) => {
3174
+ if (param === false || isEmpty(value)) return null;
3175
+ return URL_RE.test(String(value)) ? null : "url";
3176
+ },
3177
+ pattern: (value, param) => {
3178
+ if (isEmpty(value)) return null;
3179
+ const re = toRegExp(param);
3180
+ if (!re) return null;
3181
+ return re.test(String(value)) ? null : "pattern";
3182
+ },
3183
+ integer: (value, param) => {
3184
+ if (param === false || isEmpty(value)) return null;
3185
+ const n = toNumber(value);
3186
+ return n != null && Number.isInteger(n) ? null : "integer";
3187
+ },
3188
+ min: (value, param) => {
3189
+ if (typeof param !== "number" || isEmpty(value)) return null;
3190
+ const n = toNumber(value);
3191
+ return n != null && n < param ? "min" : null;
3192
+ },
3193
+ max: (value, param) => {
3194
+ if (typeof param !== "number" || isEmpty(value)) return null;
3195
+ const n = toNumber(value);
3196
+ return n != null && n > param ? "max" : null;
3197
+ },
3198
+ minDate: (value, param) => {
3199
+ if (isEmpty(value)) return null;
3200
+ const v = toTime(value);
3201
+ const p = toTime(param);
3202
+ return v != null && p != null && v < p ? "minDate" : null;
3203
+ },
3204
+ maxDate: (value, param) => {
3205
+ if (isEmpty(value)) return null;
3206
+ const v = toTime(value);
3207
+ const p = toTime(param);
3208
+ return v != null && p != null && v > p ? "maxDate" : null;
3209
+ },
3210
+ minRangeDays: (value, param) => {
3211
+ if (typeof param !== "number") return null;
3212
+ const span = rangeSpanDays(value);
3213
+ return span != null && span < param ? "minRangeDays" : null;
3214
+ },
3215
+ maxRangeDays: (value, param) => {
3216
+ if (typeof param !== "number") return null;
3217
+ const span = rangeSpanDays(value);
3218
+ return span != null && span > param ? "maxRangeDays" : null;
3219
+ },
3220
+ oneOf: (value, param) => {
3221
+ if (isEmpty(value) || !Array.isArray(param)) return null;
3222
+ return param.includes(value) ? null : "oneOf";
3223
+ },
3224
+ equals: (value, param, allValues) => {
3225
+ if (isEmpty(value)) return null;
3226
+ const target = param && typeof param === "object" && "field" in param ? allValues[param.field] : param;
3227
+ return value == target ? null : "equals";
3228
+ },
3229
+ validate: (value, param, allValues) => {
3230
+ if (typeof param !== "function") return null;
3231
+ const out = param(value, allValues);
3232
+ if (out instanceof Promise) {
3233
+ return out.then(
3234
+ (r) => typeof r === "string" ? r : r === false ? "validate" : null
3235
+ );
3236
+ }
3237
+ return typeof out === "string" ? out : out === false ? "validate" : null;
2149
3238
  }
2150
3239
  };
2151
3240
  var registry = new Map(Object.entries(builtInRules));
@@ -2159,12 +3248,12 @@ function getRule(name) {
2159
3248
  // src/core/validation/resolver.ts
2160
3249
  function createRuleResolver() {
2161
3250
  return (values, config) => {
2162
- const result = {};
3251
+ const pending = [];
2163
3252
  for (const field of config.fields) {
2164
3253
  const validation = field.validation;
2165
3254
  if (!validation) continue;
3255
+ if (!evalCondition(field.when, values)) continue;
2166
3256
  const value = values[field.name];
2167
- const errors = [];
2168
3257
  for (const key of Object.keys(validation)) {
2169
3258
  if (key === "messages") continue;
2170
3259
  const param = validation[key];
@@ -2176,16 +3265,34 @@ function createRuleResolver() {
2176
3265
  }
2177
3266
  continue;
2178
3267
  }
2179
- const token = rule(value, param, values);
2180
- if (token == null) continue;
2181
- errors.push({
3268
+ pending.push({
3269
+ field: field.name,
2182
3270
  rule: key,
2183
- message: resolveMessage(token, param, field.label, validation.messages)
3271
+ param,
3272
+ label: field.label,
3273
+ overrides: validation.messages,
3274
+ token: rule(value, param, values)
2184
3275
  });
2185
3276
  }
2186
- if (errors.length > 0) result[field.name] = errors;
2187
3277
  }
2188
- return result;
3278
+ const collect = (settled) => {
3279
+ const result = {};
3280
+ pending.forEach((p, i) => {
3281
+ const token = settled[i];
3282
+ if (token == null) return;
3283
+ const bucket = result[p.field] ?? [];
3284
+ bucket.push({
3285
+ rule: p.rule,
3286
+ message: resolveMessage(token, p.param, p.label, p.overrides)
3287
+ });
3288
+ result[p.field] = bucket;
3289
+ });
3290
+ return result;
3291
+ };
3292
+ if (pending.some((p) => p.token instanceof Promise)) {
3293
+ return Promise.all(pending.map((p) => Promise.resolve(p.token))).then(collect);
3294
+ }
3295
+ return collect(pending.map((p) => p.token));
2189
3296
  };
2190
3297
  }
2191
3298
  var defaultResolver = /* @__PURE__ */ createRuleResolver();
@@ -2417,23 +3524,43 @@ function useFieldsetState(config, options = {}) {
2417
3524
  getFieldProps
2418
3525
  };
2419
3526
  }
2420
- function FieldGrid({ columns, gap, className, children }) {
3527
+ var useBrowserLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
3528
+ var GridContext = createContext({ count: 1, tier: "desktop" });
3529
+ function FieldGrid({ columns, minColWidth, gap, className, children }) {
3530
+ const ref = useRef(null);
3531
+ const per = minColWidth && minColWidth > 0 ? minColWidth : DEFAULT_MIN_COL;
3532
+ const { desktop, tablet, mobile } = columns;
3533
+ const [grid, setGrid] = useState(() => resolveGrid(0, columns, per));
3534
+ useBrowserLayoutEffect(() => {
3535
+ const el = ref.current;
3536
+ if (!el) return;
3537
+ const cols = { desktop, tablet, mobile };
3538
+ const apply = (width) => {
3539
+ const next = resolveGrid(width, cols, per);
3540
+ setGrid((cur) => cur.count === next.count && cur.tier === next.tier ? cur : next);
3541
+ };
3542
+ apply(el.getBoundingClientRect().width);
3543
+ if (typeof ResizeObserver === "undefined") return;
3544
+ const ro = new ResizeObserver((entries) => {
3545
+ for (const entry of entries) {
3546
+ const box = entry.contentBoxSize?.[0];
3547
+ apply(box ? box.inlineSize : entry.contentRect.width);
3548
+ }
3549
+ });
3550
+ ro.observe(el);
3551
+ return () => ro.disconnect();
3552
+ }, [desktop, tablet, mobile, per]);
2421
3553
  const style = {
2422
- "--orynn-cols-desktop": columns.desktop,
2423
- "--orynn-cols-tablet": columns.tablet,
2424
- "--orynn-cols-mobile": columns.mobile,
3554
+ "--orynn-cols": grid.count,
2425
3555
  ...gap ? { "--orynn-grid-gap": gap } : {}
2426
3556
  };
2427
- return /* @__PURE__ */ jsx("div", { className: cx("orynn-grid", className), style, children: /* @__PURE__ */ jsx("div", { className: "orynn-grid__track", children }) });
3557
+ 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
3558
  }
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 });
3559
+ function FieldGridItem({ span, children }) {
3560
+ const { count, tier } = useContext(GridContext);
3561
+ const effective = span ? clampSpan(pickSpan(span, tier), count) : 1;
3562
+ if (effective <= 1) return /* @__PURE__ */ jsx(Fragment, { children });
3563
+ return /* @__PURE__ */ jsx("div", { className: "orynn-grid__item", style: { "--orynn-span": effective }, children });
2437
3564
  }
2438
3565
  var noop = () => {
2439
3566
  };
@@ -2443,6 +3570,7 @@ function FieldRendererImpl({ field, registry: registry2, slice, disabled }) {
2443
3570
  registration,
2444
3571
  `No control registered for field type "${field.type}" (field "${field.name}").`
2445
3572
  );
3573
+ const fallbackId = useId();
2446
3574
  if (!registration) return null;
2447
3575
  const Control = registration.component;
2448
3576
  const required = field.validation?.required === true;
@@ -2452,6 +3580,36 @@ function FieldRendererImpl({ field, registry: registry2, slice, disabled }) {
2452
3580
  const handleChange = (next) => {
2453
3581
  slice.setValue(registration.formatValue ? registration.formatValue(next) : next);
2454
3582
  };
3583
+ if (registration.selfContained) {
3584
+ return /* @__PURE__ */ jsx(
3585
+ "div",
3586
+ {
3587
+ style: { display: "contents" },
3588
+ onBlur: (e) => {
3589
+ if (!e.currentTarget.contains(e.relatedTarget)) slice.markTouched();
3590
+ },
3591
+ children: /* @__PURE__ */ jsx(
3592
+ Control,
3593
+ {
3594
+ id: fallbackId,
3595
+ name: field.name,
3596
+ value,
3597
+ onChange: handleChange,
3598
+ onBlur: slice.markTouched,
3599
+ onFocus: noop,
3600
+ disabled: isDisabled,
3601
+ readOnly: field.readOnly === true,
3602
+ required,
3603
+ invalid: messages.length > 0,
3604
+ label: field.label,
3605
+ description: field.description,
3606
+ errors: messages,
3607
+ config: field
3608
+ }
3609
+ )
3610
+ }
3611
+ );
3612
+ }
2455
3613
  return /* @__PURE__ */ jsx(
2456
3614
  Field,
2457
3615
  {
@@ -2484,90 +3642,290 @@ var FieldRenderer = /* @__PURE__ */ memo(
2484
3642
  FieldRendererImpl,
2485
3643
  (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
3644
  );
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;
3645
+ function FieldsetView({ state, registry: registry2, disabled, className, id }) {
3646
+ const activeRegistry = registry2 ?? defaultRegistry;
3647
+ const { columns, minColWidth, gap, legend, fields, id: configId } = state.config;
3648
+ const visible = fields.filter((f) => evalCondition(f.when, state.values));
3649
+ return /* @__PURE__ */ jsxs(
3650
+ "fieldset",
3651
+ {
3652
+ className: cx("orynn-fieldset", className),
3653
+ id: id ?? configId,
3654
+ disabled: disabled || void 0,
3655
+ children: [
3656
+ legend != null && /* @__PURE__ */ jsx("legend", { className: "orynn-fieldset__legend", children: legend }),
3657
+ /* @__PURE__ */ jsx(FieldGrid, { columns, minColWidth, gap, children: visible.map((field) => /* @__PURE__ */ jsx(FieldGridItem, { span: field.layout, children: /* @__PURE__ */ jsx(
3658
+ FieldRenderer,
3659
+ {
3660
+ field,
3661
+ registry: activeRegistry,
3662
+ slice: state.getFieldProps(field.name),
3663
+ disabled: disabled === true
3664
+ }
3665
+ ) }, field.name)) })
3666
+ ]
3667
+ }
3668
+ );
3669
+ }
3670
+ var str = (v) => typeof v === "string" ? v : v == null ? "" : String(v);
3671
+ var toISO = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
3672
+ function shell(p) {
3673
+ const c = p.config;
3674
+ return {
3675
+ id: p.id,
3676
+ name: p.name,
3677
+ label: p.label,
3678
+ description: p.description,
3679
+ error: p.errors,
3680
+ required: p.required,
3681
+ disabled: p.disabled,
3682
+ readOnly: p.readOnly,
3683
+ size: c.size,
3684
+ variant: c.variant,
3685
+ floatingLabel: c.floatingLabel
3686
+ };
3687
+ }
3688
+ var InputControl = (p) => {
3689
+ const c = p.config;
2503
3690
  return /* @__PURE__ */ jsx(
2504
- "input",
3691
+ Input,
2505
3692
  {
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
3693
+ ...shell(p),
3694
+ type: c.inputType ?? "text",
3695
+ prefix: c.prefix,
3696
+ suffix: c.suffix,
3697
+ addonBefore: c.addonBefore,
3698
+ addonAfter: c.addonAfter,
3699
+ clearable: c.clearable,
3700
+ maxLength: c.maxLength,
3701
+ showCount: c.showCount,
3702
+ debounce: c.debounce,
3703
+ inputMode: c.inputMode,
3704
+ pattern: c.pattern,
3705
+ placeholder: c.placeholder,
3706
+ value: str(p.value),
3707
+ onChange: (v) => p.onChange(v),
3708
+ ...c.props
2521
3709
  }
2522
3710
  );
2523
3711
  };
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",
3712
+ var TextareaControl = (p) => {
3713
+ const c = p.config;
3714
+ return /* @__PURE__ */ jsx(
3715
+ Textarea,
2540
3716
  {
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
- ]
3717
+ ...shell(p),
3718
+ minRows: c.rows,
3719
+ maxRows: c.maxRows,
3720
+ autoResize: c.autoResize,
3721
+ resize: c.resize,
3722
+ submitOnEnter: c.submitOnEnter,
3723
+ maxLength: c.maxLength,
3724
+ showCount: c.showCount,
3725
+ placeholder: c.placeholder,
3726
+ value: str(p.value),
3727
+ onChange: (v) => p.onChange(v),
3728
+ ...c.props
2557
3729
  }
2558
3730
  );
2559
3731
  };
2560
- function registerBuiltins(registry2) {
2561
- if (!registry2.has("input")) {
2562
- registry2.register("input", { component: InputControl, defaultValue: "" });
3732
+ var NumberFieldControl = (p) => {
3733
+ const c = p.config;
3734
+ return /* @__PURE__ */ jsx(
3735
+ NumberField,
3736
+ {
3737
+ ...shell(p),
3738
+ min: c.min,
3739
+ max: c.max,
3740
+ step: c.step,
3741
+ precision: c.precision,
3742
+ decimalScale: c.decimalScale,
3743
+ currency: c.currency,
3744
+ style: c.style,
3745
+ locale: c.locale,
3746
+ prefix: c.prefix,
3747
+ suffix: c.suffix,
3748
+ thousandSeparator: c.thousandSeparator,
3749
+ allowNegative: c.allowNegative,
3750
+ clampBehavior: c.clampBehavior,
3751
+ hideControls: c.hideControls,
3752
+ placeholder: c.placeholder,
3753
+ value: typeof p.value === "number" ? p.value : null,
3754
+ onChange: (v) => p.onChange(v),
3755
+ ...c.props
3756
+ }
3757
+ );
3758
+ };
3759
+ var DropdownControl = (p) => {
3760
+ const c = p.config;
3761
+ const value = c.multiple ? Array.isArray(p.value) ? p.value : [] : Array.isArray(p.value) ? p.value[0] ?? "" : str(p.value);
3762
+ return /* @__PURE__ */ jsx(
3763
+ Dropdown,
3764
+ {
3765
+ ...shell(p),
3766
+ options: c.options,
3767
+ searchable: c.searchable,
3768
+ multiple: c.multiple,
3769
+ clearable: c.clearable,
3770
+ creatable: c.creatable,
3771
+ maxValues: c.maxValues,
3772
+ hidePickedOptions: c.hidePickedOptions,
3773
+ showSelectAll: c.showSelectAll,
3774
+ searchDebounce: c.searchDebounce,
3775
+ placeholder: c.placeholder,
3776
+ value,
3777
+ onChange: (v) => p.onChange(v ?? (c.multiple ? [] : "")),
3778
+ ...c.props
3779
+ }
3780
+ );
3781
+ };
3782
+ var DateControl = (p) => {
3783
+ const c = p.config;
3784
+ const toDate2 = (x) => x == null ? void 0 : x instanceof Date ? x : new Date(x);
3785
+ const mode = c.mode ?? "single";
3786
+ const common = {
3787
+ ...shell(p),
3788
+ format: c.format,
3789
+ precision: c.precision,
3790
+ minDate: toDate2(c.minDate),
3791
+ maxDate: toDate2(c.maxDate),
3792
+ showToday: c.showToday,
3793
+ clearable: c.clearable,
3794
+ placeholder: c.placeholder,
3795
+ presets: c.presets,
3796
+ ...c.props
3797
+ };
3798
+ if (mode === "range") {
3799
+ const v = p.value ?? {};
3800
+ return /* @__PURE__ */ jsx(
3801
+ DatePicker,
3802
+ {
3803
+ ...common,
3804
+ mode: "range",
3805
+ value: { start: v.start ?? null, end: v.end ?? null },
3806
+ onChange: (r) => p.onChange({
3807
+ start: r.start ? toISO(r.start) : "",
3808
+ end: r.end ? toISO(r.end) : ""
3809
+ })
3810
+ }
3811
+ );
2563
3812
  }
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
- });
3813
+ if (mode === "multiple") {
3814
+ return /* @__PURE__ */ jsx(
3815
+ DatePicker,
3816
+ {
3817
+ ...common,
3818
+ mode: "multiple",
3819
+ value: Array.isArray(p.value) ? p.value : [],
3820
+ onChange: (arr) => p.onChange(arr.map(toISO))
3821
+ }
3822
+ );
2570
3823
  }
3824
+ return /* @__PURE__ */ jsx(
3825
+ DatePicker,
3826
+ {
3827
+ ...common,
3828
+ value: typeof p.value === "string" && p.value ? p.value : null,
3829
+ onChange: (d) => p.onChange(d instanceof Date ? toISO(d) : "")
3830
+ }
3831
+ );
3832
+ };
3833
+ var CheckboxControl = (p) => {
3834
+ const c = p.config;
3835
+ return /* @__PURE__ */ jsxs("div", { className: "orynn-field", children: [
3836
+ /* @__PURE__ */ jsx(
3837
+ Checkbox,
3838
+ {
3839
+ id: p.id,
3840
+ name: p.name,
3841
+ label: c.checkboxLabel ?? c.label,
3842
+ description: c.description,
3843
+ checked: Boolean(p.value),
3844
+ onChange: (v) => p.onChange(v),
3845
+ disabled: p.disabled,
3846
+ required: p.required,
3847
+ invalid: (p.errors?.length ?? 0) > 0,
3848
+ ...c.props
3849
+ }
3850
+ ),
3851
+ p.errors?.[0] && /* @__PURE__ */ jsx("p", { className: "orynn-field__error", role: "alert", children: p.errors[0] })
3852
+ ] });
3853
+ };
3854
+ var CheckboxGroupControl = (p) => {
3855
+ const c = p.config;
3856
+ return /* @__PURE__ */ jsx(
3857
+ CheckboxGroup,
3858
+ {
3859
+ id: p.id,
3860
+ name: p.name,
3861
+ label: p.label,
3862
+ description: p.description,
3863
+ error: p.errors,
3864
+ required: p.required,
3865
+ disabled: p.disabled,
3866
+ options: c.options.map((o) => ({
3867
+ label: o.label,
3868
+ value: o.value,
3869
+ description: o.description,
3870
+ icon: o.icon,
3871
+ disabled: o.disabled
3872
+ })),
3873
+ orientation: c.orientation,
3874
+ columns: c.columns,
3875
+ showSelectAll: c.showSelectAll,
3876
+ min: c.min,
3877
+ max: c.max,
3878
+ value: Array.isArray(p.value) ? p.value : [],
3879
+ onChange: (v) => p.onChange(v),
3880
+ ...c.props
3881
+ }
3882
+ );
3883
+ };
3884
+ var RadioControl = (p) => {
3885
+ const c = p.config;
3886
+ return /* @__PURE__ */ jsx(
3887
+ RadioGroup,
3888
+ {
3889
+ id: p.id,
3890
+ name: p.name,
3891
+ label: p.label,
3892
+ description: p.description,
3893
+ error: p.errors,
3894
+ required: p.required,
3895
+ disabled: p.disabled,
3896
+ options: c.options.map((o) => ({
3897
+ label: o.label,
3898
+ value: o.value,
3899
+ description: o.description,
3900
+ icon: o.icon,
3901
+ disabled: o.disabled
3902
+ })),
3903
+ orientation: c.orientation,
3904
+ columns: c.columns,
3905
+ variant: c.radioVariant,
3906
+ value: typeof p.value === "string" && p.value ? p.value : null,
3907
+ onChange: (v) => p.onChange(v),
3908
+ ...c.props
3909
+ }
3910
+ );
3911
+ };
3912
+ var needsOptions = (config) => Array.isArray(config.options) ? null : "requires an `options` array";
3913
+ function registerBuiltins(registry2) {
3914
+ const add = (type, component, extra = {}) => {
3915
+ if (!registry2.has(type)) {
3916
+ registry2.register(type, { component, selfContained: true, defaultValue: "", ...extra });
3917
+ }
3918
+ };
3919
+ add("input", InputControl);
3920
+ add("textarea", TextareaControl);
3921
+ add("number", NumberFieldControl, {
3922
+ parseValue: (raw) => typeof raw === "number" ? raw : raw === "" || raw == null ? null : Number(raw)
3923
+ });
3924
+ add("dropdown", DropdownControl, { validateConfig: needsOptions });
3925
+ add("date", DateControl);
3926
+ add("checkbox", CheckboxControl, { defaultValue: false });
3927
+ add("checkbox-group", CheckboxGroupControl, { defaultValue: [], validateConfig: needsOptions });
3928
+ add("radio", RadioControl, { validateConfig: needsOptions });
2571
3929
  }
2572
3930
  var seeded = false;
2573
3931
  function ensureBuiltins(defaultRegistry2) {
@@ -2601,95 +3959,1512 @@ function Fieldset(props) {
2601
3959
  ...validateOn ? { validateOn } : {}
2602
3960
  };
2603
3961
  const fs = useFieldsetState(config, options);
2604
- const { columns, gap, legend, fields, id: configId } = fs.config;
2605
- return /* @__PURE__ */ jsxs(
2606
- "fieldset",
3962
+ return /* @__PURE__ */ jsx(
3963
+ FieldsetView,
2607
3964
  {
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
- ]
3965
+ state: fs,
3966
+ registry: activeRegistry,
3967
+ disabled: disabled === true,
3968
+ className,
3969
+ id
2623
3970
  }
2624
3971
  );
2625
3972
  }
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);
3973
+ function Form(props) {
3974
+ const {
3975
+ config,
3976
+ value,
3977
+ defaultValue,
3978
+ onChange,
3979
+ onSubmit,
3980
+ onInvalid,
3981
+ resolver,
3982
+ validateOn,
3983
+ disabled,
3984
+ children,
3985
+ className,
3986
+ ...rest
3987
+ } = props;
3988
+ ensureBuiltins(defaultRegistry);
3989
+ const fs = useFieldsetState(config, {
3990
+ registry: defaultRegistry,
3991
+ ...value !== void 0 ? { value } : {},
3992
+ ...defaultValue !== void 0 ? { defaultValue } : {},
3993
+ ...onChange ? { onChange } : {},
3994
+ ...resolver ? { resolver } : {},
3995
+ ...validateOn ? { validateOn } : {}
3996
+ });
3997
+ return /* @__PURE__ */ jsxs(
3998
+ "form",
3999
+ {
4000
+ ...rest,
4001
+ className: cx("orynn-form", className),
4002
+ noValidate: true,
4003
+ onSubmit: fs.handleSubmit(
4004
+ (values) => onSubmit(values, fs),
4005
+ (errors) => onInvalid?.(errors)
4006
+ ),
4007
+ children: [
4008
+ /* @__PURE__ */ jsx(FieldsetView, { state: fs, disabled }),
4009
+ typeof children === "function" ? children(fs) : children
4010
+ ]
4011
+ }
4012
+ );
4013
+ }
4014
+ var Accordion = /* @__PURE__ */ forwardRef(
4015
+ function Accordion2({ className, variant = "bordered", size = "md", ...props }, ref) {
4016
+ return /* @__PURE__ */ jsx(
4017
+ Accordion$1.Root,
4018
+ {
4019
+ ref,
4020
+ "data-slot": "accordion",
4021
+ "data-variant": variant,
4022
+ "data-size": size,
4023
+ className: cx("orynn-accordion", className),
4024
+ ...props
4025
+ }
4026
+ );
4027
+ }
4028
+ );
4029
+ var AccordionItem = /* @__PURE__ */ forwardRef(function AccordionItem2({ className, ...props }, ref) {
4030
+ return /* @__PURE__ */ jsx(
4031
+ Accordion$1.Item,
4032
+ {
4033
+ ref,
4034
+ "data-slot": "accordion-item",
4035
+ className: cx("orynn-accordion__item", className),
4036
+ ...props
4037
+ }
4038
+ );
4039
+ });
4040
+ var AccordionTrigger = /* @__PURE__ */ forwardRef(function AccordionTrigger2({ className, children, icon, iconPosition = "end", hideIcon = false, ...props }, ref) {
4041
+ const indicator = hideIcon ? null : /* @__PURE__ */ jsx("span", { className: "orynn-accordion__icon", "data-slot": "accordion-icon", "aria-hidden": "true", children: icon ?? /* @__PURE__ */ jsx(ChevronDownIcon, {}) });
4042
+ return /* @__PURE__ */ jsx(Accordion$1.Header, { className: "orynn-accordion__header", "data-slot": "accordion-header", children: /* @__PURE__ */ jsxs(
4043
+ Accordion$1.Trigger,
4044
+ {
4045
+ ref,
4046
+ "data-slot": "accordion-trigger",
4047
+ "data-icon-position": iconPosition,
4048
+ className: cx("orynn-accordion__trigger", className),
4049
+ ...props,
4050
+ children: [
4051
+ iconPosition === "start" && indicator,
4052
+ /* @__PURE__ */ jsx("span", { className: "orynn-accordion__label", children }),
4053
+ iconPosition === "end" && indicator
4054
+ ]
4055
+ }
4056
+ ) });
4057
+ });
4058
+ var AccordionContent = /* @__PURE__ */ forwardRef(function AccordionContent2({ className, children, ...props }, ref) {
4059
+ return /* @__PURE__ */ jsx(
4060
+ Accordion$1.Content,
4061
+ {
4062
+ ref,
4063
+ "data-slot": "accordion-content",
4064
+ className: cx("orynn-accordion__content", className),
4065
+ ...props,
4066
+ children: /* @__PURE__ */ jsx("div", { className: "orynn-accordion__body", children })
4067
+ }
4068
+ );
4069
+ });
4070
+
4071
+ // src/primitives/shared/classes.ts
4072
+ var mod = (base3, key, fallback) => key === fallback ? void 0 : `${base3}--${key}`;
4073
+ function buttonClasses(variant = "default", size = "default") {
4074
+ return cx(
4075
+ "orynn-button",
4076
+ mod("orynn-button", variant, "default"),
4077
+ mod("orynn-button", size, "default")
4078
+ );
4079
+ }
4080
+ function badgeClasses(variant = "default") {
4081
+ return cx("orynn-badge", mod("orynn-badge", variant, "default"));
4082
+ }
4083
+ var Button = /* @__PURE__ */ forwardRef(function Button2({
4084
+ className,
4085
+ variant = "default",
4086
+ size = "default",
4087
+ asChild = false,
4088
+ type,
4089
+ loading = false,
4090
+ loadingText,
4091
+ leftIcon,
4092
+ rightIcon,
4093
+ fullWidth = false,
4094
+ hoverEffect,
4095
+ focusStyle,
4096
+ selected,
4097
+ cursor,
4098
+ disabled,
4099
+ children,
4100
+ ...props
4101
+ }, ref) {
4102
+ const shared = {
4103
+ "data-slot": "button",
4104
+ "data-variant": variant,
4105
+ "data-size": size,
4106
+ "data-loading": loading || void 0,
4107
+ "data-full-width": fullWidth || void 0,
4108
+ "data-hover-effect": hoverEffect,
4109
+ "data-focus-style": focusStyle,
4110
+ "data-selected": selected || void 0,
4111
+ "data-cursor": cursor,
4112
+ "aria-pressed": selected,
4113
+ className: cx(buttonClasses(variant, size), className),
4114
+ ...props
4115
+ };
4116
+ if (asChild) {
4117
+ return /* @__PURE__ */ jsx(Slot.Root, { ref, "aria-busy": loading || void 0, ...shared, children });
4118
+ }
4119
+ return /* @__PURE__ */ jsxs(
4120
+ "button",
4121
+ {
4122
+ ref,
4123
+ type: type ?? "button",
4124
+ disabled: disabled || loading,
4125
+ "aria-busy": loading || void 0,
4126
+ ...shared,
4127
+ children: [
4128
+ loading ? /* @__PURE__ */ jsx(SpinnerIcon, { className: "orynn-button__spinner" }) : leftIcon,
4129
+ loading && loadingText != null ? loadingText : children,
4130
+ !loading && rightIcon
4131
+ ]
4132
+ }
4133
+ );
4134
+ });
4135
+ var toArray2 = (v) => v == null ? void 0 : Array.isArray(v) ? v : [v];
4136
+ var Slider = /* @__PURE__ */ forwardRef(function Slider2({
4137
+ className,
4138
+ value,
4139
+ defaultValue,
4140
+ min = 0,
4141
+ max = 100,
4142
+ step = 1,
4143
+ size = "md",
4144
+ marks,
4145
+ showTicks = false,
4146
+ tooltip = "none",
4147
+ formatValue,
4148
+ orientation = "horizontal",
4149
+ onValueChange,
4150
+ ...props
4151
+ }, ref) {
4152
+ const controlled = value !== void 0;
4153
+ const initial = toArray2(value) ?? toArray2(defaultValue) ?? [min];
4154
+ const [internal, setInternal] = useState(initial);
4155
+ const current = controlled ? toArray2(value) : internal;
4156
+ const handleChange = (next) => {
4157
+ if (!controlled) setInternal(next);
4158
+ onValueChange?.(next);
4159
+ };
4160
+ const fmt = (n) => formatValue ? formatValue(n) : n;
4161
+ const ticks = useMemo(() => {
4162
+ if (!showTicks) return [];
4163
+ const count = Math.floor((max - min) / step);
4164
+ if (count < 1 || count > 60) return [];
4165
+ return Array.from({ length: count + 1 }, (_, i) => min + i * step);
4166
+ }, [showTicks, min, max, step]);
4167
+ const pct = (n) => (n - min) / (max - min) * 100;
4168
+ return /* @__PURE__ */ jsxs(
4169
+ Slider$1.Root,
4170
+ {
4171
+ ref,
4172
+ "data-slot": "slider",
4173
+ "data-size": size,
4174
+ "data-orientation": orientation,
4175
+ "data-tooltip": tooltip === "none" ? void 0 : tooltip,
4176
+ className: cx("orynn-slider", className),
4177
+ min,
4178
+ max,
4179
+ step,
4180
+ orientation,
4181
+ onValueChange: handleChange,
4182
+ ...controlled ? { value: current } : { defaultValue: initial },
4183
+ ...props,
4184
+ children: [
4185
+ /* @__PURE__ */ jsxs(Slider$1.Track, { "data-slot": "slider-track", className: "orynn-slider__track", children: [
4186
+ /* @__PURE__ */ jsx(Slider$1.Range, { "data-slot": "slider-range", className: "orynn-slider__range" }),
4187
+ ticks.map((t) => /* @__PURE__ */ jsx(
4188
+ "span",
4189
+ {
4190
+ className: "orynn-slider__tick",
4191
+ "aria-hidden": "true",
4192
+ style: { insetInlineStart: `${pct(t)}%` }
4193
+ },
4194
+ `tick-${t}`
4195
+ ))
4196
+ ] }),
4197
+ current.map((v, i) => /* @__PURE__ */ jsx(
4198
+ Slider$1.Thumb,
4199
+ {
4200
+ "data-slot": "slider-thumb",
4201
+ className: "orynn-slider__thumb",
4202
+ "aria-label": props["aria-label"] ?? `Value ${i + 1}`,
4203
+ children: tooltip !== "none" && /* @__PURE__ */ jsx("span", { className: "orynn-slider__tooltip", "data-slot": "slider-tooltip", children: fmt(v) })
4204
+ },
4205
+ `thumb-${i}`
4206
+ )),
4207
+ marks && marks.length > 0 && /* @__PURE__ */ jsx("div", { className: "orynn-slider__marks", "aria-hidden": "true", children: marks.map((m) => /* @__PURE__ */ jsxs(
4208
+ "span",
4209
+ {
4210
+ className: "orynn-slider__mark",
4211
+ style: { insetInlineStart: `${pct(m.value)}%` },
4212
+ children: [
4213
+ /* @__PURE__ */ jsx("span", { className: "orynn-slider__mark-dot" }),
4214
+ m.label != null && /* @__PURE__ */ jsx("span", { className: "orynn-slider__mark-label", children: m.label })
4215
+ ]
4216
+ },
4217
+ `mark-${m.value}`
4218
+ )) })
4219
+ ]
4220
+ }
4221
+ );
4222
+ });
4223
+ var CIRCLE_SIZE = { sm: 40, md: 56, lg: 72 };
4224
+ var CIRCLE_STROKE = { sm: 4, md: 5, lg: 6 };
4225
+ var Progress = /* @__PURE__ */ forwardRef(function Progress2({
4226
+ className,
4227
+ value,
4228
+ max = 100,
4229
+ shape = "line",
4230
+ size = "md",
4231
+ variant = "default",
4232
+ showValue = false,
4233
+ label,
4234
+ striped = false,
4235
+ animated = false,
4236
+ radius,
4237
+ circleThickness,
4238
+ circleSize,
4239
+ formatValue,
4240
+ style,
4241
+ ...props
4242
+ }, ref) {
4243
+ const indeterminate = value == null;
4244
+ const clamped = indeterminate ? 0 : Math.min(max, Math.max(0, value));
4245
+ const pct = max > 0 ? clamped / max * 100 : 0;
4246
+ const text = formatValue ? formatValue(clamped, max) : `${Math.round(pct)}%`;
4247
+ const shared = {
4248
+ ref,
4249
+ "data-slot": "progress",
4250
+ "data-shape": shape,
4251
+ "data-size": size,
4252
+ "data-variant": variant,
4253
+ "data-indeterminate": indeterminate || void 0,
4254
+ value: indeterminate ? null : clamped,
4255
+ max,
4256
+ ...props
4257
+ };
4258
+ if (shape === "circle") {
4259
+ const dim = circleSize ?? CIRCLE_SIZE[size];
4260
+ const stroke = circleThickness ?? CIRCLE_STROKE[size];
4261
+ const r = (dim - stroke) / 2;
4262
+ const circ = 2 * Math.PI * r;
4263
+ const offset = indeterminate ? circ * 0.7 : circ * (1 - pct / 100);
4264
+ return /* @__PURE__ */ jsxs(
4265
+ Progress$1.Root,
4266
+ {
4267
+ ...shared,
4268
+ className: cx("orynn-progress", "orynn-progress--circle", className),
4269
+ style: { inlineSize: dim, blockSize: dim, ...style },
4270
+ children: [
4271
+ /* @__PURE__ */ jsxs("svg", { className: "orynn-progress__svg", viewBox: `0 0 ${dim} ${dim}`, "aria-hidden": "true", children: [
4272
+ /* @__PURE__ */ jsx(
4273
+ "circle",
4274
+ {
4275
+ className: "orynn-progress__ring-track",
4276
+ cx: dim / 2,
4277
+ cy: dim / 2,
4278
+ r,
4279
+ fill: "none",
4280
+ strokeWidth: stroke
4281
+ }
4282
+ ),
4283
+ /* @__PURE__ */ jsx(
4284
+ "circle",
4285
+ {
4286
+ className: "orynn-progress__ring",
4287
+ cx: dim / 2,
4288
+ cy: dim / 2,
4289
+ r,
4290
+ fill: "none",
4291
+ strokeWidth: stroke,
4292
+ strokeLinecap: "round",
4293
+ strokeDasharray: circ,
4294
+ strokeDashoffset: offset
4295
+ }
4296
+ )
4297
+ ] }),
4298
+ showValue && !indeterminate && /* @__PURE__ */ jsx("span", { className: "orynn-progress__circle-value", children: text })
4299
+ ]
4300
+ }
4301
+ );
4302
+ }
4303
+ return /* @__PURE__ */ jsxs("div", { className: "orynn-progress__wrap", children: [
4304
+ (label != null || showValue && !indeterminate) && /* @__PURE__ */ jsxs("div", { className: "orynn-progress__meta", children: [
4305
+ label != null && /* @__PURE__ */ jsx("span", { className: "orynn-progress__label", children: label }),
4306
+ showValue && !indeterminate && /* @__PURE__ */ jsx("span", { className: "orynn-progress__value", children: text })
4307
+ ] }),
4308
+ /* @__PURE__ */ jsx(
4309
+ Progress$1.Root,
4310
+ {
4311
+ ...shared,
4312
+ "data-striped": striped || animated || void 0,
4313
+ "data-animated": animated || void 0,
4314
+ className: cx("orynn-progress", className),
4315
+ style: { borderRadius: radius != null ? radius : void 0, ...style },
4316
+ children: /* @__PURE__ */ jsx(
4317
+ Progress$1.Indicator,
4318
+ {
4319
+ className: "orynn-progress__bar",
4320
+ "data-slot": "progress-bar",
4321
+ style: {
4322
+ transform: indeterminate ? void 0 : `translateX(-${100 - pct}%)`
4323
+ }
4324
+ }
4325
+ )
4326
+ }
4327
+ )
4328
+ ] });
4329
+ });
4330
+ var toasts = [];
4331
+ var listeners = /* @__PURE__ */ new Set();
4332
+ var counter = 0;
4333
+ var emit = () => {
4334
+ for (const l of listeners) l(toasts);
4335
+ };
4336
+ var subscribe = (l) => {
4337
+ listeners.add(l);
4338
+ return () => {
4339
+ listeners.delete(l);
4340
+ };
4341
+ };
4342
+ var getSnapshot = () => toasts;
4343
+ var upsert = (record) => {
4344
+ const i = toasts.findIndex((t) => t.id === record.id);
4345
+ toasts = i === -1 ? [...toasts, record] : toasts.map((t) => t.id === record.id ? record : t);
4346
+ emit();
4347
+ };
4348
+ var dismissToast = (id) => {
4349
+ toasts = id == null ? [] : toasts.filter((t) => t.id !== id);
4350
+ emit();
4351
+ };
4352
+ var isMessage = (v) => typeof v === "string" || typeof v === "number" || isValidElement(v);
4353
+ var normalize = (input, extra) => isMessage(input) ? { title: input, ...extra } : { ...input, ...extra };
4354
+ function base2(input, extra) {
4355
+ const opts = normalize(input, extra);
4356
+ const id = opts.id ?? `toast-${++counter}`;
4357
+ upsert({ ...opts, id, createdAt: Date.now() });
4358
+ return id;
4359
+ }
4360
+ var withVariant = (variant) => (message, opts) => base2(message, { ...opts, variant });
4361
+ var toast = base2;
4362
+ toast.success = withVariant("success");
4363
+ toast.error = withVariant("error");
4364
+ toast.warning = withVariant("warning");
4365
+ toast.info = withVariant("info");
4366
+ toast.loading = withVariant("loading");
4367
+ toast.dismiss = dismissToast;
4368
+ toast.promise = (promise, msgs, opts) => {
4369
+ const id = base2(msgs.loading, {
4370
+ ...opts,
4371
+ variant: "loading",
4372
+ duration: Number.POSITIVE_INFINITY
4373
+ });
4374
+ promise.then(
4375
+ (value) => {
4376
+ const m = typeof msgs.success === "function" ? msgs.success(value) : msgs.success;
4377
+ base2(m, { ...opts, id, variant: "success", duration: opts?.duration });
4378
+ },
4379
+ (err) => {
4380
+ const m = typeof msgs.error === "function" ? msgs.error(err) : msgs.error;
4381
+ base2(m, { ...opts, id, variant: "error", duration: opts?.duration });
4382
+ }
4383
+ );
4384
+ return promise;
4385
+ };
4386
+ function AngleIcon() {
4387
+ return /* @__PURE__ */ jsx("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
4388
+ "path",
4389
+ {
4390
+ d: "M12 8v5M12 16h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z",
4391
+ stroke: "currentColor",
4392
+ strokeWidth: "2",
4393
+ strokeLinecap: "round",
4394
+ strokeLinejoin: "round"
4395
+ }
4396
+ ) });
4397
+ }
4398
+ function InfoIcon() {
4399
+ return /* @__PURE__ */ jsxs("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
4400
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }),
4401
+ /* @__PURE__ */ jsx("path", { d: "M12 11v5M12 8h.01", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
4402
+ ] });
4403
+ }
4404
+ function CircleXIcon() {
4405
+ return /* @__PURE__ */ jsxs("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
4406
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }),
4407
+ /* @__PURE__ */ jsx("path", { d: "m15 9-6 6M9 9l6 6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
4408
+ ] });
4409
+ }
4410
+ var VARIANT_ICON = {
4411
+ default: null,
4412
+ success: /* @__PURE__ */ jsx(CheckIcon, {}),
4413
+ error: /* @__PURE__ */ jsx(CircleXIcon, {}),
4414
+ warning: /* @__PURE__ */ jsx(AngleIcon, {}),
4415
+ info: /* @__PURE__ */ jsx(InfoIcon, {}),
4416
+ loading: /* @__PURE__ */ jsx(SpinnerIcon, { className: "orynn-spin" })
4417
+ };
4418
+ var SWIPE = {
4419
+ "top-left": "left",
4420
+ "top-center": "up",
4421
+ "top-right": "right",
4422
+ "bottom-left": "left",
4423
+ "bottom-center": "down",
4424
+ "bottom-right": "right"
4425
+ };
4426
+ function ToastItem({
4427
+ record,
4428
+ duration,
4429
+ closeButton
4430
+ }) {
4431
+ const variant = record.variant ?? "default";
4432
+ const icon = record.icon === null ? null : record.icon ?? VARIANT_ICON[variant];
4433
+ const showClose = record.dismissible ?? closeButton;
4434
+ const d = record.duration ?? (variant === "loading" ? Number.POSITIVE_INFINITY : duration);
4435
+ return /* @__PURE__ */ jsxs(
4436
+ Toast.Root,
4437
+ {
4438
+ "data-slot": "toast",
4439
+ "data-variant": variant,
4440
+ className: "orynn-toast",
4441
+ duration: Number.isFinite(d) ? d : 1e9,
4442
+ onOpenChange: (open) => {
4443
+ record.onOpenChange?.(open);
4444
+ if (!open) dismissToast(record.id);
4445
+ },
4446
+ children: [
4447
+ icon != null && /* @__PURE__ */ jsx("span", { className: "orynn-toast__icon", "data-slot": "toast-icon", "aria-hidden": "true", children: icon }),
4448
+ /* @__PURE__ */ jsxs("div", { className: "orynn-toast__content", children: [
4449
+ record.title != null && /* @__PURE__ */ jsx(Toast.Title, { className: "orynn-toast__title", children: record.title }),
4450
+ record.description != null && /* @__PURE__ */ jsx(Toast.Description, { className: "orynn-toast__description", children: record.description })
4451
+ ] }),
4452
+ record.action && /* @__PURE__ */ jsx(
4453
+ Toast.Action,
4454
+ {
4455
+ className: "orynn-toast__action",
4456
+ altText: record.action.altText ?? (typeof record.action.label === "string" ? record.action.label : "Action"),
4457
+ onClick: record.action.onClick,
4458
+ children: record.action.label
4459
+ }
4460
+ ),
4461
+ showClose && /* @__PURE__ */ jsx(
4462
+ Toast.Close,
4463
+ {
4464
+ className: "orynn-toast__close",
4465
+ "aria-label": "Dismiss",
4466
+ "data-slot": "toast-close",
4467
+ children: /* @__PURE__ */ jsx(XIcon, {})
4468
+ }
4469
+ )
4470
+ ]
4471
+ }
4472
+ );
4473
+ }
4474
+ function Toaster({
4475
+ position = "bottom-right",
4476
+ duration = 4e3,
4477
+ visibleToasts = 4,
4478
+ closeButton = true,
4479
+ richColors = false,
4480
+ gap = 12,
4481
+ offset = 24,
4482
+ className
4483
+ }) {
4484
+ const records = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
4485
+ const shown = records.slice(-visibleToasts);
4486
+ const [side] = position.split("-");
4487
+ return /* @__PURE__ */ jsxs(Toast.Provider, { swipeDirection: SWIPE[position], duration, children: [
4488
+ shown.map((record) => /* @__PURE__ */ jsx(ToastItem, { record, duration, closeButton }, record.id)),
4489
+ /* @__PURE__ */ jsx(
4490
+ Toast.Viewport,
4491
+ {
4492
+ "data-slot": "toaster",
4493
+ "data-position": position,
4494
+ "data-rich-colors": richColors || void 0,
4495
+ className: cx("orynn-toaster", className),
4496
+ style: {
4497
+ "--orynn-toaster-gap": `${gap}px`,
4498
+ "--orynn-toaster-offset": `${offset}px`,
4499
+ [side]: `${offset}px`
4500
+ }
4501
+ }
4502
+ )
4503
+ ] });
4504
+ }
4505
+ function useToast() {
4506
+ const toasts2 = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
4507
+ return { toast, dismiss: dismissToast, toasts: toasts2 };
4508
+ }
4509
+
4510
+ // src/primitives/Pagination/use-pagination.ts
4511
+ var range = (start, end) => Array.from({ length: Math.max(0, end - start + 1) }, (_, i) => start + i);
4512
+ function usePagination({
4513
+ page,
4514
+ count,
4515
+ total,
4516
+ pageSize = 10,
4517
+ siblingCount = 1,
4518
+ boundaryCount = 1
4519
+ }) {
4520
+ const pageCount = Math.max(
4521
+ 1,
4522
+ count ?? (total != null ? Math.ceil(total / Math.max(1, pageSize)) : 1)
4523
+ );
4524
+ const current = Math.min(Math.max(1, page), pageCount);
4525
+ const startPages = range(1, Math.min(boundaryCount, pageCount));
4526
+ const endPages = range(Math.max(pageCount - boundaryCount + 1, boundaryCount + 1), pageCount);
4527
+ const siblingsStart = Math.max(
4528
+ Math.min(current - siblingCount, pageCount - boundaryCount - siblingCount * 2 - 1),
4529
+ boundaryCount + 2
4530
+ );
4531
+ const siblingsEnd = Math.min(
4532
+ Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),
4533
+ endPages.length > 0 ? endPages[0] - 2 : pageCount - 1
4534
+ );
4535
+ const slots = [
4536
+ ...startPages,
4537
+ ...siblingsStart > boundaryCount + 2 ? ["ellipsis-start"] : boundaryCount + 1 < pageCount - boundaryCount ? [boundaryCount + 1] : [],
4538
+ ...range(siblingsStart, siblingsEnd),
4539
+ ...siblingsEnd < pageCount - boundaryCount - 1 ? ["ellipsis-end"] : pageCount - boundaryCount > boundaryCount ? [pageCount - boundaryCount] : [],
4540
+ ...endPages
4541
+ ];
4542
+ return {
4543
+ pageCount,
4544
+ page: current,
4545
+ hasPrev: current > 1,
4546
+ hasNext: current < pageCount,
4547
+ slots
4548
+ };
4549
+ }
4550
+ var PaginationContent = /* @__PURE__ */ forwardRef(
4551
+ function PaginationContent2({ className, ...props }, ref) {
4552
+ return /* @__PURE__ */ jsx(
4553
+ "ul",
4554
+ {
4555
+ ref,
4556
+ "data-slot": "pagination-content",
4557
+ className: cx("orynn-pagination__list", className),
4558
+ ...props
4559
+ }
4560
+ );
4561
+ }
4562
+ );
4563
+ var PaginationItem = /* @__PURE__ */ forwardRef(
4564
+ function PaginationItem2({ className, ...props }, ref) {
4565
+ return /* @__PURE__ */ jsx(
4566
+ "li",
4567
+ {
4568
+ ref,
4569
+ "data-slot": "pagination-item",
4570
+ className: cx("orynn-pagination__item", className),
4571
+ ...props
4572
+ }
4573
+ );
4574
+ }
4575
+ );
4576
+ var PaginationLink = /* @__PURE__ */ forwardRef(
4577
+ function PaginationLink2({ className, isActive, size = "md", type, ...props }, ref) {
4578
+ return /* @__PURE__ */ jsx(
4579
+ "button",
4580
+ {
4581
+ ref,
4582
+ type: type ?? "button",
4583
+ "data-slot": "pagination-link",
4584
+ "data-active": isActive || void 0,
4585
+ "data-size": size,
4586
+ "aria-current": isActive ? "page" : void 0,
4587
+ className: cx("orynn-pagination__link", className),
4588
+ ...props
4589
+ }
4590
+ );
4591
+ }
4592
+ );
4593
+ var PaginationEllipsis = /* @__PURE__ */ forwardRef(function PaginationEllipsis2({ className, children, ...props }, ref) {
4594
+ return /* @__PURE__ */ jsx(
4595
+ "span",
4596
+ {
4597
+ ref,
4598
+ "aria-hidden": "true",
4599
+ "data-slot": "pagination-ellipsis",
4600
+ className: cx("orynn-pagination__ellipsis", className),
4601
+ ...props,
4602
+ children: children ?? "\u2026"
4603
+ }
4604
+ );
4605
+ });
4606
+ var Pagination = /* @__PURE__ */ forwardRef(
4607
+ function Pagination2({
4608
+ className,
4609
+ page,
4610
+ count,
4611
+ total,
4612
+ pageSize,
4613
+ siblingCount,
4614
+ boundaryCount,
4615
+ onPageChange,
4616
+ showFirstLast = false,
4617
+ showPrevNext = true,
4618
+ iconOnly = false,
4619
+ size = "md",
4620
+ variant = "outline",
4621
+ disabled = false,
4622
+ labels,
4623
+ prevIcon,
4624
+ nextIcon,
4625
+ ...props
4626
+ }, ref) {
4627
+ const model = usePagination({ page, count, total, pageSize, siblingCount, boundaryCount });
4628
+ const L = {
4629
+ previous: labels?.previous ?? "Previous",
4630
+ next: labels?.next ?? "Next",
4631
+ first: labels?.first ?? "First page",
4632
+ last: labels?.last ?? "Last page",
4633
+ ariaLabel: labels?.ariaLabel ?? "Pagination",
4634
+ page: labels?.page ?? ((n) => `Page ${n}`)
4635
+ };
4636
+ const go = (p) => {
4637
+ if (disabled) return;
4638
+ const next = Math.min(Math.max(1, p), model.pageCount);
4639
+ if (next !== model.page) onPageChange(next);
4640
+ };
4641
+ return /* @__PURE__ */ jsx(
4642
+ "nav",
4643
+ {
4644
+ ref,
4645
+ "aria-label": L.ariaLabel,
4646
+ "data-slot": "pagination",
4647
+ "data-size": size,
4648
+ "data-variant": variant,
4649
+ "data-disabled": disabled || void 0,
4650
+ className: cx("orynn-pagination", className),
4651
+ ...props,
4652
+ children: /* @__PURE__ */ jsxs(PaginationContent, { children: [
4653
+ showFirstLast && /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsxs(
4654
+ PaginationLink,
4655
+ {
4656
+ size,
4657
+ "aria-label": L.first,
4658
+ disabled: disabled || !model.hasPrev,
4659
+ onClick: () => go(1),
4660
+ children: [
4661
+ /* @__PURE__ */ jsx(ChevronLeftIcon, {}),
4662
+ /* @__PURE__ */ jsx(ChevronLeftIcon, { style: { marginInlineStart: "-0.65em" } })
4663
+ ]
4664
+ }
4665
+ ) }),
4666
+ showPrevNext && /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsxs(
4667
+ PaginationLink,
4668
+ {
4669
+ size,
4670
+ "aria-label": L.previous,
4671
+ disabled: disabled || !model.hasPrev,
4672
+ className: "orynn-pagination__nav",
4673
+ onClick: () => go(model.page - 1),
4674
+ children: [
4675
+ prevIcon ?? /* @__PURE__ */ jsx(ChevronLeftIcon, {}),
4676
+ !iconOnly && /* @__PURE__ */ jsx("span", { className: "orynn-pagination__nav-label", children: L.previous })
4677
+ ]
4678
+ }
4679
+ ) }),
4680
+ model.slots.map(
4681
+ (slot) => typeof slot === "number" ? /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(
4682
+ PaginationLink,
4683
+ {
4684
+ size,
4685
+ isActive: slot === model.page,
4686
+ "aria-label": L.page(slot),
4687
+ disabled,
4688
+ onClick: () => go(slot),
4689
+ children: slot
4690
+ }
4691
+ ) }, slot) : /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsx(PaginationEllipsis, {}) }, slot)
4692
+ ),
4693
+ showPrevNext && /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsxs(
4694
+ PaginationLink,
4695
+ {
4696
+ size,
4697
+ "aria-label": L.next,
4698
+ disabled: disabled || !model.hasNext,
4699
+ className: "orynn-pagination__nav",
4700
+ onClick: () => go(model.page + 1),
4701
+ children: [
4702
+ !iconOnly && /* @__PURE__ */ jsx("span", { className: "orynn-pagination__nav-label", children: L.next }),
4703
+ nextIcon ?? /* @__PURE__ */ jsx(ChevronRightIcon, {})
4704
+ ]
4705
+ }
4706
+ ) }),
4707
+ showFirstLast && /* @__PURE__ */ jsx(PaginationItem, { children: /* @__PURE__ */ jsxs(
4708
+ PaginationLink,
4709
+ {
4710
+ size,
4711
+ "aria-label": L.last,
4712
+ disabled: disabled || !model.hasNext,
4713
+ onClick: () => go(model.pageCount),
4714
+ children: [
4715
+ /* @__PURE__ */ jsx(ChevronRightIcon, {}),
4716
+ /* @__PURE__ */ jsx(ChevronRightIcon, { style: { marginInlineStart: "-0.65em" } })
4717
+ ]
4718
+ }
4719
+ ) })
4720
+ ] })
4721
+ }
4722
+ );
4723
+ }
4724
+ );
4725
+ var Label = /* @__PURE__ */ forwardRef(function Label2({ className, required, optional, children, ...props }, ref) {
4726
+ return /* @__PURE__ */ jsxs(
4727
+ Label$1.Root,
4728
+ {
4729
+ ref,
4730
+ "data-slot": "label",
4731
+ className: cx("orynn-label", className),
4732
+ ...props,
4733
+ children: [
4734
+ children,
4735
+ required && /* @__PURE__ */ jsx("span", { className: "orynn-label__required", "aria-hidden": "true", children: "*" }),
4736
+ !required && optional && /* @__PURE__ */ jsx("span", { className: "orynn-label__optional", children: optional === true ? "(optional)" : optional })
4737
+ ]
4738
+ }
4739
+ );
4740
+ });
4741
+ var Card = /* @__PURE__ */ forwardRef(function Card2({ className, asChild = false, interactive = false, ...props }, ref) {
4742
+ const shared = {
4743
+ "data-slot": "card",
4744
+ "data-interactive": interactive || void 0,
4745
+ className: cx("orynn-card", className),
4746
+ ...props
4747
+ };
4748
+ return asChild ? /* @__PURE__ */ jsx(Slot.Root, { ref, ...shared }) : /* @__PURE__ */ jsx("div", { ref, ...shared });
4749
+ });
4750
+ var CardHeader = /* @__PURE__ */ forwardRef(function CardHeader2({ className, ...props }, ref) {
4751
+ return /* @__PURE__ */ jsx(
4752
+ "div",
4753
+ {
4754
+ ref,
4755
+ "data-slot": "card-header",
4756
+ className: cx("orynn-card__header", className),
4757
+ ...props
4758
+ }
4759
+ );
4760
+ });
4761
+ var CardTitle = /* @__PURE__ */ forwardRef(function CardTitle2({ className, ...props }, ref) {
4762
+ return /* @__PURE__ */ jsx(
4763
+ "div",
4764
+ {
4765
+ ref,
4766
+ "data-slot": "card-title",
4767
+ className: cx("orynn-card__title", className),
4768
+ ...props
4769
+ }
4770
+ );
4771
+ });
4772
+ var CardDescription = /* @__PURE__ */ forwardRef(
4773
+ function CardDescription2({ className, ...props }, ref) {
4774
+ return /* @__PURE__ */ jsx(
4775
+ "div",
4776
+ {
4777
+ ref,
4778
+ "data-slot": "card-description",
4779
+ className: cx("orynn-card__description", className),
4780
+ ...props
4781
+ }
4782
+ );
4783
+ }
4784
+ );
4785
+ var CardAction = /* @__PURE__ */ forwardRef(function CardAction2({ className, ...props }, ref) {
4786
+ return /* @__PURE__ */ jsx(
4787
+ "div",
4788
+ {
4789
+ ref,
4790
+ "data-slot": "card-action",
4791
+ className: cx("orynn-card__action", className),
4792
+ ...props
4793
+ }
4794
+ );
4795
+ });
4796
+ var CardContent = /* @__PURE__ */ forwardRef(
4797
+ function CardContent2({ className, ...props }, ref) {
4798
+ return /* @__PURE__ */ jsx(
4799
+ "div",
4800
+ {
4801
+ ref,
4802
+ "data-slot": "card-content",
4803
+ className: cx("orynn-card__content", className),
4804
+ ...props
4805
+ }
4806
+ );
4807
+ }
4808
+ );
4809
+ var CardFooter = /* @__PURE__ */ forwardRef(function CardFooter2({ className, ...props }, ref) {
4810
+ return /* @__PURE__ */ jsx(
4811
+ "div",
4812
+ {
4813
+ ref,
4814
+ "data-slot": "card-footer",
4815
+ className: cx("orynn-card__footer", className),
4816
+ ...props
4817
+ }
4818
+ );
4819
+ });
4820
+ var Badge = /* @__PURE__ */ forwardRef(function Badge2({
4821
+ className,
4822
+ variant = "default",
4823
+ size = "md",
4824
+ dot = false,
4825
+ icon,
4826
+ removable = false,
4827
+ onRemove,
4828
+ asChild = false,
4829
+ children,
4830
+ ...props
4831
+ }, ref) {
4832
+ const shared = {
4833
+ "data-slot": "badge",
4834
+ "data-variant": variant,
4835
+ "data-size": size,
4836
+ className: cx(badgeClasses(variant), className),
4837
+ ...props
4838
+ };
4839
+ if (asChild) {
4840
+ return /* @__PURE__ */ jsx(Slot.Root, { ref, ...shared, children });
4841
+ }
4842
+ return /* @__PURE__ */ jsxs("span", { ref, ...shared, children: [
4843
+ dot && /* @__PURE__ */ jsx("span", { className: "orynn-badge__dot", "aria-hidden": "true" }),
4844
+ icon,
4845
+ children,
4846
+ removable && /* @__PURE__ */ jsx(
4847
+ "button",
4848
+ {
4849
+ type: "button",
4850
+ className: "orynn-badge__remove",
4851
+ "aria-label": "Remove",
4852
+ onClick: onRemove,
4853
+ children: /* @__PURE__ */ jsx(XIcon, {})
4854
+ }
4855
+ )
4856
+ ] });
4857
+ });
4858
+ var Alert = /* @__PURE__ */ forwardRef(function Alert2({ className, variant = "default", icon, title, dismissible, onDismiss, children, ...props }, ref) {
4859
+ return /* @__PURE__ */ jsxs(
4860
+ "div",
4861
+ {
4862
+ ref,
4863
+ "data-slot": "alert",
4864
+ "data-variant": variant,
4865
+ "data-dismissible": dismissible || void 0,
4866
+ role: "alert",
4867
+ className: cx("orynn-alert", variant !== "default" && `orynn-alert--${variant}`, className),
4868
+ ...props,
4869
+ children: [
4870
+ icon,
4871
+ title != null && /* @__PURE__ */ jsx(AlertTitle, { children: title }),
4872
+ children,
4873
+ dismissible && /* @__PURE__ */ jsx(
4874
+ "button",
4875
+ {
4876
+ type: "button",
4877
+ className: "orynn-alert__dismiss",
4878
+ "aria-label": "Dismiss",
4879
+ onClick: onDismiss,
4880
+ children: /* @__PURE__ */ jsx(XIcon, {})
4881
+ }
4882
+ )
4883
+ ]
4884
+ }
4885
+ );
4886
+ });
4887
+ var AlertTitle = /* @__PURE__ */ forwardRef(
4888
+ function AlertTitle2({ className, ...props }, ref) {
4889
+ return /* @__PURE__ */ jsx(
4890
+ "div",
4891
+ {
4892
+ ref,
4893
+ "data-slot": "alert-title",
4894
+ className: cx("orynn-alert__title", className),
4895
+ ...props
4896
+ }
4897
+ );
2662
4898
  }
2663
- Object.assign(vars, config.vars ?? {});
2664
- return vars;
4899
+ );
4900
+ var AlertDescription = /* @__PURE__ */ forwardRef(
4901
+ function AlertDescription2({ className, ...props }, ref) {
4902
+ return /* @__PURE__ */ jsx(
4903
+ "div",
4904
+ {
4905
+ ref,
4906
+ "data-slot": "alert-description",
4907
+ className: cx("orynn-alert__description", className),
4908
+ ...props
4909
+ }
4910
+ );
4911
+ }
4912
+ );
4913
+ var Separator = /* @__PURE__ */ forwardRef(
4914
+ function Separator2({
4915
+ className,
4916
+ orientation = "horizontal",
4917
+ decorative = true,
4918
+ variant = "solid",
4919
+ children,
4920
+ ...props
4921
+ }, ref) {
4922
+ if (children != null && orientation === "horizontal") {
4923
+ return /* @__PURE__ */ jsx(
4924
+ "div",
4925
+ {
4926
+ ref,
4927
+ role: "separator",
4928
+ "aria-orientation": "horizontal",
4929
+ "data-slot": "separator",
4930
+ "data-orientation": "horizontal",
4931
+ "data-variant": variant,
4932
+ className: cx("orynn-separator", "orynn-separator--labelled", className),
4933
+ ...props,
4934
+ children: /* @__PURE__ */ jsx("span", { className: "orynn-separator__label", children })
4935
+ }
4936
+ );
4937
+ }
4938
+ return /* @__PURE__ */ jsx(
4939
+ Separator$1.Root,
4940
+ {
4941
+ ref,
4942
+ "data-slot": "separator",
4943
+ "data-variant": variant,
4944
+ orientation,
4945
+ decorative,
4946
+ className: cx("orynn-separator", className),
4947
+ ...props
4948
+ }
4949
+ );
4950
+ }
4951
+ );
4952
+ var Table = /* @__PURE__ */ forwardRef(function Table2({ className, size, dense, ...props }, ref) {
4953
+ return /* @__PURE__ */ jsx("div", { "data-slot": "table-container", className: "orynn-table__wrap", children: /* @__PURE__ */ jsx(
4954
+ "table",
4955
+ {
4956
+ ref,
4957
+ "data-slot": "table",
4958
+ "data-size": dense ? "sm" : size,
4959
+ className: cx("orynn-table", className),
4960
+ ...props
4961
+ }
4962
+ ) });
4963
+ });
4964
+ var TableHeader = /* @__PURE__ */ forwardRef(
4965
+ function TableHeader2({ className, sticky, ...props }, ref) {
4966
+ return /* @__PURE__ */ jsx(
4967
+ "thead",
4968
+ {
4969
+ ref,
4970
+ "data-slot": "table-header",
4971
+ "data-sticky": sticky || void 0,
4972
+ className: cx("orynn-table__header", className),
4973
+ ...props
4974
+ }
4975
+ );
4976
+ }
4977
+ );
4978
+ var TableBody = /* @__PURE__ */ forwardRef(function TableBody2({ className, ...props }, ref) {
4979
+ return /* @__PURE__ */ jsx(
4980
+ "tbody",
4981
+ {
4982
+ ref,
4983
+ "data-slot": "table-body",
4984
+ className: cx("orynn-table__body", className),
4985
+ ...props
4986
+ }
4987
+ );
4988
+ });
4989
+ var TableFooter = /* @__PURE__ */ forwardRef(function TableFooter2({ className, ...props }, ref) {
4990
+ return /* @__PURE__ */ jsx(
4991
+ "tfoot",
4992
+ {
4993
+ ref,
4994
+ "data-slot": "table-footer",
4995
+ className: cx("orynn-table__footer", className),
4996
+ ...props
4997
+ }
4998
+ );
4999
+ });
5000
+ var TableRow = /* @__PURE__ */ forwardRef(
5001
+ function TableRow2({ className, selected, ...props }, ref) {
5002
+ return /* @__PURE__ */ jsx(
5003
+ "tr",
5004
+ {
5005
+ ref,
5006
+ "data-slot": "table-row",
5007
+ "data-state": selected ? "selected" : void 0,
5008
+ className: cx("orynn-table__row", className),
5009
+ ...props
5010
+ }
5011
+ );
5012
+ }
5013
+ );
5014
+ var TableHead = /* @__PURE__ */ forwardRef(
5015
+ function TableHead2({ className, ...props }, ref) {
5016
+ return /* @__PURE__ */ jsx(
5017
+ "th",
5018
+ {
5019
+ ref,
5020
+ "data-slot": "table-head",
5021
+ className: cx("orynn-table__head", className),
5022
+ ...props
5023
+ }
5024
+ );
5025
+ }
5026
+ );
5027
+ var TableCell = /* @__PURE__ */ forwardRef(
5028
+ function TableCell2({ className, ...props }, ref) {
5029
+ return /* @__PURE__ */ jsx(
5030
+ "td",
5031
+ {
5032
+ ref,
5033
+ "data-slot": "table-cell",
5034
+ className: cx("orynn-table__cell", className),
5035
+ ...props
5036
+ }
5037
+ );
5038
+ }
5039
+ );
5040
+ var TableCaption = /* @__PURE__ */ forwardRef(function TableCaption2({ className, ...props }, ref) {
5041
+ return /* @__PURE__ */ jsx(
5042
+ "caption",
5043
+ {
5044
+ ref,
5045
+ "data-slot": "table-caption",
5046
+ className: cx("orynn-table__caption", className),
5047
+ ...props
5048
+ }
5049
+ );
5050
+ });
5051
+ var Switch = /* @__PURE__ */ forwardRef(function Switch2({
5052
+ className,
5053
+ size = "default",
5054
+ label,
5055
+ description,
5056
+ labelPlacement = "end",
5057
+ onLabel,
5058
+ offLabel,
5059
+ thumbIcon,
5060
+ id: idProp,
5061
+ ...props
5062
+ }, ref) {
5063
+ const reactId = useId();
5064
+ const id = idProp ?? reactId;
5065
+ const control = /* @__PURE__ */ jsxs(
5066
+ Switch$1.Root,
5067
+ {
5068
+ ref,
5069
+ id,
5070
+ "data-slot": "switch",
5071
+ "data-size": size,
5072
+ className: cx("orynn-switch", className),
5073
+ ...props,
5074
+ children: [
5075
+ offLabel != null && /* @__PURE__ */ jsx("span", { className: "orynn-switch__text orynn-switch__text--off", "aria-hidden": "true", children: offLabel }),
5076
+ onLabel != null && /* @__PURE__ */ jsx("span", { className: "orynn-switch__text orynn-switch__text--on", "aria-hidden": "true", children: onLabel }),
5077
+ /* @__PURE__ */ jsx(Switch$1.Thumb, { "data-slot": "switch-thumb", className: "orynn-switch__thumb", children: thumbIcon })
5078
+ ]
5079
+ }
5080
+ );
5081
+ if (label == null && description == null) return control;
5082
+ return /* @__PURE__ */ jsxs(
5083
+ "label",
5084
+ {
5085
+ className: "orynn-switch-field",
5086
+ "data-slot": "switch-field",
5087
+ "data-placement": labelPlacement,
5088
+ htmlFor: id,
5089
+ children: [
5090
+ control,
5091
+ /* @__PURE__ */ jsxs("span", { className: "orynn-switch-field__text", children: [
5092
+ /* @__PURE__ */ jsx("span", { className: "orynn-switch-field__label", children: label }),
5093
+ description != null && /* @__PURE__ */ jsx("span", { className: "orynn-switch-field__description", children: description })
5094
+ ] })
5095
+ ]
5096
+ }
5097
+ );
5098
+ });
5099
+ var Tabs = /* @__PURE__ */ forwardRef(function Tabs2({ className, variant = "pill", size = "md", fitted = false, ...props }, ref) {
5100
+ return /* @__PURE__ */ jsx(
5101
+ Tabs$1.Root,
5102
+ {
5103
+ ref,
5104
+ "data-slot": "tabs",
5105
+ "data-variant": variant,
5106
+ "data-size": size,
5107
+ "data-fitted": fitted || void 0,
5108
+ className: cx("orynn-tabs", className),
5109
+ ...props
5110
+ }
5111
+ );
5112
+ });
5113
+ var TabsList = /* @__PURE__ */ forwardRef(function TabsList2({ className, ...props }, ref) {
5114
+ return /* @__PURE__ */ jsx(
5115
+ Tabs$1.List,
5116
+ {
5117
+ ref,
5118
+ "data-slot": "tabs-list",
5119
+ className: cx("orynn-tabs__list", className),
5120
+ ...props
5121
+ }
5122
+ );
5123
+ });
5124
+ var TabsTrigger = /* @__PURE__ */ forwardRef(function TabsTrigger2({ className, ...props }, ref) {
5125
+ return /* @__PURE__ */ jsx(
5126
+ Tabs$1.Trigger,
5127
+ {
5128
+ ref,
5129
+ "data-slot": "tabs-trigger",
5130
+ className: cx("orynn-tabs__trigger", className),
5131
+ ...props
5132
+ }
5133
+ );
5134
+ });
5135
+ var TabsContent = /* @__PURE__ */ forwardRef(function TabsContent2({ className, ...props }, ref) {
5136
+ return /* @__PURE__ */ jsx(
5137
+ Tabs$1.Content,
5138
+ {
5139
+ ref,
5140
+ "data-slot": "tabs-content",
5141
+ className: cx("orynn-tabs__content", className),
5142
+ ...props
5143
+ }
5144
+ );
5145
+ });
5146
+ var Dialog = (props) => /* @__PURE__ */ jsx(Dialog$1.Root, { "data-slot": "dialog", ...props });
5147
+ var DialogTrigger = (props) => /* @__PURE__ */ jsx(Dialog$1.Trigger, { "data-slot": "dialog-trigger", ...props });
5148
+ var DialogClose = (props) => /* @__PURE__ */ jsx(Dialog$1.Close, { "data-slot": "dialog-close", ...props });
5149
+ var DialogPortal = Dialog$1.Portal;
5150
+ var DialogContent = /* @__PURE__ */ forwardRef(
5151
+ function DialogContent2({ className, children, showCloseButton = true, size = "lg", scrollable, container, ...props }, ref) {
5152
+ const themed = usePortalContainer();
5153
+ return /* @__PURE__ */ jsxs(Dialog$1.Portal, { container: container ?? themed ?? void 0, children: [
5154
+ /* @__PURE__ */ jsx(
5155
+ Dialog$1.Overlay,
5156
+ {
5157
+ "data-slot": "dialog-overlay",
5158
+ className: "orynn-overlay orynn-dialog__overlay"
5159
+ }
5160
+ ),
5161
+ /* @__PURE__ */ jsxs(
5162
+ Dialog$1.Content,
5163
+ {
5164
+ ref,
5165
+ "data-slot": "dialog-content",
5166
+ "data-size": size,
5167
+ "data-scrollable": scrollable || void 0,
5168
+ className: cx("orynn-dialog__content", className),
5169
+ ...props,
5170
+ children: [
5171
+ children,
5172
+ showCloseButton && /* @__PURE__ */ jsx(
5173
+ Dialog$1.Close,
5174
+ {
5175
+ "data-slot": "dialog-close",
5176
+ className: "orynn-dialog__close",
5177
+ "aria-label": "Close",
5178
+ children: /* @__PURE__ */ jsx(XIcon, {})
5179
+ }
5180
+ )
5181
+ ]
5182
+ }
5183
+ )
5184
+ ] });
5185
+ }
5186
+ );
5187
+ function DialogHeader({ className, ...props }) {
5188
+ return /* @__PURE__ */ jsx("div", { "data-slot": "dialog-header", className: cx("orynn-dialog__header", className), ...props });
2665
5189
  }
2666
- var OrynnContext = createContext({
2667
- preset: "default",
2668
- density: "comfortable",
2669
- config: {}
5190
+ function DialogFooter({ className, ...props }) {
5191
+ return /* @__PURE__ */ jsx("div", { "data-slot": "dialog-footer", className: cx("orynn-dialog__footer", className), ...props });
5192
+ }
5193
+ var DialogTitle = /* @__PURE__ */ forwardRef(function DialogTitle2({ className, ...props }, ref) {
5194
+ return /* @__PURE__ */ jsx(
5195
+ Dialog$1.Title,
5196
+ {
5197
+ ref,
5198
+ "data-slot": "dialog-title",
5199
+ className: cx("orynn-dialog__title", className),
5200
+ ...props
5201
+ }
5202
+ );
2670
5203
  });
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]
5204
+ var DialogDescription = /* @__PURE__ */ forwardRef(function DialogDescription2({ className, ...props }, ref) {
5205
+ return /* @__PURE__ */ jsx(
5206
+ Dialog$1.Description,
5207
+ {
5208
+ ref,
5209
+ "data-slot": "dialog-description",
5210
+ className: cx("orynn-dialog__description", className),
5211
+ ...props
5212
+ }
5213
+ );
5214
+ });
5215
+ var Popover2 = (props) => /* @__PURE__ */ jsx(Popover$1.Root, { "data-slot": "popover", ...props });
5216
+ var PopoverTrigger = (props) => /* @__PURE__ */ jsx(Popover$1.Trigger, { "data-slot": "popover-trigger", ...props });
5217
+ var PopoverAnchor = (props) => /* @__PURE__ */ jsx(Popover$1.Anchor, { "data-slot": "popover-anchor", ...props });
5218
+ var PopoverClose = Popover$1.Close;
5219
+ var PopoverArrow = (props) => /* @__PURE__ */ jsx(Popover$1.Arrow, { "data-slot": "popover-arrow", className: "orynn-popover__arrow", ...props });
5220
+ var PopoverContent = /* @__PURE__ */ forwardRef(
5221
+ function PopoverContent2({ className, align = "center", sideOffset = 4, arrow = false, container, children, ...props }, ref) {
5222
+ const themed = usePortalContainer();
5223
+ return /* @__PURE__ */ jsx(Popover$1.Portal, { container: container ?? themed ?? void 0, children: /* @__PURE__ */ jsxs(
5224
+ Popover$1.Content,
5225
+ {
5226
+ ref,
5227
+ "data-slot": "popover-content",
5228
+ align,
5229
+ sideOffset,
5230
+ className: cx("orynn-content orynn-popover__content", className),
5231
+ ...props,
5232
+ children: [
5233
+ children,
5234
+ arrow && /* @__PURE__ */ jsx(PopoverArrow, {})
5235
+ ]
5236
+ }
5237
+ ) });
5238
+ }
5239
+ );
5240
+ function TooltipProvider({
5241
+ delayDuration = 0,
5242
+ ...props
5243
+ }) {
5244
+ return /* @__PURE__ */ jsx(
5245
+ Tooltip$1.Provider,
5246
+ {
5247
+ "data-slot": "tooltip-provider",
5248
+ delayDuration,
5249
+ ...props
5250
+ }
2679
5251
  );
2680
- const dataProps = {
2681
- "data-orynn-theme": preset === "default" ? void 0 : preset,
2682
- "data-orynn-density": density === "comfortable" ? void 0 : density
2683
- };
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 }) });
2685
5252
  }
2686
- function useOrynnTheme() {
2687
- return useContext(OrynnContext);
5253
+ function Tooltip({
5254
+ delayDuration = 0,
5255
+ skipDelayDuration,
5256
+ disableHoverableContent,
5257
+ disableProvider = false,
5258
+ ...props
5259
+ }) {
5260
+ const root = /* @__PURE__ */ jsx(
5261
+ Tooltip$1.Root,
5262
+ {
5263
+ "data-slot": "tooltip",
5264
+ delayDuration,
5265
+ disableHoverableContent,
5266
+ ...props
5267
+ }
5268
+ );
5269
+ if (disableProvider) return root;
5270
+ return /* @__PURE__ */ jsx(
5271
+ TooltipProvider,
5272
+ {
5273
+ delayDuration,
5274
+ skipDelayDuration,
5275
+ disableHoverableContent,
5276
+ children: root
5277
+ }
5278
+ );
5279
+ }
5280
+ var TooltipTrigger = (props) => /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
5281
+ var TooltipContent = /* @__PURE__ */ forwardRef(
5282
+ function TooltipContent2({ className, sideOffset = 0, arrow = true, arrowSize = 11, container, children, ...props }, ref) {
5283
+ const themed = usePortalContainer();
5284
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { container: container ?? themed ?? void 0, children: /* @__PURE__ */ jsxs(
5285
+ Tooltip$1.Content,
5286
+ {
5287
+ ref,
5288
+ "data-slot": "tooltip-content",
5289
+ sideOffset,
5290
+ className: cx("orynn-content orynn-tooltip__content", className),
5291
+ ...props,
5292
+ children: [
5293
+ children,
5294
+ arrow && /* @__PURE__ */ jsx(
5295
+ Tooltip$1.Arrow,
5296
+ {
5297
+ width: arrowSize,
5298
+ height: Math.round(arrowSize / 2.2),
5299
+ className: "orynn-tooltip__arrow"
5300
+ }
5301
+ )
5302
+ ]
5303
+ }
5304
+ ) });
5305
+ }
5306
+ );
5307
+ var DropdownMenu = (props) => /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
5308
+ var DropdownMenuTrigger = (props) => /* @__PURE__ */ jsx(DropdownMenu$1.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
5309
+ var DropdownMenuGroup = DropdownMenu$1.Group;
5310
+ var DropdownMenuPortal = DropdownMenu$1.Portal;
5311
+ var DropdownMenuSub = DropdownMenu$1.Sub;
5312
+ var DropdownMenuRadioGroup = DropdownMenu$1.RadioGroup;
5313
+ var DropdownMenuContent = /* @__PURE__ */ forwardRef(function DropdownMenuContent2({ className, sideOffset = 4, container, ...props }, ref) {
5314
+ const themed = usePortalContainer();
5315
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { container: container ?? themed ?? void 0, children: /* @__PURE__ */ jsx(
5316
+ DropdownMenu$1.Content,
5317
+ {
5318
+ ref,
5319
+ "data-slot": "dropdown-menu-content",
5320
+ sideOffset,
5321
+ className: cx("orynn-content orynn-menu__content", className),
5322
+ ...props
5323
+ }
5324
+ ) });
5325
+ });
5326
+ var DropdownMenuItem = /* @__PURE__ */ forwardRef(
5327
+ function DropdownMenuItem2({ className, inset, variant = "default", icon, shortcut, children, ...props }, ref) {
5328
+ return /* @__PURE__ */ jsxs(
5329
+ DropdownMenu$1.Item,
5330
+ {
5331
+ ref,
5332
+ "data-slot": "dropdown-menu-item",
5333
+ "data-inset": inset || void 0,
5334
+ "data-variant": variant,
5335
+ className: cx("orynn-menu__item", className),
5336
+ ...props,
5337
+ children: [
5338
+ icon,
5339
+ children,
5340
+ shortcut != null && /* @__PURE__ */ jsx("span", { className: "orynn-menu__shortcut", children: shortcut })
5341
+ ]
5342
+ }
5343
+ );
5344
+ }
5345
+ );
5346
+ var DropdownMenuCheckboxItem = /* @__PURE__ */ forwardRef(function DropdownMenuCheckboxItem2({ className, children, checked, ...props }, ref) {
5347
+ return /* @__PURE__ */ jsxs(
5348
+ DropdownMenu$1.CheckboxItem,
5349
+ {
5350
+ ref,
5351
+ "data-slot": "dropdown-menu-checkbox-item",
5352
+ className: cx("orynn-menu__item", "orynn-menu__item--check", className),
5353
+ checked,
5354
+ ...props,
5355
+ children: [
5356
+ /* @__PURE__ */ jsx("span", { className: "orynn-menu__indicator", children: /* @__PURE__ */ jsx(DropdownMenu$1.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) }) }),
5357
+ children
5358
+ ]
5359
+ }
5360
+ );
5361
+ });
5362
+ var DropdownMenuRadioItem = /* @__PURE__ */ forwardRef(function DropdownMenuRadioItem2({ className, children, ...props }, ref) {
5363
+ return /* @__PURE__ */ jsxs(
5364
+ DropdownMenu$1.RadioItem,
5365
+ {
5366
+ ref,
5367
+ "data-slot": "dropdown-menu-radio-item",
5368
+ className: cx("orynn-menu__item", "orynn-menu__item--check", className),
5369
+ ...props,
5370
+ children: [
5371
+ /* @__PURE__ */ jsx("span", { className: "orynn-menu__indicator", children: /* @__PURE__ */ jsx(DropdownMenu$1.ItemIndicator, { children: /* @__PURE__ */ jsx(CircleIcon, { className: "orynn-menu__dot" }) }) }),
5372
+ children
5373
+ ]
5374
+ }
5375
+ );
5376
+ });
5377
+ var DropdownMenuLabel = /* @__PURE__ */ forwardRef(
5378
+ function DropdownMenuLabel2({ className, inset, ...props }, ref) {
5379
+ return /* @__PURE__ */ jsx(
5380
+ DropdownMenu$1.Label,
5381
+ {
5382
+ ref,
5383
+ "data-slot": "dropdown-menu-label",
5384
+ "data-inset": inset || void 0,
5385
+ className: cx("orynn-menu__label", className),
5386
+ ...props
5387
+ }
5388
+ );
5389
+ }
5390
+ );
5391
+ var DropdownMenuSeparator = /* @__PURE__ */ forwardRef(function DropdownMenuSeparator2({ className, ...props }, ref) {
5392
+ return /* @__PURE__ */ jsx(
5393
+ DropdownMenu$1.Separator,
5394
+ {
5395
+ ref,
5396
+ "data-slot": "dropdown-menu-separator",
5397
+ className: cx("orynn-menu__separator", className),
5398
+ ...props
5399
+ }
5400
+ );
5401
+ });
5402
+ function DropdownMenuShortcut({ className, ...props }) {
5403
+ return /* @__PURE__ */ jsx(
5404
+ "span",
5405
+ {
5406
+ "data-slot": "dropdown-menu-shortcut",
5407
+ className: cx("orynn-menu__shortcut", className),
5408
+ ...props
5409
+ }
5410
+ );
2688
5411
  }
5412
+ var DropdownMenuSubTrigger = /* @__PURE__ */ forwardRef(function DropdownMenuSubTrigger2({ className, inset, children, ...props }, ref) {
5413
+ return /* @__PURE__ */ jsxs(
5414
+ DropdownMenu$1.SubTrigger,
5415
+ {
5416
+ ref,
5417
+ "data-slot": "dropdown-menu-sub-trigger",
5418
+ "data-inset": inset || void 0,
5419
+ className: cx("orynn-menu__item", "orynn-menu__sub-trigger", className),
5420
+ ...props,
5421
+ children: [
5422
+ children,
5423
+ /* @__PURE__ */ jsx(ChevronRightIcon, { className: "orynn-menu__sub-chevron" })
5424
+ ]
5425
+ }
5426
+ );
5427
+ });
5428
+ var DropdownMenuSubContent = /* @__PURE__ */ forwardRef(function DropdownMenuSubContent2({ className, ...props }, ref) {
5429
+ return /* @__PURE__ */ jsx(
5430
+ DropdownMenu$1.SubContent,
5431
+ {
5432
+ ref,
5433
+ "data-slot": "dropdown-menu-sub-content",
5434
+ className: cx("orynn-content", "orynn-menu__content", "orynn-menu__sub-content", className),
5435
+ ...props
5436
+ }
5437
+ );
5438
+ });
2689
5439
 
2690
5440
  // src/types/validation.ts
2691
5441
  var FORM_ERROR_KEY = "$form";
2692
5442
 
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 };
5443
+ // src/core/validation/standard-schema.ts
5444
+ function pathToField(path) {
5445
+ if (!path || path.length === 0) return { field: FORM_ERROR_KEY, rest: "" };
5446
+ const seg = (p) => String(typeof p === "object" && p !== null && "key" in p ? p.key : p);
5447
+ const parts = path.map(seg);
5448
+ return { field: parts[0] ?? FORM_ERROR_KEY, rest: parts.slice(1).join(".") };
5449
+ }
5450
+ function standardSchemaResolver(schema) {
5451
+ return (values) => {
5452
+ const out = schema["~standard"].validate(values);
5453
+ const toResult = (r) => {
5454
+ const result = {};
5455
+ if (!("issues" in r) || !r.issues) return result;
5456
+ for (const issue of r.issues) {
5457
+ const { field, rest } = pathToField(issue.path);
5458
+ const bucket = result[field] ?? [];
5459
+ bucket.push({ rule: "schema", message: issue.message, ...rest ? { path: rest } : {} });
5460
+ result[field] = bucket;
5461
+ }
5462
+ return result;
5463
+ };
5464
+ return out instanceof Promise ? out.then(toResult) : toResult(out);
5465
+ };
5466
+ }
5467
+
5468
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, 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, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, Popover2 as Popover, PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverTrigger, Progress, Radio, RadioGroup, Dropdown as Select, Separator, Slider, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toaster, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeClasses, builtInRules, buttonClasses, createRegistry, createRuleResolver, defaultRegistry, defaultResolver, normalizeConfig, registerField, registerRule, resolveThemeVars, standardSchemaResolver, toast, useFieldsetState, useOrynnTheme, usePagination, usePortalContainer, useToast };
2694
5469
  //# sourceMappingURL=index.js.map
2695
5470
  //# sourceMappingURL=index.js.map