@skiddph/prui 0.8.0 → 0.9.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.
@@ -1,158 +1,168 @@
1
- import { jsxs as G, Fragment as J, jsx as i } from "react/jsx-runtime";
2
- import * as o from "react";
3
- import { ChevronDown as X } from "lucide-react";
4
- import { cn as N } from "./cn.js";
5
- import { useOverlayStack as Y, useEscapeKey as Z, Portal as _ } from "./overlay.js";
6
- import { useAnchoredPosition as K } from "./anchor.js";
7
- import { endIndex as ee, homeIndex as te, moveIndex as q } from "./list-nav.js";
8
- const re = o.forwardRef(function({
9
- options: u,
10
- value: x,
11
- defaultValue: C,
12
- onChange: h,
1
+ import { jsxs as $, Fragment as Z, jsx as c } from "react/jsx-runtime";
2
+ import * as a from "react";
3
+ import { ChevronDown as _ } from "lucide-react";
4
+ import { cn as x } from "./cn.js";
5
+ import { useOverlayStack as K, useEscapeKey as ee, Portal as te } from "./overlay.js";
6
+ import { useAnchoredPosition as re } from "./anchor.js";
7
+ import { endIndex as ae, homeIndex as ne, moveIndex as j } from "./list-nav.js";
8
+ const oe = a.forwardRef(function({
9
+ options: d,
10
+ value: h,
11
+ defaultValue: P,
12
+ onChange: g,
13
13
  onOpenChange: y,
14
- placeholder: T = "Type to search...",
15
- emptyText: R,
16
- noMatchText: $ = "No matches.",
17
- defaultOpen: L = !1,
18
- filter: g,
19
- disabled: I,
20
- id: U,
21
- className: z,
22
- onKeyDown: w,
23
- ...D
24
- }, b) {
25
- var M, j;
26
- const [F, P] = o.useState(C ?? ""), [a, W] = o.useState(L), [p, k] = o.useState(null), [n, s] = o.useState(0), c = o.useRef(null), E = o.useRef(null), v = o.useId(), A = x !== void 0 ? x : F, H = (M = u.find((e) => e.value === A)) == null ? void 0 : M.label, d = (e) => {
27
- var t, l;
28
- if (W(e), y == null || y(e), !e) {
29
- k(null);
30
- const m = document.activeElement;
31
- m && ((t = E.current) != null && t.contains(m)) && ((l = c.current) == null || l.focus());
14
+ placeholder: U = "Type to search...",
15
+ emptyText: I,
16
+ noMatchText: z = "No matches.",
17
+ defaultOpen: F = !1,
18
+ filter: w,
19
+ disabled: D,
20
+ id: W,
21
+ className: A,
22
+ onKeyDown: k,
23
+ ...S
24
+ }, v) {
25
+ var q, C;
26
+ const [H, O] = a.useState(P ?? ""), [n, Q] = a.useState(F), [m, R] = a.useState(null), [l, s] = a.useState(0), p = a.useRef(null), E = a.useRef(null), N = a.useRef(null), b = a.useId(), L = h !== void 0 ? h : H, V = (q = d.find((e) => e.value === L)) == null ? void 0 : q.label, u = (e) => {
27
+ var t, o;
28
+ if (Q(e), y == null || y(e), !e) {
29
+ R(null);
30
+ const i = document.activeElement;
31
+ i && ((t = E.current) != null && t.contains(i)) && ((o = p.current) == null || o.focus());
32
32
  }
33
- }, { setElement: O, isTop: Q } = Y(a);
34
- Z(a, Q, () => {
33
+ }, { setElement: B, isTop: G } = K(n);
34
+ ee(n, G, () => {
35
35
  var e;
36
- d(!1), (e = c.current) == null || e.focus();
36
+ u(!1), (e = p.current) == null || e.focus();
37
+ }), a.useEffect(() => {
38
+ if (!n) return;
39
+ const e = (t) => {
40
+ var i, T;
41
+ const o = t.target;
42
+ (i = N.current) != null && i.contains(o) || (T = E.current) != null && T.contains(o) || u(!1);
43
+ };
44
+ return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
37
45
  });
38
- const r = o.useMemo(() => {
39
- if (p === null) return u;
40
- const e = p.toLowerCase();
41
- return e ? u.filter((t) => g ? g(t, e) : t.label.toLowerCase().includes(e)) : u;
42
- }, [u, p, g]);
43
- o.useEffect(() => {
46
+ const r = a.useMemo(() => {
47
+ if (m === null) return d;
48
+ const e = m.toLowerCase();
49
+ return e ? d.filter((t) => w ? w(t, e) : t.label.toLowerCase().includes(e)) : d;
50
+ }, [d, m, w]);
51
+ a.useEffect(() => {
44
52
  s((e) => e < r.length ? e : Math.max(0, r.length - 1));
45
53
  }, [r.length]);
46
- const S = (e) => {
54
+ const M = (e) => {
47
55
  var t;
48
- e.disabled || (x === void 0 && P(e.value), h == null || h(e.value), k(null), d(!1), (t = c.current) == null || t.focus());
49
- }, { ref: V, position: f } = K({ active: a, anchorRef: c, side: "bottom", align: "start" }), B = (e) => {
50
- if (w == null || w(e), e.defaultPrevented) return;
51
- if (!a && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
52
- e.preventDefault(), d(!0);
56
+ e.disabled || (h === void 0 && O(e.value), g == null || g(e.value), R(null), u(!1), (t = p.current) == null || t.focus());
57
+ }, J = N, { ref: X, position: f } = re({ active: n, anchorRef: J, side: "bottom", align: "start" }), Y = (e) => {
58
+ if (k == null || k(e), e.defaultPrevented) return;
59
+ if (!n && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
60
+ e.preventDefault(), u(!0);
53
61
  return;
54
62
  }
55
- if (!a) return;
56
- const t = (l) => {
57
- var m;
58
- return !((m = r[l]) != null && m.disabled);
63
+ if (!n) return;
64
+ const t = (o) => {
65
+ var i;
66
+ return !((i = r[o]) != null && i.disabled);
59
67
  };
60
68
  switch (e.key) {
61
69
  case "ArrowDown":
62
- e.preventDefault(), s(q(n, 1, r.length, { enabled: t }));
70
+ e.preventDefault(), s(j(l, 1, r.length, { enabled: t }));
63
71
  break;
64
72
  case "ArrowUp":
65
- e.preventDefault(), s(q(n, -1, r.length, { enabled: t }));
73
+ e.preventDefault(), s(j(l, -1, r.length, { enabled: t }));
66
74
  break;
67
75
  case "Home":
68
- r.length && (e.preventDefault(), s(te(r.length, t)));
76
+ r.length && (e.preventDefault(), s(ne(r.length, t)));
69
77
  break;
70
78
  case "End":
71
- r.length && (e.preventDefault(), s(ee(r.length, t)));
79
+ r.length && (e.preventDefault(), s(ae(r.length, t)));
72
80
  break;
73
81
  case "Enter":
74
- e.preventDefault(), r[n] && S(r[n]);
82
+ e.preventDefault(), r[l] && M(r[l]);
75
83
  break;
76
84
  case "Tab":
77
- d(!1);
85
+ u(!1);
78
86
  break;
79
87
  }
80
88
  };
81
- return /* @__PURE__ */ G(J, { children: [
82
- /* @__PURE__ */ i(
83
- "input",
84
- {
85
- ref: (e) => {
86
- c.current = e, typeof b == "function" ? b(e) : b && (b.current = e);
87
- },
88
- id: U,
89
- type: "text",
90
- role: "combobox",
91
- "aria-expanded": a,
92
- "aria-haspopup": "listbox",
93
- "aria-controls": a ? v : void 0,
94
- "aria-activedescendant": a && r[n] ? `${v}-option-${n}` : void 0,
95
- "aria-autocomplete": "list",
96
- autoComplete: "off",
97
- disabled: I,
98
- placeholder: T,
99
- className: N(
100
- "prui-combobox-trigger flex h-9 w-full items-center justify-between gap-2 border border-[var(--prui-line)]",
101
- "bg-[var(--prui-background)] rounded-[var(--prui-radius)] px-3 text-sm text-[var(--prui-fg)] placeholder:text-[var(--prui-dim)]",
102
- "outline-none transition-colors focus:border-[var(--prui-brand)] focus:ring-2 focus:ring-[var(--prui-brand)]/30",
103
- "disabled:opacity-50 disabled:cursor-not-allowed cursor-text",
104
- z
105
- ),
106
- value: p !== null ? p : H ?? "",
107
- onChange: (e) => {
108
- k(e.target.value), d(!0), s(0);
109
- },
110
- onFocus: () => {
111
- !a && !I && d(!0);
112
- },
113
- onKeyDown: B,
114
- ...D
115
- }
116
- ),
117
- /* @__PURE__ */ i("span", { className: "pointer-events-none relative", children: /* @__PURE__ */ i(X, { className: "pointer-events-none absolute -left-7 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--prui-dim)]", "aria-hidden": !0 }) }),
118
- a ? /* @__PURE__ */ i(_, { children: /* @__PURE__ */ i(
89
+ return /* @__PURE__ */ $(Z, { children: [
90
+ /* @__PURE__ */ $("div", { ref: N, className: x("prui-combobox relative inline-flex w-full items-center", A), children: [
91
+ /* @__PURE__ */ c(
92
+ "input",
93
+ {
94
+ ref: (e) => {
95
+ p.current = e, typeof v == "function" ? v(e) : v && (v.current = e);
96
+ },
97
+ id: W,
98
+ type: "text",
99
+ role: "combobox",
100
+ "aria-expanded": n,
101
+ "aria-haspopup": "listbox",
102
+ "aria-controls": n ? b : void 0,
103
+ "aria-activedescendant": n && r[l] ? `${b}-option-${l}` : void 0,
104
+ "aria-autocomplete": "list",
105
+ autoComplete: "off",
106
+ disabled: D,
107
+ placeholder: U,
108
+ className: x(
109
+ "prui-combobox-trigger h-9 w-full pr-8",
110
+ "bg-[var(--prui-background)] rounded-[var(--prui-radius)] px-3 text-sm text-[var(--prui-fg)] placeholder:text-[var(--prui-dim)]",
111
+ "outline-none transition-colors focus:border-[var(--prui-brand)] focus:ring-2 focus:ring-[var(--prui-brand)]/30",
112
+ "disabled:opacity-50 disabled:cursor-not-allowed cursor-text",
113
+ A
114
+ ),
115
+ value: m !== null ? m : V ?? "",
116
+ onChange: (e) => {
117
+ R(e.target.value), u(!0), s(0);
118
+ },
119
+ onFocus: () => {
120
+ !n && !D && u(!0);
121
+ },
122
+ onKeyDown: Y,
123
+ ...S
124
+ }
125
+ ),
126
+ /* @__PURE__ */ c(_, { className: "pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--prui-dim)]", "aria-hidden": !0 })
127
+ ] }),
128
+ n ? /* @__PURE__ */ c(te, { children: /* @__PURE__ */ c(
119
129
  "div",
120
130
  {
121
131
  ref: (e) => {
122
- E.current = e, V(e), O(e);
132
+ E.current = e, X(e), B(e);
123
133
  },
124
- id: v,
134
+ id: b,
125
135
  role: "listbox",
126
- "aria-label": D["aria-label"] ?? "Options",
136
+ "aria-label": S["aria-label"] ?? "Options",
127
137
  tabIndex: -1,
128
138
  "data-state": "open",
129
- className: N(
139
+ className: x(
130
140
  "prui-combobox-content fixed z-[var(--prui-z-overlay)] min-w-40 max-h-60 overflow-auto p-1",
131
141
  "border border-[var(--prui-line)] rounded-[var(--prui-radius)] bg-[var(--prui-surface)] shadow-[var(--prui-shadow-md)]"
132
142
  ),
133
143
  style: {
134
144
  top: (f == null ? void 0 : f.top) ?? -9999,
135
145
  left: (f == null ? void 0 : f.left) ?? -9999,
136
- minWidth: ((j = c.current) == null ? void 0 : j.offsetWidth) ?? 200
146
+ minWidth: ((C = p.current) == null ? void 0 : C.offsetWidth) ?? 200
137
147
  },
138
- children: u.length === 0 && R ? /* @__PURE__ */ i("div", { className: "px-3 py-2 text-sm text-[var(--prui-dim)]", children: R }) : r.length === 0 ? /* @__PURE__ */ i("div", { className: "px-3 py-2 text-sm text-[var(--prui-dim)]", role: "option", "aria-selected": "false", "aria-disabled": "true", children: $ }) : r.map((e, t) => {
139
- const l = A === e.value;
140
- return /* @__PURE__ */ i(
148
+ children: d.length === 0 && I ? /* @__PURE__ */ c("div", { className: "px-3 py-2 text-sm text-[var(--prui-dim)]", children: I }) : r.length === 0 ? /* @__PURE__ */ c("div", { className: "px-3 py-2 text-sm text-[var(--prui-dim)]", role: "option", "aria-selected": "false", "aria-disabled": "true", children: z }) : r.map((e, t) => {
149
+ const o = L === e.value;
150
+ return /* @__PURE__ */ c(
141
151
  "div",
142
152
  {
143
- id: `${v}-option-${t}`,
153
+ id: `${b}-option-${t}`,
144
154
  role: "option",
145
- "aria-selected": l,
155
+ "aria-selected": o,
146
156
  "aria-disabled": e.disabled,
147
- "data-active": t === n || void 0,
148
- "data-selected": l || void 0,
157
+ "data-active": t === l || void 0,
158
+ "data-selected": o || void 0,
149
159
  tabIndex: -1,
150
160
  onMouseEnter: () => s(t),
151
- onClick: () => S(e),
152
- className: N(
161
+ onClick: () => M(e),
162
+ className: x(
153
163
  "flex cursor-pointer items-center rounded-[calc(var(--prui-radius)-1px)] px-3 py-1.5 text-sm text-[var(--prui-fg)]",
154
- t === n && "bg-[var(--prui-raise)]",
155
- l && "font-medium",
164
+ t === l && "bg-[var(--prui-raise)]",
165
+ o && "font-medium",
156
166
  e.disabled && "opacity-50 pointer-events-none"
157
167
  ),
158
168
  children: e.label
@@ -164,8 +174,8 @@ const re = o.forwardRef(function({
164
174
  ) }) : null
165
175
  ] });
166
176
  });
167
- re.displayName = "Combobox";
168
- const ce = {
177
+ oe.displayName = "Combobox";
178
+ const me = {
169
179
  name: "Combobox",
170
180
  props: [
171
181
  { name: "options", type: "ComboboxOption[]", default: null, control: "object" },
@@ -178,6 +188,6 @@ const ce = {
178
188
  ]
179
189
  };
180
190
  export {
181
- re as Combobox,
182
- ce as comboboxPropsMeta
191
+ oe as Combobox,
192
+ me as comboboxPropsMeta
183
193
  };
@@ -2,11 +2,14 @@ import { PropsMeta } from './props-meta';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * DatePicker / DateRangePicker: a text trigger opening a Calendar popover.
5
- * Values stay plain "YYYY-MM-DD" strings (native input-compatible, no date
6
- * library). The trigger is keyboard-operable (Enter/Space/ArrowDown opens,
7
- * Escape topmost closes and refocuses); typing a date in the input is also
8
- * accepted. The range picker anchors on the start date and shows a
9
- * start–end chip row.
5
+ * Values stay plain strings — "YYYY-MM-DD" by default, or
6
+ * "YYYY-MM-DD HH:mm(:ss)" with timepicker on so they remain
7
+ * native-input compatible with no date library. The trigger is
8
+ * keyboard-operable (Enter/ArrowDown opens, Escape topmost closes and
9
+ * refocuses, typing a full value commits); clicking outside dismisses.
10
+ * The range picker anchors on the start date, supports a maxRange cap
11
+ * ("90d", "12w", "3m", "1y", or a plain number of days), and restarts
12
+ * when the picked end precedes the start.
10
13
  */
11
14
  export interface DatePickerProps {
12
15
  value?: string;
@@ -19,22 +22,40 @@ export interface DatePickerProps {
19
22
  className?: string;
20
23
  /** Accessible label for the trigger input. */
21
24
  ariaLabel?: string;
25
+ /** Include a time panel; values become "YYYY-MM-DD HH:mm(:ss)". Default false. */
26
+ timepicker?: boolean;
27
+ /** Seconds column for the time panel (requires timepicker). */
28
+ seconds?: boolean;
29
+ /** Minute step for the time panel (requires timepicker). */
30
+ minuteStep?: number;
22
31
  }
23
32
  export declare const DatePicker: React.ForwardRefExoticComponent<DatePickerProps & React.RefAttributes<HTMLInputElement>>;
24
33
  export interface DateRange {
25
34
  from?: string;
26
35
  to?: string;
27
36
  }
37
+ /**
38
+ * maxRange caps the span between from and to. Accept a plain number of
39
+ * days, or a string with a unit: "90d" days, "12w" weeks, "3m" months
40
+ * (calendar-aware), "1y" years.
41
+ */
42
+ export type MaxRange = number | `${number}d` | `${number}w` | `${number}m` | `${number}y` | (string & {});
28
43
  export interface DateRangePickerProps {
29
44
  value?: DateRange;
30
45
  defaultValue?: DateRange;
31
46
  onChange?: (range: DateRange) => void;
32
47
  min?: string;
33
48
  max?: string;
49
+ /** Cap the selectable span, e.g. 90, "90d", "12w", "3m", "1y". */
50
+ maxRange?: MaxRange;
34
51
  placeholder?: string;
35
52
  disabled?: boolean;
36
53
  className?: string;
37
54
  ariaLabel?: string;
55
+ /** Include time panels; values become "YYYY-MM-DD HH:mm(:ss)". Default false. */
56
+ timepicker?: boolean;
57
+ seconds?: boolean;
58
+ minuteStep?: number;
38
59
  }
39
60
  export declare const DateRangePicker: React.ForwardRefExoticComponent<DateRangePickerProps & React.RefAttributes<HTMLInputElement>>;
40
61
  export declare const datePickerPropsMeta: PropsMeta;