@jirawatpyk/aura-react 5.11.0 → 5.12.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.
@@ -55,7 +55,7 @@ const FilterBar = React.forwardRef(function FilterBar2(props, ref) {
55
55
  ref,
56
56
  role: "region",
57
57
  "aria-label": props.label || t.filters,
58
- className: cx("aura-filterbar", props.className)
58
+ className: cx("aura-filterbar", props.searchGrow && "aura-filterbar--grow", props.className)
59
59
  },
60
60
  /* @__PURE__ */ React.createElement("div", { className: "aura-filterbar__row" }, hasSearch ? /* @__PURE__ */ React.createElement("div", { className: "aura-input has-icon aura-filterbar__search" }, /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconSearch, null), className: "aura-input__icon" }), /* @__PURE__ */ React.createElement(
61
61
  "input",
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+ import * as React from "react";
3
+ import { Select, FILTER_KEY } from "./Select.js";
4
+ import { omit } from "./internal.js";
5
+ const FilterSelect = React.forwardRef(function FilterSelect2(props, ref) {
6
+ const onChange = props.onChange;
7
+ const pass = Object.assign(omit(props, ["label", "allLabel", "onChange"]), {
8
+ [FILTER_KEY]: { name: props.label, allLabel: props.allLabel },
9
+ onChange: onChange ? function(e) {
10
+ onChange(e.target.value);
11
+ } : void 0
12
+ });
13
+ return /* @__PURE__ */ React.createElement(Select, { ...pass, ref });
14
+ });
15
+ export {
16
+ FilterSelect
17
+ };
@@ -7,6 +7,7 @@ import { useDensity } from "./locale.js";
7
7
  import { cx, devWarnOnce, omit, uid, useIsoLayoutEffect, useMergedRef, useMounted } from "./internal.js";
8
8
  import { IconCheck, IconChevronDown } from "./icons.js";
9
9
  const h = React.createElement;
10
+ const FILTER_KEY = "__auraFilter";
10
11
  const FORM_KEYS = ["name", "form", "value", "defaultValue", "disabled", "autoComplete", "onInput", "onInvalid"];
11
12
  const HANDLED_KEYS = FORM_KEYS.concat([
12
13
  "onChange",
@@ -40,20 +41,49 @@ function readItems(el) {
40
41
  };
41
42
  });
42
43
  }
43
- function initialLabel(props) {
44
+ function textOf(n) {
45
+ if (n == null || typeof n === "boolean") return "";
46
+ if (typeof n === "string" || typeof n === "number") return String(n);
47
+ if (Array.isArray(n)) return n.map(textOf).join("");
48
+ return React.isValidElement(n) ? textOf(n.props.children) : "";
49
+ }
50
+ function childOptions(children, off) {
51
+ const out = [];
52
+ React.Children.forEach(children, function(c) {
53
+ if (!React.isValidElement(c)) return;
54
+ const p = c.props;
55
+ if (c.type === "optgroup" || c.type === React.Fragment)
56
+ out.push.apply(out, childOptions(p.children, off || c.type === "optgroup" && !!p.disabled));
57
+ else if (c.type === "option") {
58
+ const label = textOf(p.children);
59
+ out.push({ value: p.value != null ? String(p.value) : label, label, disabled: off || !!p.disabled });
60
+ }
61
+ });
62
+ return out;
63
+ }
64
+ function initialLabel(props, filter) {
44
65
  const opts = (props.options || []).map(function(o) {
45
66
  return typeof o === "object" ? o : { value: o, label: o };
46
- });
47
- const v = props.value !== void 0 && props.value !== null ? String(props.value) : props.defaultValue !== void 0 && props.defaultValue !== null ? String(props.defaultValue) : props.placeholder ? "" : opts[0] ? opts[0].value : "";
67
+ }).concat(childOptions(props.children));
68
+ const first = opts.filter(function(o) {
69
+ return !o.disabled;
70
+ })[0];
71
+ const v = props.value !== void 0 && props.value !== null ? String(props.value) : props.defaultValue !== void 0 && props.defaultValue !== null ? String(props.defaultValue) : props.placeholder ? "" : first ? first.value : "";
48
72
  const hit = opts.filter(function(o) {
49
73
  return o.value === v;
50
74
  })[0];
