@leonardaustin/ui 0.15.0 → 0.16.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.
@@ -3,6 +3,8 @@ export interface FieldContextValue {
3
3
  describedBy?: string;
4
4
  invalid?: boolean;
5
5
  required?: boolean;
6
+ /** Text a single-value Select shows in its empty closed control instead of a visible field label. */
7
+ placeholderLabel?: string;
6
8
  }
7
9
  export declare const FieldContext: import("react").Context<FieldContextValue | null>;
8
10
  export declare function describeField(...ids: (string | undefined)[]): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"FieldContext.d.ts","sourceRoot":"","sources":["../../../src/components/ui/FieldContext.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AACD,eAAO,MAAM,YAAY,mDAAgD,CAAC;AAE1E,wBAAgB,aAAa,CAAC,GAAG,GAAG,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,sBAM3D"}
1
+ {"version":3,"file":"FieldContext.d.ts","sourceRoot":"","sources":["../../../src/components/ui/FieldContext.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qGAAqG;IACrG,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AACD,eAAO,MAAM,YAAY,mDAAgD,CAAC;AAE1E,wBAAgB,aAAa,CAAC,GAAG,GAAG,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,sBAM3D"}
@@ -18,10 +18,18 @@ export interface FormFieldProps extends Omit<HTMLAttributes<HTMLDivElement>, "ch
18
18
  required?: boolean;
19
19
  /** Text shown beside the label when the field is not required, e.g. "(optional)". */
20
20
  optionalLabel?: ReactNode;
21
+ /**
22
+ * `"placeholder"` visually hides the label and asks a child single-value
23
+ * `Select` without its own label to show the label text in its empty closed
24
+ * control. The label stays the accessible name. Only a string label can be
25
+ * shown as a placeholder; any other label stays above. Use it only around a
26
+ * single-value Select.
27
+ */
28
+ labelPlacement?: "above" | "placeholder";
21
29
  children: ReactNode | ((attributes: FieldControlAttributes) => ReactNode);
22
30
  }
23
31
  /** Label, hint and error association for one native control. */
24
- export declare function FormField({ id, label, hint, error, required, optionalLabel, children, className, ...props }: FormFieldProps): import("react/jsx-runtime").JSX.Element;
32
+ export declare function FormField({ id, label, hint, error, required, optionalLabel, labelPlacement, children, className, ...props }: FormFieldProps): import("react/jsx-runtime").JSX.Element;
25
33
  /** Standalone hint text for custom controls; connect its ID with aria-describedby. */
26
34
  export declare function FieldHint({ className, ...props }: HTMLAttributes<HTMLParagraphElement>): import("react/jsx-runtime").JSX.Element;
27
35
  /** Standalone field error associated with its control. */
@@ -1 +1 @@
1
- {"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../../src/components/ui/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKnE,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,yEAAyE;IACzE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kFAAkF;IAClF,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,QAAQ,EAAE,SAAS,GAAG,CAAC,CAAC,UAAU,EAAE,sBAAsB,KAAK,SAAS,CAAC,CAAC;CAC3E;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,EACxB,EAAE,EACF,KAAK,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,2CA2ChB;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,oBAAoB,CAAC,2CAItC;AACD,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,oBAAoB,CAAC,2CAEtC"}
1
+ {"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../../src/components/ui/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKnE,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,yEAAyE;IACzE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kFAAkF;IAClF,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IACzC,QAAQ,EAAE,SAAS,GAAG,CAAC,CAAC,UAAU,EAAE,sBAAsB,KAAK,SAAS,CAAC,CAAC;CAC3E;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,EACxB,EAAE,EACF,KAAK,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,aAAa,EACb,cAAwB,EACxB,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,2CAmDhB;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,oBAAoB,CAAC,2CAItC;AACD,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,oBAAoB,CAAC,2CAEtC"}
@@ -1,8 +1,11 @@
1
1
  import { type SelectHTMLAttributes } from "react";
2
2
  import { type ControlSize } from "../../providers/ControlSizeContext";