51
- if (hit) return { label: hit.label, empty: false };
52
- return { label: props.placeholder || "", empty: true };
75
+ if (hit)
76
+ return {
77
+ label: filter && filter.allLabel && opts[0] && hit.value === opts[0].value ? filter.allLabel : hit.label,
78
+ full: hit.label,
79
+ empty: false
80
+ };
81
+ return { label: props.placeholder || "", full: props.placeholder || "", empty: true };
53
82
  }
54
83
  const Select = React.forwardRef(function Select2(props, ref) {
55
84
  const auto = uid(), id = props.id || auto;
56
- const rest = omit(props, FIELD_KEYS.concat(["children"]));
85
+ const filter = props[FILTER_KEY];
86
+ const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY]));
57
87
  const opts = (props.options || []).map(function(o) {
58
88
  const v = typeof o === "object" ? o : { value: o, label: o };
59
89
  return /* @__PURE__ */ React.createElement("option", { key: v.value, value: v.value, disabled: v.disabled }, v.label);
@@ -68,7 +98,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
68
98
  const live = !native && mounted;
69
99
  const density = useDensity();
70
100
  React.useEffect(function() {
71
- if (props.label || props["aria-label"] || props["aria-labelledby"] || props.title) return;
101
+ if (filter || props.label || props["aria-label"] || props["aria-labelledby"] || props.title) return;
72
102
  if (document.querySelector('label[for="' + id.replace(/["\\]/g, "\\$&") + '"]')) return;
73
103
  devWarnOnce(
74
104
  "select-name",
@@ -77,20 +107,24 @@ const Select = React.forwardRef(function Select2(props, ref) {
77
107
  }, []);
78
108
  const selRef = React.useRef(null), trigRef = React.useRef(null), listRef = React.useRef(null);
79
109
  const shown = React.useState(function() {
80
- return initialLabel(props);
110
+ return initialLabel(props, filter);
81
111
  });
82
112
  const openState = React.useState(false), open = openState[0];
83
113
  const activeState = React.useState(-1), active = activeState[0];
84
114
  const items = React.useState([]);
85
115
  const pos = React.useState(null);
86
116
  const typed = React.useRef({ text: "", at: 0 });
117
+ const allLabel = React.useRef(void 0);
118
+ allLabel.current = filter ? filter.allLabel : void 0;
87
119
  const sync = React.useCallback(function() {
88
120
  const el = selRef.current;
89
121
  if (!el) return;
90
122
  const o = el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null;
91
- const next = o ? { label: o.textContent || "", empty: o.value === "" } : { label: "", empty: true };
123
+ const text = o ? o.textContent || "" : "";
124
+ const short = allLabel.current && el.selectedIndex === 0 ? allLabel.current : text;
125
+ const next = o ? { label: short, full: text, empty: o.value === "" } : { label: "", full: "", empty: true };
92
126
  shown[1](function(cur) {
93
- return cur.label === next.label && cur.empty === next.empty ? cur : next;
127
+ return cur.label === next.label && cur.full === next.full && cur.empty === next.empty ? cur : next;
94
128
  });
95
129
  }, []);
96
130
  const hook = React.useCallback(
@@ -281,12 +315,16 @@ const Select = React.forwardRef(function Select2(props, ref) {
281
315
  const t = trigRef.current && trigRef.current.parentElement;
282
316
  if (!t) return;
283
317
  const r = t.getBoundingClientRect(), below = window.innerHeight - r.bottom - 8, above = r.top - 8, want = listRef.current ? listRef.current.scrollHeight : 320, up = below < Math.min(want, 200) && above > below;
318
+ const lw = listRef.current ? Math.max(r.width, listRef.current.offsetWidth) : r.width;
319
+ const rtl = getComputedStyle(t).direction === "rtl";
320
+ const left = lw <= r.width ? r.left : Math.max(8, Math.min(rtl ? r.right - lw : r.left, window.innerWidth - lw - 8));
284
321
  pos[1]({
285
- left: r.left,
322
+ left,
286
323
  width: r.width,
287
324
  top: up ? void 0 : r.bottom + 4,
288
325
  bottom: up ? window.innerHeight - r.top + 4 : void 0,
289
- maxHeight: Math.min(320, (up ? above : below) - 4)
326
+ maxHeight: Math.min(320, (up ? above : below) - 4),
327
+ rtl
290
328
  });
291
329
  }
292
330
  place();
@@ -319,7 +357,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
319
357
  [open, active, pos[0] != null]
320
358
  );
321
359
  const described = [describedBy(id, props), rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
322
- const field = (child) => /* @__PURE__ */ React.createElement(
360
+ const field = (child) => filter ? child : /* @__PURE__ */ React.createElement(
323
361
  Field,
324
362
  {
325
363
  id,
@@ -334,6 +372,21 @@ const Select = React.forwardRef(function Select2(props, ref) {
334
372
  },
335
373
  child
336
374
  );
375
+ const nameId = id + "-name";
376
+ const face = filter ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { id: nameId, className: "aura-filterselect__name", "aria-hidden": true }, filter.name), /* @__PURE__ */ React.createElement("span", { className: "aura-filterselect__value", "aria-hidden": shown[0].label !== shown[0].full || void 0 }, shown[0].label || " "), shown[0].label !== shown[0].full ? /* @__PURE__ */ React.createElement("span", { className: "aura-sr-only" }, shown[0].full) : null, /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconChevronDown, null), className: "aura-filterselect__chevron" })) : null;
377
+ if (!live && filter)
378
+ return /* @__PURE__ */ React.createElement("div", { className: cx("aura-filterselect", props.disabled && "is-disabled", props.className) }, /* @__PURE__ */ React.createElement("span", { className: "aura-filterselect__face", "aria-hidden": true, style: props.style }, face), h(
379
+ "select",
380
+ Object.assign(extra, omit(rest, ["style"]), {
381
+ ref: selMerged,
382
+ id,
383
+ className: "aura-filterselect__native",
384
+ "aria-label": rest["aria-label"] || (rest["aria-labelledby"] ? void 0 : filter.name),
385
+ "aria-describedby": described
386
+ }),
387
+ opts,
388
+ props.children
389
+ ));
337
390
  if (!live)
338
391
  return field(
339
392
  /* @__PURE__ */ React.createElement("div", { className: cx("aura-input aura-select", props.icon && "has-icon") }, props.icon ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, h(
@@ -354,7 +407,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
354
407
  const optId = function(i) {
355
408
  return id + "-opt-" + i;
356
409
  };
357
- const labelledBy = rest["aria-labelledby"] || (props.label ? labelId : void 0);
410
+ const labelledBy = rest["aria-labelledby"] || (filter && !rest["aria-label"] ? nameId : props.label ? labelId : void 0);
358
411
  const selIndex = selRef.current ? selRef.current.selectedIndex : -1;
359
412
  const option = function(it) {
360
413
  const sel = selIndex === it.index;
@@ -400,6 +453,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
400
453
  {
401
454
  ref: listRef,
402
455
  "data-density": density,
456
+ dir: pos[0] ? pos[0].rtl ? "rtl" : "ltr" : void 0,
403
457
  className: "aura-combo__popover aura-select__popover",
404
458
  style: pos[0] ? {
405
459
  left: pos[0].left,
@@ -432,10 +486,17 @@ const Select = React.forwardRef(function Select2(props, ref) {
432
486
  /* @__PURE__ */ React.createElement(
433
487
  "div",
434
488
  {
435
- className: cx("aura-input aura-select aura-select--custom", props.icon && "has-icon", open && "is-open"),
489
+ className: cx(
490
+ filter ? "aura-filterselect" : "aura-input",
491
+ "aura-select aura-select--custom",
492
+ props.icon && "has-icon",
493
+ open && "is-open",
494
+ filter && props.disabled && "is-disabled",
495
+ filter && props.className
496
+ ),
436
497
  "data-invalid": props.error ? "" : void 0
437
498
  },
438
- props.icon ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null,
499
+ props.icon && !filter ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null,
439
500
  h(
440
501
  "select",
441
502
  Object.assign(extra, pick(rest, FORM_KEYS), {
@@ -469,7 +530,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
469
530
  type: "button",
470
531
  role: "combobox",
471
532
  form: id + "-no-form",
472
- className: "aura-input__control aura-select__trigger",
533
+ className: filter ? "aura-select__trigger aura-filterselect__face" : "aura-input__control aura-select__trigger",
473
534
  disabled: props.disabled,
474
535
  tabIndex: rest.tabIndex,
475
536
  "aria-haspopup": "listbox",
@@ -478,7 +539,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
478
539
  "aria-activedescendant": open && active >= 0 ? optId(active) : void 0,
479
540
  id,
480
541
  "aria-label": rest["aria-label"],
481
- "aria-labelledby": rest["aria-labelledby"],
542
+ "aria-labelledby": rest["aria-labelledby"] || (filter && !rest["aria-label"] ? nameId : void 0),
482
543
  "aria-required": props.required || void 0,
483
544
  "aria-invalid": props.error ? true : void 0,
484
545
  "aria-describedby": described,
@@ -501,13 +562,14 @@ const Select = React.forwardRef(function Select2(props, ref) {
501
562
  if (selRef.current) selRef.current.dispatchEvent(new FocusEvent("focusout", { bubbles: true }));
502
563
  }
503
564
  },
504
- /* @__PURE__ */ React.createElement("span", { id: id + "-value", className: cx("aura-select__value", shown[0].empty && "is-placeholder") }, shown[0].label || " ")
565
+ face || /* @__PURE__ */ React.createElement("span", { id: id + "-value", className: cx("aura-select__value", shown[0].empty && "is-placeholder") }, shown[0].label || " ")
505
566
  ),
506
- /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconChevronDown, null), className: "aura-select__chevron" }),
567
+ filter ? null : /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconChevronDown, null), className: "aura-select__chevron" }),
507
568
  popup
508
569
  )
509
570
  );
510
571
  });
511
572
  export {
573
+ FILTER_KEY,
512
574
  Select
513
575
  };
package/dist/esm/index.js CHANGED
@@ -10,6 +10,7 @@ import { Field } from "./Field.js";
10
10
  import { TextField } from "./TextField.js";
11
11
  import { Textarea } from "./Textarea.js";
12
12
  import { Select } from "./Select.js";
13
+ import { FilterSelect } from "./FilterSelect.js";
13
14
  import { RadioGroup } from "./RadioGroup.js";
14
15
  import { Switch } from "./Switch.js";
15
16
  import { Combobox, defaultFilter } from "./Combobox.js";
@@ -87,6 +88,7 @@ export {
87
88
  Field,
88
89
  FileUpload,
89
90
  FilterBar,
91
+ FilterSelect,
90
92
  FormErrorSummary,
91
93
  Grid,
92
94
  ICONS,
package/dist/index.d.cts CHANGED
@@ -401,6 +401,9 @@ export interface FilterBarProps {
401
401
  actions?: React$1.ReactNode | undefined;
402
402
  /** Accessible name of the region. Default "Filters". */
403
403
  label?: string | undefined;
404
+ /** The search takes all the room the controls leave in its row, instead of stopping at 360px with the count and
405
+ * actions pushed to the end (5.12). */
406
+ searchGrow?: boolean | undefined;
404
407
  className?: string | undefined;
405
408
  }
406
409
  /** One command in the palette. */
@@ -467,6 +470,20 @@ export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.Sel
467
470
  placeholder?: string | undefined;
468
471
  icon?: IconInput | undefined;
469
472
  }
473
+ /** A compact filter for FilterBar (5.12): "Status All ▾", as wide as its words. */
474
+ export interface FilterSelectProps extends Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "onChange" | "value" | "defaultValue" | "multiple" | "size" | "placeholder" | "required" | "children"> {
475
+ /** The filter's name: shown first on the face ("Status") and its accessible name. */
476
+ label: string;
477
+ /** The choices; the first is the "all" choice. Or pass `<option>` children. */
478
+ options?: SelectOption[] | undefined;
479
+ children?: React$1.ReactNode | undefined;
480
+ value?: string | undefined;
481
+ defaultValue?: string | undefined;
482
+ onChange?: ((value: string) => void) | undefined;
483
+ /** A shorter word the face shows while the first option is chosen ("All" for "All statuses"). The list and screen
484
+ * readers keep the option's own text. */
485
+ allLabel?: string | undefined;
486
+ }
470
487
  export type ChoiceOption = string | {
471
488
  value: string;
472
489
  label: string;
@@ -1338,6 +1355,9 @@ export declare const TextField: React$1.ForwardRefExoticComponent<TextFieldProps
1338
1355
  export declare const Textarea: React$1.ForwardRefExoticComponent<TextareaProps & React$1.RefAttributes<HTMLTextAreaElement>>;
1339
1356
  /** A select field: a button that opens an AURA list (5.3), over a real <select> that keeps forms, refs and events. */
1340
1357
  export declare const Select: React$1.ForwardRefExoticComponent<SelectProps & React$1.RefAttributes<HTMLSelectElement>>;
1358
+ /** A compact filter for FilterBar (5.12): a small button reading "Status All ▾" that opens Select's list. The name
1359
+ * is the accessible name, the chosen option its value; forms, refs and the keyboard work as in Select. */
1360
+ export declare const FilterSelect: React$1.ForwardRefExoticComponent<FilterSelectProps & React$1.RefAttributes<HTMLSelectElement>>;
1341
1361
  export declare const RadioGroup: React$1.ForwardRefExoticComponent<RadioGroupProps & React$1.RefAttributes<HTMLFieldSetElement>>;
1342
1362
  export declare const Switch: React$1.ForwardRefExoticComponent<SwitchProps & React$1.RefAttributes<HTMLButtonElement>>;
1343
1363
  /** One value, or with `multiple` any number (chips). Two call signatures so value/onChange are typed for each. */
package/dist/index.d.ts CHANGED
@@ -401,6 +401,9 @@ export interface FilterBarProps {
401
401
  actions?: React$1.ReactNode | undefined;
402
402
  /** Accessible name of the region. Default "Filters". */
403
403
  label?: string | undefined;
404
+ /** The search takes all the room the controls leave in its row, instead of stopping at 360px with the count and
405
+ * actions pushed to the end (5.12). */
406
+ searchGrow?: boolean | undefined;
404
407
  className?: string | undefined;
405
408
  }
406
409
  /** One command in the palette. */
@@ -467,6 +470,20 @@ export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.Sel
467
470
  placeholder?: string | undefined;
468
471
  icon?: IconInput | undefined;
469
472
  }
473
+ /** A compact filter for FilterBar (5.12): "Status All ▾", as wide as its words. */
474
+ export interface FilterSelectProps extends Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "onChange" | "value" | "defaultValue" | "multiple" | "size" | "placeholder" | "required" | "children"> {
475
+ /** The filter's name: shown first on the face ("Status") and its accessible name. */
476
+ label: string;
477
+ /** The choices; the first is the "all" choice. Or pass `<option>` children. */
478
+ options?: SelectOption[] | undefined;
479
+ children?: React$1.ReactNode | undefined;
480
+ value?: string | undefined;
481
+ defaultValue?: string | undefined;
482
+ onChange?: ((value: string) => void) | undefined;
483
+ /** A shorter word the face shows while the first option is chosen ("All" for "All statuses"). The list and screen
484
+ * readers keep the option's own text. */
485
+ allLabel?: string | undefined;
486
+ }
470
487
  export type ChoiceOption = string | {
471
488
  value: string;
472
489
  label: string;
@@ -1338,6 +1355,9 @@ export declare const TextField: React$1.ForwardRefExoticComponent<TextFieldProps
1338
1355
  export declare const Textarea: React$1.ForwardRefExoticComponent<TextareaProps & React$1.RefAttributes<HTMLTextAreaElement>>;
1339
1356
  /** A select field: a button that opens an AURA list (5.3), over a real <select> that keeps forms, refs and events. */
1340
1357
  export declare const Select: React$1.ForwardRefExoticComponent<SelectProps & React$1.RefAttributes<HTMLSelectElement>>;
1358
+ /** A compact filter for FilterBar (5.12): a small button reading "Status All ▾" that opens Select's list. The name
1359
+ * is the accessible name, the chosen option its value; forms, refs and the keyboard work as in Select. */
1360
+ export declare const FilterSelect: React$1.ForwardRefExoticComponent<FilterSelectProps & React$1.RefAttributes<HTMLSelectElement>>;
1341
1361
  export declare const RadioGroup: React$1.ForwardRefExoticComponent<RadioGroupProps & React$1.RefAttributes<HTMLFieldSetElement>>;
1342
1362
  export declare const Switch: React$1.ForwardRefExoticComponent<SwitchProps & React$1.RefAttributes<HTMLButtonElement>>;
1343
1363
  /** One value, or with `multiple` any number (chips). Two call signatures so value/onChange are typed for each. */
package/dist/styles.css CHANGED
@@ -1363,6 +1363,37 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1363
1363
  .aura-filterbar__search { flex-basis: 100%; max-width: none; }
1364
1364
  .aura-filterbar__spacer { display: none; }
1365
1365
  }
1366
+ /* 5.12 searchGrow: the search fills what the controls leave. */
1367
+ .aura-filterbar--grow .aura-filterbar__search { max-width: none; }
1368
+ .aura-filterbar--grow .aura-filterbar__search ~ .aura-filterbar__spacer { display: none; }
1369
+ /* Compact filters sit closer together than full fields. */
1370
+ .aura-filterbar__controls:has(> .aura-filterselect) { gap: var(--aura-space-2); }
1371
+
1372
+ /* 5.12 FilterSelect: a compact face — "Status All ▾" — as wide as its words, over Select's machinery and list. */
1373
+ .aura-filterselect { position: relative; display: inline-flex; width: fit-content; max-width: 100%; vertical-align: middle; font-family: var(--font-sans); }
1374
+ .aura-filterselect__face {
1375
+ box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--aura-space-1); width: auto; max-width: 100%;
1376
+ min-height: var(--aura-input-height); padding-block: 0; padding-inline: var(--aura-space-2) var(--aura-space-1);
1377
+ background: var(--aura-bg-input); color: var(--aura-fg-primary);
1378
+ border: 1px solid var(--aura-border-control); border-radius: var(--aura-radius-md);
1379
+ font: inherit; font-size: 13px; line-height: 18px; text-align: start; cursor: pointer;
1380
+ transition: border-color var(--aura-duration-fast) var(--aura-ease);
1381
+ }
1382
+ .aura-filterselect__face:hover { border-color: var(--aura-fg-secondary); }
1383
+ .aura-filterselect__name { flex: none; white-space: nowrap; color: var(--aura-fg-secondary); }
1384
+ .aura-filterselect__value { min-width: 0; font-weight: 500; overflow-wrap: anywhere; }
1385
+ .aura-filterselect__chevron { flex: none; width: 14px; height: 14px; color: var(--aura-fg-secondary); }
1386
+ .aura-filterselect.is-open .aura-filterselect__chevron { transform: rotate(180deg); }
1387
+ .aura-filterselect__face:focus-visible, .aura-filterselect:has(.aura-filterselect__native:focus-visible) .aura-filterselect__face {
1388
+ outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 1px; border-color: var(--aura-border-strong);
1389
+ }
1390
+ .aura-filterselect__face:focus { outline-style: none; }
1391
+ .aura-filterselect__face:focus-visible { outline-style: solid; }
1392
+ /* Before hydration: the real <select>, invisible, over the face (16px so iOS doesn't zoom when it opens). */
1393
+ .aura-filterselect__native { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; opacity: 0; font-size: 16px; cursor: pointer; appearance: none; }
1394
+ .aura-filterselect.is-disabled .aura-filterselect__face { background: var(--aura-bg-input-disabled); color: var(--aura-fg-secondary); border-color: var(--aura-border-default); cursor: not-allowed; }
1395
+ .aura-filterselect.is-disabled .aura-filterselect__native { cursor: not-allowed; }
1396
+ @media (forced-colors: active) { .aura-filterselect__face { border-color: ButtonText; } }
1366
1397
 
1367
1398
  /* Command palette: a search field over a grouped list, above everything else (it can open from a Dialog) */
1368
1399
  .aura-dialog-layer.aura-command-layer { z-index: var(--aura-z-menu); align-items: flex-start; padding: 12vh var(--aura-space-4) var(--aura-space-4); }
@@ -1365,6 +1365,37 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1365
1365
  .aura-filterbar__search { flex-basis: 100%; max-width: none; }
1366
1366
  .aura-filterbar__spacer { display: none; }
1367
1367
  }
1368
+ /* 5.12 searchGrow: the search fills what the controls leave. */
1369
+ .aura-filterbar--grow .aura-filterbar__search { max-width: none; }
1370
+ .aura-filterbar--grow .aura-filterbar__search ~ .aura-filterbar__spacer { display: none; }
1371
+ /* Compact filters sit closer together than full fields. */
1372
+ .aura-filterbar__controls:has(> .aura-filterselect) { gap: var(--aura-space-2); }
1373
+
1374
+ /* 5.12 FilterSelect: a compact face — "Status All ▾" — as wide as its words, over Select's machinery and list. */
1375
+ .aura-filterselect { position: relative; display: inline-flex; width: fit-content; max-width: 100%; vertical-align: middle; font-family: var(--font-sans); }
1376
+ .aura-filterselect__face {
1377
+ box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--aura-space-1); width: auto; max-width: 100%;
1378
+ min-height: var(--aura-input-height); padding-block: 0; padding-inline: var(--aura-space-2) var(--aura-space-1);
1379
+ background: var(--aura-bg-input); color: var(--aura-fg-primary);
1380
+ border: 1px solid var(--aura-border-control); border-radius: var(--aura-radius-md);
1381
+ font: inherit; font-size: 13px; line-height: 18px; text-align: start; cursor: pointer;
1382
+ transition: border-color var(--aura-duration-fast) var(--aura-ease);
1383
+ }
1384
+ .aura-filterselect__face:hover { border-color: var(--aura-fg-secondary); }
1385
+ .aura-filterselect__name { flex: none; white-space: nowrap; color: var(--aura-fg-secondary); }
1386
+ .aura-filterselect__value { min-width: 0; font-weight: 500; overflow-wrap: anywhere; }
1387
+ .aura-filterselect__chevron { flex: none; width: 14px; height: 14px; color: var(--aura-fg-secondary); }
1388
+ .aura-filterselect.is-open .aura-filterselect__chevron { transform: rotate(180deg); }
1389
+ .aura-filterselect__face:focus-visible, .aura-filterselect:has(.aura-filterselect__native:focus-visible) .aura-filterselect__face {
1390
+ outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 1px; border-color: var(--aura-border-strong);
1391
+ }
1392
+ .aura-filterselect__face:focus { outline-style: none; }
1393
+ .aura-filterselect__face:focus-visible { outline-style: solid; }
1394
+ /* Before hydration: the real <select>, invisible, over the face (16px so iOS doesn't zoom when it opens). */
1395
+ .aura-filterselect__native { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; opacity: 0; font-size: 16px; cursor: pointer; appearance: none; }
1396
+ .aura-filterselect.is-disabled .aura-filterselect__face { background: var(--aura-bg-input-disabled); color: var(--aura-fg-secondary); border-color: var(--aura-border-default); cursor: not-allowed; }
1397
+ .aura-filterselect.is-disabled .aura-filterselect__native { cursor: not-allowed; }
1398
+ @media (forced-colors: active) { .aura-filterselect__face { border-color: ButtonText; } }
1368
1399
 
1369
1400
  /* Command palette: a search field over a grouped list, above everything else (it can open from a Dialog) */
1370
1401
  .aura-dialog-layer.aura-command-layer { z-index: var(--aura-z-menu); align-items: flex-start; padding: 12vh var(--aura-space-4) var(--aura-space-4); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "5.11.0",
3
+ "version": "5.12.0",
4
4
  "description": "AURA Design System — React components (Enterprise Standard, Human Creative). Thai-first: Buddhist-era dates, Thai collation.",
5
5
  "license": "MIT",
6
6
  "type": "module",