3
- interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
3
+ interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "value"> {
4
4
  /** Label text rendered above the select and linked to it. */
5
5
  label?: string;
6
+ /** Render the label inside an empty single-select control. Listboxes retain the above label. */
7
+ labelPlacement?: "above" | "placeholder";
8
+ value?: SelectHTMLAttributes<HTMLSelectElement>["value"] | null;
6
9
  /** Error message shown next to the label; also marks the select invalid and turns the border red. */
7
10
  error?: string;
8
11
  /** Helper text shown above the select and linked via `aria-describedby`. */
@@ -1 +1 @@
1
- {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAK9D,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,oCAAoC,CAAC;AAG5C,UAAU,WAAY,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IACnE,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qGAAqG;IACrG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,uFAAuF;IACvF,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7C;AAED,eAAO,MAAM,MAAM,2GAkFlB,CAAC"}
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,oBAAoB,EAC1B,MAAM,OAAO,CAAC;AAKf,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,oCAAoC,CAAC;AAG5C,UAAU,WAAY,SAAQ,IAAI,CAChC,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,OAAO,CACR;IACC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,cAAc,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IACzC,KAAK,CAAC,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAChE,qGAAqG;IACrG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,uFAAuF;IACvF,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7C;AAED,eAAO,MAAM,MAAM,2GAqMlB,CAAC"}
@@ -6,9 +6,11 @@ interface SelectDropdownOption {
6
6
  }
7
7
  interface SelectDropdownProps {
8
8
  options: SelectDropdownOption[];
9
- value: string;
9
+ value?: string | null;
10
10
  onChange: (value: string) => void;
11
11
  label?: string;
12
+ /** Render the label in the empty trigger, including when an empty-valued option is selected. */
13
+ labelPlacement?: "above" | "placeholder";
12
14
  placeholder?: string;
13
15
  className?: string;
14
16
  size?: "sm" | "md";
@@ -21,6 +23,6 @@ interface SelectDropdownProps {
21
23
  * typed text (the text resets after a short pause, and repeating a letter
22
24
  * cycles); on the closed trigger it selects the match without opening.
23
25
  */
24
- export declare function SelectDropdown({ options, value, onChange, label, placeholder, className, size, }: SelectDropdownProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function SelectDropdown({ options, value, onChange, label, labelPlacement, placeholder, className, size, }: SelectDropdownProps): import("react/jsx-runtime").JSX.Element;
25
27
  export {};
26
28
  //# sourceMappingURL=SelectDropdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SelectDropdown.d.ts","sourceRoot":"","sources":["../../../src/components/ui/SelectDropdown.tsx"],"names":[],"mappings":"AASA,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,mBAAmB;IAC3B,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,KAAK,EACL,WAAyB,EACzB,SAAS,EACT,IAAW,GACZ,EAAE,mBAAmB,2CAsQrB"}
1
+ {"version":3,"file":"SelectDropdown.d.ts","sourceRoot":"","sources":["../../../src/components/ui/SelectDropdown.tsx"],"names":[],"mappings":"AASA,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,mBAAmB;IAC3B,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,cAAc,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,KAAK,EACL,cAAwB,EACxB,WAAyB,EACzB,SAAS,EACT,IAAW,GACZ,EAAE,mBAAmB,2CAqRrB"}
@@ -13,5 +13,6 @@ export declare function useFieldControl({ id, error, hint, describedBy, invalid,
13
13
  describedBy: string | undefined;
14
14
  invalid: boolean | "false" | "true" | "grammar" | "spelling" | undefined;
15
15
  required: boolean | undefined;
16
+ placeholderLabel: string | undefined;
16
17
  };
17
18
  //# sourceMappingURL=useFieldControl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFieldControl.d.ts","sourceRoot":"","sources":["../../../src/components/ui/useFieldControl.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAClC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,KAAK,EACL,IAAI,EACJ,WAAW,EACX,OAAO,EACP,QAAQ,GACT,EAAE,mBAAmB;;;;;;;EAmBrB"}
1
+ {"version":3,"file":"useFieldControl.d.ts","sourceRoot":"","sources":["../../../src/components/ui/useFieldControl.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAClC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,KAAK,EACL,IAAI,EACJ,WAAW,EACX,OAAO,EACP,QAAQ,GACT,EAAE,mBAAmB;;;;;;;;EAoBrB"}
package/dist/index.js CHANGED
@@ -3168,139 +3168,140 @@ function ar() {
3168
3168
  }
3169
3169
  //#endregion
3170
3170
  //#region src/components/ui/SelectDropdown.tsx
3171
- function or({ options: e, value: t, onChange: n, label: r, placeholder: i = "Select...", className: a, size: o = "md" }) {
3172
- let [s, c] = h(-1), { open: u, show: d, close: f, rootRef: p, anchorRef: g, triggerRef: y, popupRef: b, labelId: x, popupId: S, triggerAria: C } = Qn({
3171
+ function or({ options: e, value: t, onChange: n, label: r, labelPlacement: i = "above", placeholder: a = "Select...", className: o, size: s = "md" }) {
3172
+ let [c, u] = h(-1), { open: d, show: f, close: p, rootRef: g, anchorRef: y, triggerRef: b, popupRef: x, labelId: S, popupId: C, triggerAria: w } = Qn({
3173
3173
  haspopup: "listbox",
3174
3174
  matchWidth: !0,
3175
3175
  labelled: !!r
3176
- }), w = ar(), T = m(!1), D = m(!1), k = (e) => `${S}-option-${e}`, A = e.find((e) => e.value === t), j = m(!1);
3176
+ }), T = ar(), D = m(!1), k = m(!1), A = (e) => `${C}-option-${e}`, j = i === "placeholder" && r && !t ? void 0 : e.find((e) => e.value === t), M = m(!1);
3177
3177
  l(() => {
3178
- j.current && !u && w.clear(), j.current = u;
3178
+ M.current && !d && T.clear(), M.current = d;
3179
3179
  }), l(() => {
3180
- !u || !T.current || (T.current = !1, document.getElementById(k(s))?.scrollIntoView({ block: "nearest" }));
3180
+ !d || !D.current || (D.current = !1, document.getElementById(A(c))?.scrollIntoView({ block: "nearest" }));
3181
3181
  });
3182
- function M() {
3182
+ function N() {
3183
3183
  let n = e.findIndex((e) => e.value === t);
3184
- w.clear(), F(n >= 0 && !e[n].disabled ? n : L(1)), d();
3185
- }
3186
- function N(e) {
3187
- n(e.value), f("select");
3184
+ T.clear(), I(n >= 0 && !e[n].disabled ? n : R(1)), f();
3188
3185
  }
3189
3186
  function P(e) {
3190
- D.current && (D.current = !1, e.detail === 0) || (u ? f("toggle") : M());
3187
+ n(e.value), p("select");
3191
3188
  }
3192
3189
  function F(e) {
3193
- T.current = !0, c(e);
3190
+ k.current && (k.current = !1, e.detail === 0) || (d ? p("toggle") : N());
3191
+ }
3192
+ function I(e) {
3193
+ D.current = !0, u(e);
3194
3194
  }
3195
- function I(t, n) {
3195
+ function L(t, n) {
3196
3196
  for (let r = t + n; r >= 0 && r < e.length; r += n) if (!e[r].disabled) return r;
3197
3197
  return t;
3198
3198
  }
3199
- function L(t) {
3200
- let n = I(t === 1 ? -1 : e.length, t);
3199
+ function R(t) {
3200
+ let n = L(t === 1 ? -1 : e.length, t);
3201
3201
  return n >= 0 && n < e.length ? n : -1;
3202
3202
  }
3203
- function R(r) {
3203
+ function z(r) {
3204
3204
  r.preventDefault();
3205
- let i = w.push(r.key), a = e.map((e) => e.label), o = (t) => !!e[t].disabled;
3206
- if (u) {
3207
- let e = ir(a, o, s, i);
3208
- e !== -1 && F(e);
3205
+ let i = T.push(r.key), a = e.map((e) => e.label), o = (t) => !!e[t].disabled;
3206
+ if (d) {
3207
+ let e = ir(a, o, c, i);
3208
+ e !== -1 && I(e);
3209
3209
  } else {
3210
3210
  let r = ir(a, o, e.findIndex((e) => e.value === t), i);
3211
3211
  r !== -1 && e[r].value !== t && n(e[r].value);
3212
3212
  }
3213
3213
  }
3214
- function z(e) {
3215
- e.key === " " && D.current && e.preventDefault();
3214
+ function B(e) {
3215
+ e.key === " " && k.current && e.preventDefault();
3216
3216
  }
3217
- function B(t) {
3218
- if (D.current = !1, t.key === " " && (u || w.active()) && (D.current = !0), tr(t) && (t.key !== " " || w.active())) {
3219
- R(t);
3217
+ function ee(t) {
3218
+ if (k.current = !1, t.key === " " && (d || T.active()) && (k.current = !0), tr(t) && (t.key !== " " || T.active())) {
3219
+ z(t);
3220
3220
  return;
3221
3221
  }
3222
- if (!u) {
3223
- (t.key === "ArrowDown" || t.key === "ArrowUp") && (t.preventDefault(), M());
3222
+ if (!d) {
3223
+ (t.key === "ArrowDown" || t.key === "ArrowUp") && (t.preventDefault(), N());
3224
3224
  return;
3225
3225
  }
3226
3226
  switch (t.key) {
3227
3227
  case "ArrowDown":
3228
- t.preventDefault(), F(I(s, 1));
3228
+ t.preventDefault(), I(L(c, 1));
3229
3229
  break;
3230
3230
  case "ArrowUp":
3231
- t.preventDefault(), F(I(s, -1));
3231
+ t.preventDefault(), I(L(c, -1));
3232
3232
  break;
3233
3233
  case "Home":
3234
3234
  t.preventDefault();
3235
3235
  {
3236
- let e = L(1);
3237
- e !== -1 && F(e);
3236
+ let e = R(1);
3237
+ e !== -1 && I(e);
3238
3238
  }
3239
3239
  break;
3240
3240
  case "End":
3241
3241
  t.preventDefault();
3242
3242
  {
3243
- let e = L(-1);
3244
- e !== -1 && F(e);
3243
+ let e = R(-1);
3244
+ e !== -1 && I(e);
3245
3245
  }
3246
3246
  break;
3247
3247
  case "Enter":
3248
3248
  case " ": {
3249
3249
  t.preventDefault();
3250
- let n = e[s];
3251
- n && !n.disabled && N(n);
3250
+ let n = e[c];
3251
+ n && !n.disabled && P(n);
3252
3252
  break;
3253
3253
  }
3254
3254
  case "Escape":
3255
- t.preventDefault(), f("escape");
3255
+ t.preventDefault(), p("escape");
3256
3256
  break;
3257
3257
  }
3258
3258
  }
3259
3259
  return /* @__PURE__ */ v("div", {
3260
- className: K("flex flex-col gap-1.5", a),
3261
- ref: p,
3260
+ className: K("flex flex-col gap-1.5", o),
3261
+ ref: g,
3262
3262
  children: [r && /* @__PURE__ */ _("span", {
3263
- id: x,
3264
- className: "ui-field-label font-label text-xs font-medium",
3263
+ id: S,
3264
+ className: K("ui-field-label font-label text-xs font-medium", i === "placeholder" && "sr-only"),
3265
3265
  children: r
3266
3266
  }), /* @__PURE__ */ v("div", {
3267
3267
  className: "relative",
3268
- ref: g,
3268
+ ref: y,
3269
3269
  children: [
3270
3270
  /* @__PURE__ */ _("button", {
3271
3271
  type: "button",
3272
- onClick: P,
3273
- onKeyDown: B,
3274
- onKeyUp: z,
3275
- ref: y,
3276
- ...C,
3277
- "aria-activedescendant": u && s >= 0 ? k(s) : void 0,
3278
- className: K("font-control flex w-full cursor-pointer items-center rounded-md text-xs", "bg-bg-secondary border-border text-text-primary border", "hover:bg-bg-hover hover:border-border-strong", "transition-colors duration-[100ms]", "focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2", o === "sm" ? "h-6 px-2 pr-6" : "h-7 px-2 pr-7"),
3279
- children: A ? A.label : /* @__PURE__ */ _("span", {
3272
+ onClick: F,
3273
+ onKeyDown: ee,
3274
+ onKeyUp: B,
3275
+ ref: b,
3276
+ ...w,
3277
+ "aria-activedescendant": d && c >= 0 ? A(c) : void 0,
3278
+ className: K("font-control flex w-full cursor-pointer items-center rounded-md text-xs", "bg-bg-secondary border-border text-text-primary border", "hover:bg-bg-hover hover:border-border-strong", "transition-colors duration-[100ms]", "focus-visible:outline-accent focus-visible:outline-2 focus-visible:outline-offset-2", s === "sm" ? "h-6 px-2 pr-6" : "h-7 px-2 pr-7"),
3279
+ children: j ? j.label : /* @__PURE__ */ _("span", {
3280
3280
  className: "text-text-disabled",
3281
- children: i
3281
+ "aria-hidden": i === "placeholder" && r ? !0 : void 0,
3282
+ children: i === "placeholder" && r ? r : a
3282
3283
  })
3283
3284
  }),
3284
- /* @__PURE__ */ _(O, { className: K("text-text-tertiary pointer-events-none absolute top-1/2 h-3 w-3 -translate-y-1/2 transition-transform", o === "sm" ? "right-1.5" : "right-2", u && "rotate-180") }),
3285
- u && /* @__PURE__ */ _("div", {
3286
- ref: b,
3285
+ /* @__PURE__ */ _(O, { className: K("text-text-tertiary pointer-events-none absolute top-1/2 h-3 w-3 -translate-y-1/2 transition-transform", s === "sm" ? "right-1.5" : "right-2", d && "rotate-180") }),
3286
+ d && /* @__PURE__ */ _("div", {
3287
+ ref: x,
3287
3288
  className: K("bg-bg-tertiary border-border-strong animate-scale-in fixed top-0 left-0 z-[100] rounded-md border shadow-lg", "overflow-hidden"),
3288
3289
  children: /* @__PURE__ */ _("ul", {
3289
- id: S,
3290
+ id: C,
3290
3291
  className: "max-h-48 overflow-y-auto py-1",
3291
3292
  role: "listbox",
3292
3293
  children: e.map((e, n) => /* @__PURE__ */ v("li", {
3293
- id: k(n),
3294
+ id: A(n),
3294
3295
  role: "option",
3295
3296
  "aria-selected": e.value === t,
3296
3297
  "aria-disabled": e.disabled || void 0,
3297
3298
  onClick: () => {
3298
- e.disabled || N(e);
3299
+ e.disabled || P(e);
3299
3300
  },
3300
3301
  onMouseEnter: () => {
3301
- e.disabled || c(n);
3302
+ e.disabled || u(n);
3302
3303
  },
3303
- className: K("font-control flex h-7 w-full items-center gap-2 px-3 text-xs", !e.disabled && "cursor-pointer", "transition-colors duration-[100ms]", e.disabled ? "text-text-disabled cursor-not-allowed" : n === s ? "bg-accent-muted text-accent" : e.value === t ? "text-accent" : "text-text-secondary hover:text-text-primary hover:bg-bg-hover"),
3304
+ className: K("font-control flex h-7 w-full items-center gap-2 px-3 text-xs", !e.disabled && "cursor-pointer", "transition-colors duration-[100ms]", e.disabled ? "text-text-disabled cursor-not-allowed" : n === c ? "bg-accent-muted text-accent" : e.value === t ? "text-accent" : "text-text-secondary hover:text-text-primary hover:bg-bg-hover"),
3304
3305
  children: [/* @__PURE__ */ _("span", {
3305
3306
  className: "flex-1 truncate",
3306
3307
  children: e.label
@@ -9199,7 +9200,8 @@ function Ts({ id: e, error: t, hint: n, describedBy: r, invalid: i, required: a
9199
9200
  hintId: f,
9200
9201
  describedBy: wr(r, s?.describedBy, f, d),
9201
9202
  invalid: i ?? (!!t || s?.invalid || void 0),
9202
- required: a ?? s?.required
9203
+ required: a ?? s?.required,
9204
+ placeholderLabel: s?.placeholderLabel
9203
9205
  };
9204
9206
  }
9205
9207
  //#endregion
@@ -9987,51 +9989,105 @@ function nc({ value: e, onChange: t, placeholder: n = "Search...", className: r
9987
9989
  }
9988
9990
  //#endregion
9989
9991
  //#region src/components/ui/Select.tsx
9990
- var rc = a(({ label: e, error: t, options: n, className: r, id: i, hint: a, controlSize: o, required: s, "aria-describedby": c, "aria-invalid": l, ...u }, d) => {
9991
- let f = Ts({
9992
- id: i,
9993
- error: t,
9994
- hint: a,
9995
- describedBy: c,
9996
- invalid: l,
9997
- required: s
9998
- }), { inputId: p, errorId: m, hintId: h } = f, g = Un(o);
9999
- return /* @__PURE__ */ v("div", {
9992
+ var rc = a(({ label: e, labelPlacement: t = "above", error: n, options: r, className: i, id: a, hint: o, controlSize: c, required: u, "aria-describedby": d, "aria-invalid": f, ...p }, g) => {
9993
+ let y = Ts({
9994
+ id: a,
9995
+ error: n,
9996
+ hint: o,
9997
+ describedBy: d,
9998
+ invalid: f,
9999
+ required: u
10000
+ }), { inputId: b, errorId: x, hintId: S } = y, C = Un(c), w = m(null), T = s((e) => {
10001
+ if (w.current = e, typeof g == "function") return g(e);
10002
+ g && (g.current = e);
10003
+ }, [g]), E = t === "placeholder" && e ? e : e ? void 0 : y.placeholderLabel, D = !!E && !p.multiple && (!p.size || p.size <= 1), k = p.value !== void 0, [A, j] = h(p.value ?? p.defaultValue ?? ""), M = k ? p.value ?? "" : A, N = D && M === "";
10004
+ return l(() => {
10005
+ let e = w.current;
10006
+ if (!e || k || !D) return;
10007
+ let t = !0;
10008
+ queueMicrotask(() => {
10009
+ t && j(e.value);
10010
+ });
10011
+ let n = e.form, r, i = () => {
10012
+ clearTimeout(r), r = setTimeout(() => {
10013
+ j(e.value);
10014
+ }, 0);
10015
+ };
10016
+ n?.addEventListener("reset", i);
10017
+ let a = new MutationObserver(() => {
10018
+ j(e.value);
10019
+ });
10020
+ return a.observe(e, {
10021
+ childList: !0,
10022
+ subtree: !0,
10023
+ attributes: !0,
10024
+ attributeFilter: ["value", "selected"]
10025
+ }), () => {
10026
+ t = !1, n?.removeEventListener("reset", i), a.disconnect(), clearTimeout(r);
10027
+ };
10028
+ }, [
10029
+ k,
10030
+ D,
10031
+ p.form
10032
+ ]), /* @__PURE__ */ v("div", {
10000
10033
  className: "flex flex-col gap-1.5",
10001
10034
  children: [
10002
- a && /* @__PURE__ */ _("p", {
10003
- id: h,
10035
+ o && /* @__PURE__ */ _("p", {
10036
+ id: S,
10004
10037
  className: "text-text-secondary text-xs",
10005
- children: a
10038
+ children: o
10006
10039
  }),
10007
- (e || t) && /* @__PURE__ */ v("div", {
10008
- className: "flex items-center justify-between",
10040
+ (e || n) && /* @__PURE__ */ v("div", {
10041
+ className: K("flex items-center justify-between", D && !n && "sr-only"),
10009
10042
  children: [e && /* @__PURE__ */ _("label", {
10010
- htmlFor: p,
10011
- className: "ui-field-label font-label text-xs font-medium",
10043
+ htmlFor: b,
10044
+ className: K("ui-field-label font-label text-xs font-medium", D && "sr-only"),
10012
10045
  children: e
10013
- }), t && /* @__PURE__ */ _("p", {
10014
- id: m,
10046
+ }), n && /* @__PURE__ */ _("p", {
10047
+ id: x,
10015
10048
  className: "text-2xs text-red",
10016
- children: t
10049
+ children: n
10017
10050
  })]
10018
10051
  }),
10019
10052
  /* @__PURE__ */ v("div", {
10020
10053
  className: "relative",
10021
- children: [/* @__PURE__ */ _("select", {
10022
- ref: d,
10023
- "data-control-size": g,
10024
- required: f.required,
10025
- "aria-invalid": f.invalid,
10026
- "aria-describedby": f.describedBy,
10027
- id: p,
10028
- className: K("ui-sized-control bg-bg-secondary text-text-primary font-control h-7 w-full px-2 pr-7 text-xs", "appearance-none rounded-md border", "transition-colors duration-[100ms]", "focus:border-accent focus:outline-none", "disabled:cursor-not-allowed disabled:opacity-40", "cursor-pointer", t ? "border-red" : "border-border", r),
10029
- ...u,
10030
- children: n.map((e) => /* @__PURE__ */ _("option", {
10031
- value: e.value,
10032
- children: e.label
10033
- }, e.value))
10034
- }), /* @__PURE__ */ _(O, { className: "text-text-tertiary pointer-events-none absolute top-1/2 right-2.5 h-3.5 w-3.5 -translate-y-1/2" })]
10054
+ "data-control-size": D ? C : void 0,
10055
+ children: [
10056
+ /* @__PURE__ */ v("select", {
10057
+ ref: D ? T : g,
10058
+ "data-control-size": C,
10059
+ required: y.required,
10060
+ "aria-invalid": y.invalid,
10061
+ "aria-describedby": y.describedBy,
10062
+ id: b,
10063
+ className: K("ui-sized-control bg-bg-secondary text-text-primary font-control h-7 w-full px-2 pr-7 text-xs", "appearance-none rounded-md border", "transition-colors duration-[100ms]", "focus:border-accent focus:outline-none", "disabled:cursor-not-allowed disabled:opacity-40", "cursor-pointer", n ? "border-red" : "border-border", i),
10064
+ ...p,
10065
+ value: D && k ? p.value ?? "" : p.value ?? void 0,
10066
+ defaultValue: D && !k ? p.defaultValue ?? "" : p.defaultValue,
10067
+ onChange: D ? (e) => {
10068
+ k || j(e.currentTarget.value), p.onChange?.(e);
10069
+ } : p.onChange,
10070
+ style: N ? {
10071
+ ...p.style,
10072
+ color: "transparent"
10073
+ } : p.style,
10074
+ children: [D && !r.some((e) => e.value === "") && /* @__PURE__ */ _("option", {
10075
+ value: "",
10076
+ disabled: !0,
10077
+ hidden: !0
10078
+ }), r.map((e) => /* @__PURE__ */ _("option", {
10079
+ value: e.value,
10080
+ style: D ? { color: "var(--text-primary)" } : void 0,
10081
+ children: e.label
10082
+ }, e.value))]
10083
+ }),
10084
+ N && /* @__PURE__ */ _("span", {
10085
+ "aria-hidden": "true",
10086
+ className: "ui-select-placeholder text-text-disabled font-control pointer-events-none absolute top-1/2 -translate-y-1/2 truncate",
10087
+ children: E
10088
+ }),
10089
+ /* @__PURE__ */ _(O, { className: "text-text-tertiary pointer-events-none absolute top-1/2 right-2.5 h-3.5 w-3.5 -translate-y-1/2" })
10090
+ ]
10035
10091
  })
10036
10092
  ]
10037
10093
  });
@@ -10849,27 +10905,28 @@ function Wc({ children: e }) {
10849
10905
  }
10850
10906
  //#endregion
10851
10907
  //#region src/components/ui/FormField.tsx
10852
- function Gc({ id: e, label: t, hint: n, error: r, required: i, optionalLabel: a, children: o, className: s, ...c }) {
10853
- let l = u(), d = e ?? l, f = n ? `${d}-hint` : void 0, p = r ? `${d}-error` : void 0, m = {
10854
- id: d,
10855
- "aria-describedby": wr(f, p),
10908
+ function Gc({ id: e, label: t, hint: n, error: r, required: i, optionalLabel: a, labelPlacement: o = "above", children: s, className: c, ...l }) {
10909
+ let d = u(), f = e ?? d, p = n ? `${f}-hint` : void 0, m = r ? `${f}-error` : void 0, h = o === "placeholder" && typeof t == "string" && t ? t : void 0, g = {
10910
+ id: f,
10911
+ "aria-describedby": wr(p, m),
10856
10912
  "aria-invalid": !!r || void 0,
10857
10913
  required: i
10858
10914
  };
10859
10915
  return /* @__PURE__ */ _(Cr.Provider, {
10860
10916
  value: {
10861
- id: d,
10862
- describedBy: wr(f, p),
10917
+ id: f,
10918
+ describedBy: wr(p, m),
10863
10919
  invalid: !!r,
10864
- required: i
10920
+ required: i,
10921
+ placeholderLabel: h
10865
10922
  },
10866
10923
  children: /* @__PURE__ */ v("div", {
10867
- ...c,
10868
- className: K("flex flex-col gap-1.5", s),
10924
+ ...l,
10925
+ className: K("flex flex-col gap-1.5", c),
10869
10926
  children: [
10870
10927
  /* @__PURE__ */ v("label", {
10871
- htmlFor: d,
10872
- className: "ui-field-label font-label text-xs font-medium",
10928
+ htmlFor: f,
10929
+ className: K("ui-field-label font-label text-xs font-medium", h && "sr-only"),
10873
10930
  children: [
10874
10931
  t,
10875
10932
  i && /* @__PURE__ */ _("span", {
@@ -10880,16 +10937,16 @@ function Gc({ id: e, label: t, hint: n, error: r, required: i, optionalLabel: a,
10880
10937
  ]
10881
10938
  }),
10882
10939
  n && /* @__PURE__ */ _("p", {
10883
- id: f,
10940
+ id: p,
10884
10941
  className: "text-text-secondary text-xs",
10885
10942
  children: n
10886
10943
  }),
10887
10944
  r && /* @__PURE__ */ _("p", {
10888
- id: p,
10945
+ id: m,
10889
10946
  className: "text-red text-xs",
10890
10947
  children: r
10891
10948
  }),
10892
- typeof o == "function" ? o(m) : o
10949
+ typeof s == "function" ? s(g) : s
10893
10950
  ]
10894
10951
  })
10895
10952
  });
package/dist/styles.css CHANGED
@@ -2048,6 +2048,23 @@ button.ui-sized-control[data-control-size="comfortable"] {
2048
2048
  select.ui-sized-control[data-control-size="comfortable"] {
2049
2049
  padding-inline-end: 2.25rem;
2050
2050
  }
2051
+ .ui-select-placeholder {
2052
+ left: calc(var(--spacing) * 2);
2053
+ right: calc(var(--spacing) * 7);
2054
+ font-size: 0.75rem;
2055
+ }
2056
+ [data-control-size="compact"] > .ui-select-placeholder {
2057
+ left: var(--ui-control-padding-x, calc(var(--spacing) * 2));
2058
+ font-size: var(--ui-control-font-size, 0.75rem);
2059
+ }
2060
+ [data-control-size="comfortable"] > .ui-select-placeholder {
2061
+ left: 0.75rem;
2062
+ right: 2.25rem;
2063
+ font-size: 1rem;
2064
+ }
2065
+ select:disabled + .ui-select-placeholder {
2066
+ opacity: 0.4;
2067
+ }
2051
2068
  .ui-password-input[data-control-size="comfortable"] {
2052
2069
  padding-inline-end: 44px;
2053
2070
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leonardaustin/ui",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Reusable React component library and application shell for Leonard Austin projects.",
5
5
  "license": "MIT",
6
6
  "type": "module",