@jirawatpyk/aura-react 5.74.0 → 5.76.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/README.md CHANGED
@@ -186,6 +186,20 @@ DataTable with sort and page in the URL (4.17): `onStateChange={({ sort, page })
186
186
  - **Time zone**: `timeZone` on AuraProvider, DatePicker, DateRangePicker and Calendar decides "today" (the marker, `min`/`max="today"`, the first month shown); or pass `today` as an ISO date. `todayIn('Asia/Bangkok')` is exported from the root and `/server`.
187
187
  - **Toasts** queue past three instead of dropping: six in a row all show, in order, three at a time.
188
188
 
189
+ ## 5.76 — Chamber-OS addendum 95 (#194)
190
+
191
+ - **A Dialog on a short screen scrolls as one box:** on a screen 30rem tall or less (480px at the default 16px — 400% zoom on a laptop, a phone on its side with the keyboard up), the head, the description, the body and the footer scroll together inside the dialog, with the footer at the end of the content, instead of a fixed head and footer around a body squeezed to nothing. Focus moves (`data-autofocus`, the first field, the focus trap) work as before, and a focused field scrolls into view with 16px to spare (`scroll-padding`). Above 30rem nothing changes: the footer stays fixed under a scrolling body. The same threshold as ActionBar's and AppShell's bars (5.48). Wide content in the body (a long URL, a `<pre>`) still scrolls sideways there. Scrolled to the footer, the × has scrolled away with the head — Escape and Cancel still close. A `position: sticky` element in the body doesn't stick on a short screen (it scrolls with the content). Dialog only: a Drawer keeps its fixed head and footer. A `.aura-dialog` / `.aura-dialog__body` stand-in in your CSS can go.
192
+
193
+ ## 5.75 — visual audit, set 1
194
+
195
+ - **Status pills are soft in every tone.** Blocked is red-800 on red-100 (dark red-300 on a red wash), Warning amber-800 on amber-100 (dark amber-300 on an amber wash), like Ready, In Progress and To Do, and like Alert and Badge — so a table of statuses reads evenly; the word and the icon carry the meaning. For the one status on a screen that must catch the eye (an overdue total, a failed payment) pass `variant="solid"`: the strong fill, in every tone (`--aura-status-{tone}-solid-bg` / `-fg`; Blocked and Warning are the 5.30 fills). Badge `tone="danger" variant="solid"` is unchanged.
196
+ - **Status words:** Down, Offline and Outage read as blocked; Active, Up, Online and Resolved as ready (`statusTone`, StatusPill, DataTable status columns — which also moves them in a DataTable sort by status). Pass `tone` to keep an earlier look.
197
+ - **List options:** in Select, Combobox and TimePicker, the option under the pointer is shaded only, as a Menu item; moving by keys (or opening with them) shows the focus ring on the active option.
198
+ - **Plain links** (`<a>` with no class) in a PageHeader, a Popover, a Drawer, `.aura-prose`, `.aura-text-body` and `.aura-text-caption` take the accent, as in a Card or a table since 5.30, and are always underlined. Zero specificity: your own link rules win.
199
+ - **Dark theme:** `--aura-bg-selected` is a 20% violet wash (#352f48) and the active SideNav item has a 3px violet edge (`--aura-nav-active-edge`; `createTheme` uses the brand's — regenerate a theme made with an earlier `createTheme`, or its dark edge stays AURA's violet); the DataTable and Table header band is #1f1f22, a step above the card instead of below it.
200
+ - **Progress `tone="warning"`** fills amber-700 in light (`--aura-progress-warning-fill`), amber like the dark theme, no longer rust.
201
+ - **Small fixes:** a sortable `Th` label sits on the same line as a plain one; Tag toggles among FilterBar's controls are as tall as the search; Tag's remove × is 16px in a 24px button.
202
+
189
203
  ## 5.74 — Chamber-OS addendum 94 (#193)
190
204
 
191
205
  - **A PageHeader bar with nothing left in it goes:** when every direct child of the phone bar but the status line (looking through `display: contents` wrappers) is hidden — the `hidden` attribute, `display: none`, `visibility: hidden` set on it, or visually hidden text (an `aria-describedby` note: `.sr-only`-style classes, or an inline absolutely positioned, clipped box) — and no text sits in it, the strip takes no box and the page loses its bar padding and `scroll-padding` (`--aura-page-actions-height` is `0px`), as if there were no bar. A status alone is not a bar. It applies to `actionsBar`'s strip and to the plain `actionsBarBelow` bar, and follows a child being shown or hidden (its `hidden`, `class` or `style`), added or removed, at most once a frame — for a pay bar that hides, not unmounts, once paying succeeds. A bar under a `visibility: hidden` ancestor (a page-enter gate) is not taken for empty. The client marks the header `data-aura-bar="empty"` or `"shown"`; before it does (server HTML), `actionsBar`'s strip whose element children are all `hidden` or visually hidden already goes, though the page padding waits for the client. From the breakpoint up nothing changes. A `:has(> [hidden])` stand-in can go once your PageHeaders render on the client. The `hidden` attribute is trusted as hidden: don't give a hidden child a class that displays it anyway (Tailwind v3's `flex` beats the browser's `[hidden]`).
@@ -488,16 +488,46 @@ window.Aura = (() => {
488
488
  write
489
489
  };
490
490
  }
491
+ var modalityOn = false;
492
+ function setModality(m) {
493
+ const root = document.documentElement;
494
+ if (m === "pointer") {
495
+ if (root.getAttribute("data-aura-modality") !== "pointer") root.setAttribute("data-aura-modality", "pointer");
496
+ } else if (root.hasAttribute("data-aura-modality")) root.removeAttribute("data-aura-modality");
497
+ }
498
+ function useInputModality() {
499
+ React3.useEffect(function() {
500
+ if (modalityOn) return;
501
+ modalityOn = true;
502
+ document.addEventListener(
503
+ "keydown",
504
+ function() {
505
+ setModality("key");
506
+ },
507
+ true
508
+ );
509
+ document.addEventListener(
510
+ "pointerdown",
511
+ function() {
512
+ setModality("pointer");
513
+ },
514
+ true
515
+ );
516
+ }, []);
517
+ }
518
+ function pointerModality() {
519
+ setModality("pointer");
520
+ }
491
521
 
492
522
  // src/display.tsx
493
523
  var React4 = __toESM(require_react(), 1);
494
524
 
495
525
  // src/status.ts
496
526
  var TONE_WORDS = {
497
- ready: ["ready", "done", "complete", "completed", "approved", "live", "passed"],
527
+ ready: ["ready", "done", "complete", "completed", "approved", "live", "passed", "active", "up", "online", "resolved"],
498
528
  progress: ["in progress", "in review", "review", "syncing", "running", "pending"],
499
529
  warning: ["warning", "problem", "degraded", "at risk"],
500
- blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled"]
530
+ blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled", "down", "offline", "outage"]
501
531
  };
502
532
  function toneFor(status) {
503
533
  const s = String(status == null ? "" : status).trim().toLowerCase();
@@ -815,13 +845,19 @@ window.Aura = (() => {
815
845
  };
816
846
  function statusPillElement(props, ref) {
817
847
  const tone2 = props.tone || toneFor(props.children);
818
- const rest = omit(props, ["tone", "className", "children", "wrap", "busy"]);
848
+ const rest = omit(props, ["tone", "className", "children", "wrap", "busy", "variant"]);
819
849
  return /* @__PURE__ */ React4.createElement(
820
850
  "span",
821
851
  {
822
852
  ...rest,
823
853
  ref,
824
- className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
854
+ className: cx(
855
+ "aura-pill",
856
+ "aura-pill--" + tone2,
857
+ props.variant === "solid" && "is-solid",
858
+ props.wrap && "is-wrap",
859
+ props.className
860
+ )
825
861
  },
826
862
  props.busy ? /* @__PURE__ */ React4.createElement(Icon, { name: h2(IconLoaderCircle), size: 12, className: "aura-spin" }) : /* @__PURE__ */ React4.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
827
863
  props.wrap ? /* @__PURE__ */ React4.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
@@ -2395,6 +2431,7 @@ window.Aura = (() => {
2395
2431
  const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY, "readOnly"]));
2396
2432
  const ro = !!props.readOnly && !props.disabled;
2397
2433
  const mounted = useMounted();
2434
+ useInputModality();
2398
2435
  const kept = React16.useRef(null);
2399
2436
  const current2 = [].concat(props.value != null ? props.value : props.defaultValue != null ? props.defaultValue : []).map(String);
2400
2437
  const opts = (props.options || []).map(function(o) {
@@ -2807,6 +2844,7 @@ window.Aura = (() => {
2807
2844
  e.preventDefault();
2808
2845
  },
2809
2846
  onPointerMove: function() {
2847
+ pointerModality();
2810
2848
  if (!it.disabled && active !== it.index) activeState[1](it.index);
2811
2849
  },
2812
2850
  onClick: function() {
@@ -4208,6 +4246,7 @@ window.Aura = (() => {
4208
4246
  const posState = React23.useState(null);
4209
4247
  const inputRef = React23.useRef(null), inputMerged = useMergedRef(ref, inputRef), boxRef = React23.useRef(null), listRef = React23.useRef(null);
4210
4248
  const mounted = useMounted();
4249
+ useInputModality();
4211
4250
  const limit = props.limit || 200;
4212
4251
  const filter = props.filter || defaultFilter;
4213
4252
  let shown = props.onSearch || query == null ? options : options.filter(function(o) {
@@ -4391,6 +4430,7 @@ window.Aura = (() => {
4391
4430
  if (!blocked || isSel) choose(o);
4392
4431
  },
4393
4432
  onPointerMove: function() {
4433
+ pointerModality();
4394
4434
  if (activeIdx !== i) setActive(i);
4395
4435
  }
4396
4436
  },
@@ -5527,7 +5567,7 @@ window.Aura = (() => {
5527
5567
  "aria-label": props.removeLabel || t.remove(name),
5528
5568
  onClick: props.onRemove
5529
5569
  },
5530
- /* @__PURE__ */ React31.createElement(Icon, { name: /* @__PURE__ */ React31.createElement(IconX, null), size: 14 })
5570
+ /* @__PURE__ */ React31.createElement(Icon, { name: /* @__PURE__ */ React31.createElement(IconX, null), size: 16 })
5531
5571
  );
5532
5572
  return /* @__PURE__ */ React31.createElement(Tooltip, { content: fullText, open: fullText ? void 0 : false }, btn);
5533
5573
  }() : null);
@@ -9581,6 +9621,7 @@ window.Aura = (() => {
9581
9621
  const pos = React57.useState(null);
9582
9622
  const boxRef = React57.useRef(null), inputRef = React57.useRef(null), listRef = React57.useRef(null), inputMerged = useMergedRef(ref, inputRef);
9583
9623
  const mounted = useMounted();
9624
+ useInputModality();
9584
9625
  function nearest(v) {
9585
9626
  const n3 = toMin(v);
9586
9627
  if (n3 == null) return 0;
@@ -9752,6 +9793,7 @@ window.Aura = (() => {
9752
9793
  choose(i);
9753
9794
  },
9754
9795
  onPointerMove: function() {
9796
+ pointerModality();
9755
9797
  if (active !== i) setActive(i);
9756
9798
  }
9757
9799
  },
@@ -10347,7 +10389,7 @@ window.Aura = (() => {
10347
10389
  D["fg-accent"] = fit(b[300], darkGrounds, 4.5, 1);
10348
10390
  D["accent-violet"] = fit(b[400], darkGrounds, 3, 1);
10349
10391
  D["focus-ring"] = fit(b[400], darkGrounds, 3, 1);
10350
- D["bg-selected"] = mix(ZINC[900], b[500], 0.16);
10392
+ D["bg-selected"] = mix(ZINC[900], b[500], 0.2);
10351
10393
  D["alert-info-bg"] = D["bg-selected"];
10352
10394
  D["alert-info-fg"] = fit(b[300], [D["alert-info-bg"]], 4.5, 1);
10353
10395
  D["alert-info-border"] = mix(ZINC[900], b[400], 0.4);
@@ -10368,6 +10410,7 @@ window.Aura = (() => {
10368
10410
  L["control-checked-fg"] = onFill(L["control-checked-bg"]);
10369
10411
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
10370
10412
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
10413
+ D["nav-active-edge"] = D["control-checked-bg"];
10371
10414
  const shadowLight = INK, shadowDark = D["accent-violet"];
10372
10415
  if (o.primary === "brand") {
10373
10416
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -470,16 +470,46 @@ function barStack(name, onMany, onRead) {
470
470
  write
471
471
  };
472
472
  }
473
+ var modalityOn = false;
474
+ function setModality(m) {
475
+ const root = document.documentElement;
476
+ if (m === "pointer") {
477
+ if (root.getAttribute("data-aura-modality") !== "pointer") root.setAttribute("data-aura-modality", "pointer");
478
+ } else if (root.hasAttribute("data-aura-modality")) root.removeAttribute("data-aura-modality");
479
+ }
480
+ function useInputModality() {
481
+ React3.useEffect(function() {
482
+ if (modalityOn) return;
483
+ modalityOn = true;
484
+ document.addEventListener(
485
+ "keydown",
486
+ function() {
487
+ setModality("key");
488
+ },
489
+ true
490
+ );
491
+ document.addEventListener(
492
+ "pointerdown",
493
+ function() {
494
+ setModality("pointer");
495
+ },
496
+ true
497
+ );
498
+ }, []);
499
+ }
500
+ function pointerModality() {
501
+ setModality("pointer");
502
+ }
473
503
 
474
504
  // src/display.tsx
475
505
  var React4 = __toESM(require("react"), 1);
476
506
 
477
507
  // src/status.ts
478
508
  var TONE_WORDS = {
479
- ready: ["ready", "done", "complete", "completed", "approved", "live", "passed"],
509
+ ready: ["ready", "done", "complete", "completed", "approved", "live", "passed", "active", "up", "online", "resolved"],
480
510
  progress: ["in progress", "in review", "review", "syncing", "running", "pending"],
481
511
  warning: ["warning", "problem", "degraded", "at risk"],
482
- blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled"]
512
+ blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled", "down", "offline", "outage"]
483
513
  };
484
514
  function toneFor(status) {
485
515
  const s = String(status == null ? "" : status).trim().toLowerCase();
@@ -797,13 +827,19 @@ var PILL_ICON = {
797
827
  };
798
828
  function statusPillElement(props, ref) {
799
829
  const tone2 = props.tone || toneFor(props.children);
800
- const rest = omit(props, ["tone", "className", "children", "wrap", "busy"]);
830
+ const rest = omit(props, ["tone", "className", "children", "wrap", "busy", "variant"]);
801
831
  return /* @__PURE__ */ React4.createElement(
802
832
  "span",
803
833
  {
804
834
  ...rest,
805
835
  ref,
806
- className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
836
+ className: cx(
837
+ "aura-pill",
838
+ "aura-pill--" + tone2,
839
+ props.variant === "solid" && "is-solid",
840
+ props.wrap && "is-wrap",
841
+ props.className
842
+ )
807
843
  },
808
844
  props.busy ? /* @__PURE__ */ React4.createElement(Icon, { name: h2(IconLoaderCircle), size: 12, className: "aura-spin" }) : /* @__PURE__ */ React4.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
809
845
  props.wrap ? /* @__PURE__ */ React4.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
@@ -2377,6 +2413,7 @@ var Select = React16.forwardRef(function Select2(props, ref) {
2377
2413
  const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY, "readOnly"]));
2378
2414
  const ro = !!props.readOnly && !props.disabled;
2379
2415
  const mounted = useMounted();
2416
+ useInputModality();
2380
2417
  const kept = React16.useRef(null);
2381
2418
  const current2 = [].concat(props.value != null ? props.value : props.defaultValue != null ? props.defaultValue : []).map(String);
2382
2419
  const opts = (props.options || []).map(function(o) {
@@ -2789,6 +2826,7 @@ var Select = React16.forwardRef(function Select2(props, ref) {
2789
2826
  e.preventDefault();
2790
2827
  },
2791
2828
  onPointerMove: function() {
2829
+ pointerModality();
2792
2830
  if (!it.disabled && active !== it.index) activeState[1](it.index);
2793
2831
  },
2794
2832
  onClick: function() {
@@ -4190,6 +4228,7 @@ var Combobox = React23.forwardRef(
4190
4228
  const posState = React23.useState(null);
4191
4229
  const inputRef = React23.useRef(null), inputMerged = useMergedRef(ref, inputRef), boxRef = React23.useRef(null), listRef = React23.useRef(null);
4192
4230
  const mounted = useMounted();
4231
+ useInputModality();
4193
4232
  const limit = props.limit || 200;
4194
4233
  const filter = props.filter || defaultFilter;
4195
4234
  let shown = props.onSearch || query == null ? options : options.filter(function(o) {
@@ -4373,6 +4412,7 @@ var Combobox = React23.forwardRef(
4373
4412
  if (!blocked || isSel) choose(o);
4374
4413
  },
4375
4414
  onPointerMove: function() {
4415
+ pointerModality();
4376
4416
  if (activeIdx !== i) setActive(i);
4377
4417
  }
4378
4418
  },
@@ -5509,7 +5549,7 @@ var Tag = React31.forwardRef(function Tag2(props, ref) {
5509
5549
  "aria-label": props.removeLabel || t.remove(name),
5510
5550
  onClick: props.onRemove
5511
5551
  },
5512
- /* @__PURE__ */ React31.createElement(Icon, { name: /* @__PURE__ */ React31.createElement(IconX, null), size: 14 })
5552
+ /* @__PURE__ */ React31.createElement(Icon, { name: /* @__PURE__ */ React31.createElement(IconX, null), size: 16 })
5513
5553
  );
5514
5554
  return /* @__PURE__ */ React31.createElement(Tooltip, { content: fullText, open: fullText ? void 0 : false }, btn);
5515
5555
  }() : null);
@@ -9563,6 +9603,7 @@ var TimePicker = React57.forwardRef(function TimePicker2(props, ref) {
9563
9603
  const pos = React57.useState(null);
9564
9604
  const boxRef = React57.useRef(null), inputRef = React57.useRef(null), listRef = React57.useRef(null), inputMerged = useMergedRef(ref, inputRef);
9565
9605
  const mounted = useMounted();
9606
+ useInputModality();
9566
9607
  function nearest(v) {
9567
9608
  const n3 = toMin(v);
9568
9609
  if (n3 == null) return 0;
@@ -9734,6 +9775,7 @@ var TimePicker = React57.forwardRef(function TimePicker2(props, ref) {
9734
9775
  choose(i);
9735
9776
  },
9736
9777
  onPointerMove: function() {
9778
+ pointerModality();
9737
9779
  if (active !== i) setActive(i);
9738
9780
  }
9739
9781
  },
@@ -10329,7 +10371,7 @@ function createTheme(opts) {
10329
10371
  D["fg-accent"] = fit(b[300], darkGrounds, 4.5, 1);
10330
10372
  D["accent-violet"] = fit(b[400], darkGrounds, 3, 1);
10331
10373
  D["focus-ring"] = fit(b[400], darkGrounds, 3, 1);
10332
- D["bg-selected"] = mix(ZINC[900], b[500], 0.16);
10374
+ D["bg-selected"] = mix(ZINC[900], b[500], 0.2);
10333
10375
  D["alert-info-bg"] = D["bg-selected"];
10334
10376
  D["alert-info-fg"] = fit(b[300], [D["alert-info-bg"]], 4.5, 1);
10335
10377
  D["alert-info-border"] = mix(ZINC[900], b[400], 0.4);
@@ -10350,6 +10392,7 @@ function createTheme(opts) {
10350
10392
  L["control-checked-fg"] = onFill(L["control-checked-bg"]);
10351
10393
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
10352
10394
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
10395
+ D["nav-active-edge"] = D["control-checked-bg"];
10353
10396
  const shadowLight = INK, shadowDark = D["accent-violet"];
10354
10397
  if (o.primary === "brand") {
10355
10398
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -2,7 +2,17 @@
2
2
  import * as React from "react";
3
3
  import { useStrings, useDensity } from "./locale.js";
4
4
  import { createPortal } from "react-dom";
5
- import { cx, uid, useMaybeControlled, useMounted, useIsoLayoutEffect, useMergedRef, touchClass } from "./internal.js";
5
+ import {
6
+ cx,
7
+ uid,
8
+ useMaybeControlled,
9
+ useMounted,
10
+ useIsoLayoutEffect,
11
+ useMergedRef,
12
+ touchClass,
13
+ useInputModality,
14
+ pointerModality
15
+ } from "./internal.js";
6
16
  import { Icon } from "./Icon.js";
7
17
  import { Field } from "./Field.js";
8
18
  import { IconCheck, IconChevronDown, IconLoaderCircle, IconSearch, IconX } from "./icons.js";
@@ -67,6 +77,7 @@ const Combobox = React.forwardRef(
67
77
  const posState = React.useState(null);
68
78
  const inputRef = React.useRef(null), inputMerged = useMergedRef(ref, inputRef), boxRef = React.useRef(null), listRef = React.useRef(null);
69
79
  const mounted = useMounted();
80
+ useInputModality();
70
81
  const limit = props.limit || 200;
71
82
  const filter = props.filter || defaultFilter;
72
83
  let shown = props.onSearch || query == null ? options : options.filter(function(o) {
@@ -250,6 +261,7 @@ const Combobox = React.forwardRef(
250
261
  if (!blocked || isSel) choose(o);
251
262
  },
252
263
  onPointerMove: function() {
264
+ pointerModality();
253
265
  if (activeIdx !== i) setActive(i);
254
266
  }
255
267
  },
@@ -4,7 +4,18 @@ import { createPortal } from "react-dom";
4
4
  import { FIELD_KEYS, Field, describedBy } from "./Field.js";
5
5
  import { Icon } from "./Icon.js";
6
6
  import { useDensity } from "./locale.js";
7
- import { cx, devWarnOnce, omit, touchClass, uid, useIsoLayoutEffect, useMergedRef, useMounted } from "./internal.js";
7
+ import {
8
+ cx,
9
+ devWarnOnce,
10
+ useInputModality,
11
+ omit,
12
+ pointerModality,
13
+ touchClass,
14
+ uid,
15
+ useIsoLayoutEffect,
16
+ useMergedRef,
17
+ useMounted
18
+ } from "./internal.js";
8
19
  import { IconCheck, IconChevronDown } from "./icons.js";
9
20
  const h = React.createElement;
10
21
  const FILTER_KEY = "__auraFilter";
@@ -86,6 +97,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
86
97
  const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY, "readOnly"]));
87
98
  const ro = !!props.readOnly && !props.disabled;
88
99
  const mounted = useMounted();
100
+ useInputModality();
89
101
  const kept = React.useRef(null);
90
102
  const current = [].concat(props.value != null ? props.value : props.defaultValue != null ? props.defaultValue : []).map(String);
91
103
  const opts = (props.options || []).map(function(o) {
@@ -498,6 +510,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
498
510
  e.preventDefault();
499
511
  },
500
512
  onPointerMove: function() {
513
+ pointerModality();
501
514
  if (!it.disabled && active !== it.index) activeState[1](it.index);
502
515
  },
503
516
  onClick: function() {
package/dist/esm/Tag.js CHANGED
@@ -92,7 +92,7 @@ const Tag = React.forwardRef(function Tag2(props, ref) {
92
92
  "aria-label": props.removeLabel || t.remove(name),
93
93
  onClick: props.onRemove
94
94
  },
95
- /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconX, null), size: 14 })
95
+ /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconX, null), size: 16 })
96
96
  );
97
97
  return /* @__PURE__ */ React.createElement(Tooltip, { content: fullText, open: fullText ? void 0 : false }, btn);
98
98
  }() : null);
@@ -1,7 +1,17 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { createPortal } from "react-dom";
4
- import { cx, uid, useMaybeControlled, useMounted, useIsoLayoutEffect, useMergedRef, touchClass } from "./internal.js";
4
+ import {
5
+ cx,
6
+ uid,
7
+ useMaybeControlled,
8
+ useMounted,
9
+ useIsoLayoutEffect,
10
+ useMergedRef,
11
+ touchClass,
12
+ useInputModality,
13
+ pointerModality
14
+ } from "./internal.js";
5
15
  import { useStrings } from "./locale.js";
6
16
  import { Icon } from "./Icon.js";
7
17
  import { Field } from "./Field.js";
@@ -46,6 +56,7 @@ const TimePicker = React.forwardRef(function TimePicker2(props, ref) {
46
56
  const pos = React.useState(null);
47
57
  const boxRef = React.useRef(null), inputRef = React.useRef(null), listRef = React.useRef(null), inputMerged = useMergedRef(ref, inputRef);
48
58
  const mounted = useMounted();
59
+ useInputModality();
49
60
  function nearest(v) {
50
61
  const n = toMin(v);
51
62
  if (n == null) return 0;
@@ -217,6 +228,7 @@ const TimePicker = React.forwardRef(function TimePicker2(props, ref) {
217
228
  choose(i);
218
229
  },
219
230
  onPointerMove: function() {
231
+ pointerModality();
220
232
  if (active !== i) setActive(i);
221
233
  }
222
234
  },
@@ -102,13 +102,19 @@ const PILL_ICON = {
102
102
  };
103
103
  function statusPillElement(props, ref) {
104
104
  const tone = props.tone || toneFor(props.children);
105
- const rest = omit(props, ["tone", "className", "children", "wrap", "busy"]);
105
+ const rest = omit(props, ["tone", "className", "children", "wrap", "busy", "variant"]);
106
106
  return /* @__PURE__ */ React.createElement(
107
107
  "span",
108
108
  {
109
109
  ...rest,
110
110
  ref,
111
- className: cx("aura-pill", "aura-pill--" + tone, props.wrap && "is-wrap", props.className)
111
+ className: cx(
112
+ "aura-pill",
113
+ "aura-pill--" + tone,
114
+ props.variant === "solid" && "is-solid",
115
+ props.wrap && "is-wrap",
116
+ props.className
117
+ )
112
118
  },
113
119
  props.busy ? /* @__PURE__ */ React.createElement(Icon, { name: h(IconLoaderCircle), size: 12, className: "aura-spin" }) : /* @__PURE__ */ React.createElement(Icon, { name: h(PILL_ICON[tone] || IconCircle), size: 12 }),
114
120
  props.wrap ? /* @__PURE__ */ React.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
@@ -187,6 +187,36 @@ function barStack(name, onMany, onRead) {
187
187
  write
188
188
  };
189
189
  }
190
+ let modalityOn = false;
191
+ function setModality(m) {
192
+ const root = document.documentElement;
193
+ if (m === "pointer") {
194
+ if (root.getAttribute("data-aura-modality") !== "pointer") root.setAttribute("data-aura-modality", "pointer");
195
+ } else if (root.hasAttribute("data-aura-modality")) root.removeAttribute("data-aura-modality");
196
+ }
197
+ function useInputModality() {
198
+ React.useEffect(function() {
199
+ if (modalityOn) return;
200
+ modalityOn = true;
201
+ document.addEventListener(
202
+ "keydown",
203
+ function() {
204
+ setModality("key");
205
+ },
206
+ true
207
+ );
208
+ document.addEventListener(
209
+ "pointerdown",
210
+ function() {
211
+ setModality("pointer");
212
+ },
213
+ true
214
+ );
215
+ }, []);
216
+ }
217
+ function pointerModality() {
218
+ setModality("pointer");
219
+ }
190
220
  export {
191
221
  FOCUSABLE,
192
222
  FilterSheet,
@@ -202,10 +232,12 @@ export {
202
232
  isTabStop,
203
233
  omit,
204
234
  plainClick,
235
+ pointerModality,
205
236
  tone,
206
237
  touchClass,
207
238
  trapTab,
208
239
  uid,
240
+ useInputModality,
209
241
  useIsoLayoutEffect,
210
242
  useMaybeControlled,
211
243
  useMergedRef,
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
  const TONE_WORDS = {
3
- ready: ["ready", "done", "complete", "completed", "approved", "live", "passed"],
3
+ ready: ["ready", "done", "complete", "completed", "approved", "live", "passed", "active", "up", "online", "resolved"],
4
4
  progress: ["in progress", "in review", "review", "syncing", "running", "pending"],
5
5
  warning: ["warning", "problem", "degraded", "at risk"],
6
- blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled"]
6
+ blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled", "down", "offline", "outage"]
7
7
  };
8
8
  function toneFor(status) {
9
9
  const s = String(status == null ? "" : status).trim().toLowerCase();
package/dist/esm/theme.js CHANGED
@@ -148,7 +148,7 @@ function createTheme(opts) {
148
148
  D["fg-accent"] = fit(b[300], darkGrounds, 4.5, 1);
149
149
  D["accent-violet"] = fit(b[400], darkGrounds, 3, 1);
150
150
  D["focus-ring"] = fit(b[400], darkGrounds, 3, 1);
151
- D["bg-selected"] = mix(ZINC[900], b[500], 0.16);
151
+ D["bg-selected"] = mix(ZINC[900], b[500], 0.2);
152
152
  D["alert-info-bg"] = D["bg-selected"];
153
153
  D["alert-info-fg"] = fit(b[300], [D["alert-info-bg"]], 4.5, 1);
154
154
  D["alert-info-border"] = mix(ZINC[900], b[400], 0.4);
@@ -169,6 +169,7 @@ function createTheme(opts) {
169
169
  L["control-checked-fg"] = onFill(L["control-checked-bg"]);
170
170
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
171
171
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
172
+ D["nav-active-edge"] = D["control-checked-bg"];
172
173
  const shadowLight = INK, shadowDark = D["accent-violet"];
173
174
  if (o.primary === "brand") {
174
175
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
package/dist/index.d.cts CHANGED
@@ -335,7 +335,11 @@ export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanEle
335
335
  /** 5.70 (Chamber-OS 189): the spinner in the icon slot, for a state still in progress ("Waiting for payment").
336
336
  * The words still say it; the tone is unchanged. */
337
337
  busy?: boolean | undefined;
338
- /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
338
+ /** 5.75 (visual audit): `"soft"` (default) is a tint with a darker word, like Alert and Badge, so a column of statuses
339
+ * reads evenly. `"solid"` is the strong fill — for the one status on a screen that must catch the eye (an overdue
340
+ * total, a failed payment), not for every row. */
341
+ variant?: "soft" | "solid" | undefined;
342
+ /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked; 5.75: Active/Up/Online/Resolved → ready, Down/Offline/Outage → blocked; anything else → neutral). */
339
343
  tone?: StatusTone | undefined;
340
344
  className?: string | undefined;
341
345
  }
package/dist/index.d.ts CHANGED
@@ -335,7 +335,11 @@ export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanEle
335
335
  /** 5.70 (Chamber-OS 189): the spinner in the icon slot, for a state still in progress ("Waiting for payment").
336
336
  * The words still say it; the tone is unchanged. */
337
337
  busy?: boolean | undefined;
338
- /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
338
+ /** 5.75 (visual audit): `"soft"` (default) is a tint with a darker word, like Alert and Badge, so a column of statuses
339
+ * reads evenly. `"solid"` is the strong fill — for the one status on a screen that must catch the eye (an overdue
340
+ * total, a failed payment), not for every row. */
341
+ variant?: "soft" | "solid" | undefined;
342
+ /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked; 5.75: Active/Up/Online/Resolved → ready, Down/Offline/Outage → blocked; anything else → neutral). */
339
343
  tone?: StatusTone | undefined;
340
344
  className?: string | undefined;
341
345
  }
@@ -203,10 +203,10 @@ function parseTime(text) {
203
203
 
204
204
  // src/status.ts
205
205
  var TONE_WORDS = {
206
- ready: ["ready", "done", "complete", "completed", "approved", "live", "passed"],
206
+ ready: ["ready", "done", "complete", "completed", "approved", "live", "passed", "active", "up", "online", "resolved"],
207
207
  progress: ["in progress", "in review", "review", "syncing", "running", "pending"],
208
208
  warning: ["warning", "problem", "degraded", "at risk"],
209
- blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled"]
209
+ blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled", "down", "offline", "outage"]
210
210
  };
211
211
  function toneFor(status) {
212
212
  const s = String(status == null ? "" : status).trim().toLowerCase();
@@ -797,7 +797,7 @@ function createTheme(opts) {
797
797
  D["fg-accent"] = fit(b[300], darkGrounds, 4.5, 1);
798
798
  D["accent-violet"] = fit(b[400], darkGrounds, 3, 1);
799
799
  D["focus-ring"] = fit(b[400], darkGrounds, 3, 1);
800
- D["bg-selected"] = mix(ZINC[900], b[500], 0.16);
800
+ D["bg-selected"] = mix(ZINC[900], b[500], 0.2);
801
801
  D["alert-info-bg"] = D["bg-selected"];
802
802
  D["alert-info-fg"] = fit(b[300], [D["alert-info-bg"]], 4.5, 1);
803
803
  D["alert-info-border"] = mix(ZINC[900], b[400], 0.4);
@@ -818,6 +818,7 @@ function createTheme(opts) {
818
818
  L["control-checked-fg"] = onFill(L["control-checked-bg"]);
819
819
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
820
820
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
821
+ D["nav-active-edge"] = D["control-checked-bg"];
821
822
  const shadowLight = INK, shadowDark = D["accent-violet"];
822
823
  if (o.primary === "brand") {
823
824
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -1268,13 +1269,19 @@ var PILL_ICON = {
1268
1269
  };
1269
1270
  function statusPillElement(props, ref) {
1270
1271
  const tone2 = props.tone || toneFor(props.children);
1271
- const rest = omit(props, ["tone", "className", "children", "wrap", "busy"]);
1272
+ const rest = omit(props, ["tone", "className", "children", "wrap", "busy", "variant"]);
1272
1273
  return /* @__PURE__ */ React3.createElement(
1273
1274
  "span",
1274
1275
  {
1275
1276
  ...rest,
1276
1277
  ref,
1277
- className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
1278
+ className: cx(
1279
+ "aura-pill",
1280
+ "aura-pill--" + tone2,
1281
+ props.variant === "solid" && "is-solid",
1282
+ props.wrap && "is-wrap",
1283
+ props.className
1284
+ )
1278
1285
  },
1279
1286
  props.busy ? /* @__PURE__ */ React3.createElement(Icon, { name: h2(IconLoaderCircle), size: 12, className: "aura-spin" }) : /* @__PURE__ */ React3.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
1280
1287
  props.wrap ? /* @__PURE__ */ React3.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
@@ -54,7 +54,11 @@ export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanEle
54
54
  /** 5.70 (Chamber-OS 189): the spinner in the icon slot, for a state still in progress ("Waiting for payment").
55
55
  * The words still say it; the tone is unchanged. */
56
56
  busy?: boolean | undefined;
57
- /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
57
+ /** 5.75 (visual audit): `"soft"` (default) is a tint with a darker word, like Alert and Badge, so a column of statuses
58
+ * reads evenly. `"solid"` is the strong fill — for the one status on a screen that must catch the eye (an overdue
59
+ * total, a failed payment), not for every row. */
60
+ variant?: "soft" | "solid" | undefined;
61
+ /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked; 5.75: Active/Up/Online/Resolved → ready, Down/Offline/Outage → blocked; anything else → neutral). */
58
62
  tone?: StatusTone | undefined;
59
63
  className?: string | undefined;
60
64
  }
@@ -54,7 +54,11 @@ export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanEle
54
54
  /** 5.70 (Chamber-OS 189): the spinner in the icon slot, for a state still in progress ("Waiting for payment").
55
55
  * The words still say it; the tone is unchanged. */
56
56
  busy?: boolean | undefined;
57
- /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
57
+ /** 5.75 (visual audit): `"soft"` (default) is a tint with a darker word, like Alert and Badge, so a column of statuses
58
+ * reads evenly. `"solid"` is the strong fill — for the one status on a screen that must catch the eye (an overdue
59
+ * total, a failed payment), not for every row. */
60
+ variant?: "soft" | "solid" | undefined;
61
+ /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked; 5.75: Active/Up/Online/Resolved → ready, Down/Offline/Outage → blocked; anything else → neutral). */
58
62
  tone?: StatusTone | undefined;
59
63
  className?: string | undefined;
60
64
  }
@@ -136,10 +136,10 @@ function parseTime(text) {
136
136
 
137
137
  // src/status.ts
138
138
  var TONE_WORDS = {
139
- ready: ["ready", "done", "complete", "completed", "approved", "live", "passed"],
139
+ ready: ["ready", "done", "complete", "completed", "approved", "live", "passed", "active", "up", "online", "resolved"],
140
140
  progress: ["in progress", "in review", "review", "syncing", "running", "pending"],
141
141
  warning: ["warning", "problem", "degraded", "at risk"],
142
- blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled"]
142
+ blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled", "down", "offline", "outage"]
143
143
  };
144
144
  function toneFor(status) {
145
145
  const s = String(status == null ? "" : status).trim().toLowerCase();
@@ -730,7 +730,7 @@ function createTheme(opts) {
730
730
  D["fg-accent"] = fit(b[300], darkGrounds, 4.5, 1);
731
731
  D["accent-violet"] = fit(b[400], darkGrounds, 3, 1);
732
732
  D["focus-ring"] = fit(b[400], darkGrounds, 3, 1);
733
- D["bg-selected"] = mix(ZINC[900], b[500], 0.16);
733
+ D["bg-selected"] = mix(ZINC[900], b[500], 0.2);
734
734
  D["alert-info-bg"] = D["bg-selected"];
735
735
  D["alert-info-fg"] = fit(b[300], [D["alert-info-bg"]], 4.5, 1);
736
736
  D["alert-info-border"] = mix(ZINC[900], b[400], 0.4);
@@ -751,6 +751,7 @@ function createTheme(opts) {
751
751
  L["control-checked-fg"] = onFill(L["control-checked-bg"]);
752
752
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
753
753
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
754
+ D["nav-active-edge"] = D["control-checked-bg"];
754
755
  const shadowLight = INK, shadowDark = D["accent-violet"];
755
756
  if (o.primary === "brand") {
756
757
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -1201,13 +1202,19 @@ var PILL_ICON = {
1201
1202
  };
1202
1203
  function statusPillElement(props, ref) {
1203
1204
  const tone2 = props.tone || toneFor(props.children);
1204
- const rest = omit(props, ["tone", "className", "children", "wrap", "busy"]);
1205
+ const rest = omit(props, ["tone", "className", "children", "wrap", "busy", "variant"]);
1205
1206
  return /* @__PURE__ */ React3.createElement(
1206
1207
  "span",
1207
1208
  {
1208
1209
  ...rest,
1209
1210
  ref,
1210
- className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
1211
+ className: cx(
1212
+ "aura-pill",
1213
+ "aura-pill--" + tone2,
1214
+ props.variant === "solid" && "is-solid",
1215
+ props.wrap && "is-wrap",
1216
+ props.className
1217
+ )
1211
1218
  },
1212
1219
  props.busy ? /* @__PURE__ */ React3.createElement(Icon, { name: h2(IconLoaderCircle), size: 12, className: "aura-spin" }) : /* @__PURE__ */ React3.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
1213
1220
  props.wrap ? /* @__PURE__ */ React3.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
package/dist/styles.css CHANGED
@@ -245,7 +245,7 @@ button.aura-btn--creative[aria-disabled='true']:hover { box-shadow: var(--aura-s
245
245
  /* Sortable header */
246
246
  .aura-table__sort {
247
247
  display: inline-flex; align-items: center; gap: var(--aura-space-1); min-width: 0;
248
- margin: 0 calc(-1 * var(--aura-space-1)); padding: var(--aura-space-1);
248
+ margin: calc(-1 * var(--aura-space-1)); padding: var(--aura-space-1); /* 5.75: negative on all sides, so a sortable label sits on the line of a plain one */
249
249
  border: 0; border-radius: var(--aura-radius-xs); background: transparent;
250
250
  font: inherit; color: inherit; letter-spacing: inherit; cursor: pointer; white-space: nowrap;
251
251
  transition: color var(--aura-duration-fast) var(--aura-ease);
@@ -481,6 +481,14 @@ a.aura-icon-btn { text-decoration: none; }
481
481
  .aura-pill--ready { background: var(--aura-status-ready-bg); color: var(--aura-status-ready-fg); }
482
482
  .aura-pill--warning { background: var(--aura-status-warning-bg); color: var(--aura-status-warning-fg); }
483
483
  .aura-pill--blocked { background: var(--aura-status-blocked-bg); color: var(--aura-status-blocked-fg); }
484
+ /* 5.75 (visual audit): every pill is soft — a tint and its own darker word, as Alert and Badge — so a table of
485
+ * statuses reads evenly and Blocked or Warning don't shout in every row. variant="solid" is the strong fill, for the
486
+ * one status on a screen that must catch the eye. */
487
+ .aura-pill--neutral.is-solid { background: var(--aura-status-neutral-solid-bg); color: var(--aura-status-neutral-solid-fg); }
488
+ .aura-pill--progress.is-solid { background: var(--aura-status-progress-solid-bg); color: var(--aura-status-progress-solid-fg); }
489
+ .aura-pill--ready.is-solid { background: var(--aura-status-ready-solid-bg); color: var(--aura-status-ready-solid-fg); }
490
+ .aura-pill--warning.is-solid { background: var(--aura-status-warning-solid-bg); color: var(--aura-status-warning-solid-fg); }
491
+ .aura-pill--blocked.is-solid { background: var(--aura-status-blocked-solid-bg); color: var(--aura-status-blocked-solid-fg); }
484
492
 
485
493
  /* Icon — Lucide, 24-grid, stroke in currentColor */
486
494
  .aura-icon { flex: none; display: inline-block; vertical-align: middle; }
@@ -878,7 +886,8 @@ a.aura-tab { text-decoration: none; }
878
886
  .aura-cal__popover, .aura-menu, .aura-toast, .aura-command { --aura-hover: var(--aura-bg-surface-hover); }
879
887
  /* Canvas-coloured bands inside those surfaces: SideNav in the drawer, the DataTable header, picker corner and totals. */
880
888
  .aura-drawer .aura-nav, .aura-table__head, .aura-table__picker, .aura-table__total { --aura-hover: var(--aura-bg-canvas-hover); }
881
- .aura-nav__item.is-active { background: var(--aura-bg-selected); color: var(--aura-fg-primary); }
889
+ .aura-nav__item.is-active { background: var(--aura-bg-selected); color: var(--aura-fg-primary); box-shadow: inset 3px 0 0 var(--aura-nav-active-edge); } /* 5.75: the edge, in dark */
890
+ [dir='rtl'] .aura-nav__item.is-active, .aura-nav[dir='rtl'] .aura-nav__item.is-active { box-shadow: inset -3px 0 0 var(--aura-nav-active-edge); }
882
891
  .aura-nav__item.is-active .aura-icon { color: var(--aura-fg-accent); }
883
892
  .aura-nav__item:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: -2px; }
884
893
  /* Touch (5.7): rows are 44px on coarse pointers (AppShell's phone drawer); desktop keeps 36px. */
@@ -918,7 +927,8 @@ a.aura-tab { text-decoration: none; }
918
927
  }
919
928
  .aura-nav--collapsed .aura-nav__chevron { display: none; }
920
929
  .aura-nav--collapsed .aura-nav__section + .aura-nav__section { margin-top: var(--aura-space-3); padding-top: var(--aura-space-3); border-top: 1px solid var(--aura-border-default); }
921
- .aura-nav--collapsed .aura-nav__item--group.has-active { background: var(--aura-bg-selected); }
930
+ .aura-nav--collapsed .aura-nav__item--group.has-active { background: var(--aura-bg-selected); box-shadow: inset 3px 0 0 var(--aura-nav-active-edge); } /* 5.75: as the expanded item */
931
+ [dir='rtl'] .aura-nav--collapsed .aura-nav__item--group.has-active, .aura-nav--collapsed[dir='rtl'] .aura-nav__item--group.has-active { box-shadow: inset -3px 0 0 var(--aura-nav-active-edge); }
922
932
  .aura-nav--collapsed .aura-nav__item--group.has-active .aura-icon { color: var(--aura-fg-accent); }
923
933
  .aura-nav__initial {
924
934
  display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--aura-radius-sm);
@@ -1013,6 +1023,14 @@ a.aura-tab { text-decoration: none; }
1013
1023
  }
1014
1024
  .aura-combo__option > .aura-icon { color: var(--aura-fg-secondary); }
1015
1025
  .aura-combo__option.is-active { background: var(--aura-hover, var(--aura-bg-surface-hover)); outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: calc(-1 * var(--aura-focus-ring-width)); }
1026
+ /* 5.75 (visual audit): under the pointer the option is only shaded, as a Menu item, so the list doesn't show a second
1027
+ * ring under the field's. Moving by keys brings the ring back (the field keeps focus; the option is aria-activedescendant,
1028
+ * which :focus-visible can't see). */
1029
+ :root[data-aura-modality='pointer'] .aura-combo__option.is-active { outline: none; }
1030
+ @media (forced-colors: active) {
1031
+ /* the shade is dropped in forced colours: keep a thin mark on the row under the pointer */
1032
+ :root[data-aura-modality='pointer'] .aura-combo__option.is-active { outline: 1px solid Highlight; }
1033
+ }
1016
1034
  .aura-combo__option.is-selected { font-weight: 500; }
1017
1035
  .aura-combo__option.is-disabled { opacity: var(--aura-disabled-opacity); cursor: default; }
1018
1036
  .aura-combo__text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
@@ -1605,7 +1623,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1605
1623
  .aura-badge.is-outline { background: transparent; border-color: currentColor; }
1606
1624
  .aura-badge--neutral.is-outline { border-color: var(--aura-border-control); }
1607
1625
  .aura-badge.is-solid { background: var(--aura-button-primary-bg); color: var(--aura-button-primary-fg); }
1608
- .aura-badge--danger.is-solid { background: var(--aura-status-blocked-bg); color: var(--aura-status-blocked-fg); }
1626
+ .aura-badge--danger.is-solid { background: var(--aura-status-blocked-solid-bg); color: var(--aura-status-blocked-solid-fg); }
1609
1627
 
1610
1628
  /* ---------- Tag ---------- */
1611
1629
  .aura-tag {
@@ -1620,7 +1638,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1620
1638
  .aura-tag.is-selectable:focus-visible, .aura-tag__remove:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
1621
1639
  .aura-tag.is-selectable:disabled, .aura-tag.is-disabled { opacity: var(--aura-disabled-opacity); cursor: default; }
1622
1640
  .aura-tag__remove {
1623
- display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-right: -6px; padding: 0; border: 0; border-radius: var(--aura-radius-full);
1641
+ display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-inline-end: -8px; /* 5.75: a 16px × in a 24px button (was 14 in 20) */ padding: 0; border: 0; border-radius: var(--aura-radius-full);
1624
1642
  background: transparent; color: var(--aura-fg-secondary); cursor: pointer;
1625
1643
  }
1626
1644
  .aura-tag__remove:hover { background: var(--aura-hover, var(--aura-bg-surface-hover)); color: var(--aura-fg-primary); }
@@ -1638,7 +1656,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1638
1656
  .aura-progress__key { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
1639
1657
  .aura-progress__swatch { flex: none; width: 12px; height: 8px; border-radius: 2px; background: var(--aura-fg-accent); }
1640
1658
  .aura-progress--success :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-positive); }
1641
- .aura-progress--warning :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-alert-warning-fg); }
1659
+ .aura-progress--warning :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-progress-warning-fill); } /* 5.75: amber, not rust */
1642
1660
  .aura-progress--danger :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-danger); }
1643
1661
  .aura-progress--neutral :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-secondary); }
1644
1662
  /* 5.15 (Chamber-OS 89): a reserved amount after the value — the same tone, striped, so it reads without colour. */
@@ -2034,6 +2052,11 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2034
2052
  .aura-filterbar__search .aura-input__control::-webkit-search-cancel-button { display: none; }
2035
2053
  .aura-filterbar__clear-search { margin-right: 6px; }
2036
2054
  .aura-filterbar__controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-3); }
2055
+ /* 5.75 (visual audit): a toggle Tag among the controls is as tall as the search and selects beside it. */
2056
+ .aura-filterbar__controls .aura-tag.is-selectable { min-height: var(--aura-input-height); }
2057
+ @media (max-width: 639.98px), (pointer: coarse) {
2058
+ .aura-filterbar__controls .aura-tag.aura-tag--touch { min-height: max(var(--aura-input-height), var(--aura-touch-target)); } /* a touchHeight Tag keeps 44px (5.25) */
2059
+ }
2037
2060
  .aura-filterbar__spacer { flex: 1 1 0; }
2038
2061
  .aura-filterbar__count { font-size: max(13px, .8125rem); color: var(--aura-fg-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; }
2039
2062
  .aura-filterbar__actions { display: flex; align-items: center; gap: var(--aura-space-2); }
@@ -2296,6 +2319,13 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2296
2319
  @media (max-height: 30rem) { .aura-actionbar:not(.is-idle) { position: static; } }
2297
2320
  /* …and AppShell's top bar scrolls away with the page, so Shift+Tab never lands a field under it. */
2298
2321
  @media (max-height: 30rem) { .aura-shell__bar { position: static; } }
2322
+ /* 5.76 (Chamber-OS 194): on a screen 30rem tall or less (400% zoom, a phone on its side with the keyboard up) a Dialog
2323
+ * scrolls as one box — head, body and footer in one flow, the footer at the end of the content — instead of fixing
2324
+ * its head and footer around a body squeezed to nothing. Above that height, as before. */
2325
+ @media (max-height: 30rem) {
2326
+ .aura-dialog { overflow-y: auto; overscroll-behavior: contain; scroll-padding-block: var(--aura-space-4); } /* a focused field is shown with room around it */
2327
+ .aura-dialog__body { flex: none; min-height: auto; overflow: auto; overscroll-behavior: auto; } /* as tall as its content, so only wide content (a long <pre> or URL) scrolls here, sideways; a wheel over it goes on to the dialog */
2328
+ }
2299
2329
  /* 5.48: wherever a BottomNav shows, a field scrolled into view (Tab) stops above it, not under it. */
2300
2330
  :root { scroll-padding-bottom: var(--aura-bottomnav-offset, 0px); }
2301
2331
  .aura-actionbar__inner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2) var(--aura-space-3); min-height: 60px; box-sizing: border-box; padding-block: var(--aura-space-2); padding-inline: var(--aura-space-4) var(--aura-space-3); }
@@ -2674,6 +2704,6 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2674
2704
 
2675
2705
  /* 5.30: a plain link (no class) inside AURA's surfaces takes the accent colour instead of the browser's blue. Zero
2676
2706
  * specificity, so any rule of yours for links wins. */
2677
- :where(.aura-card, .aura-stat, .aura-alert, .aura-empty, .aura-field, .aura-tabs__panel, .aura-shell__main, .aura-tbl, .aura-table, .aura-dialog) :where(a:not([class])) {
2678
- color: var(--aura-fg-accent); text-underline-offset: 2px;
2707
+ :where(.aura-card, .aura-stat, .aura-alert, .aura-empty, .aura-field, .aura-tabs__panel, .aura-shell__main, .aura-tbl, .aura-table, .aura-dialog, .aura-page-header, .aura-popover, .aura-drawer, .aura-prose, .aura-text-body, .aura-text-caption) :where(a:not([class])) {
2708
+ color: var(--aura-fg-accent); text-decoration-line: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; /* 5.75: more surfaces, and always underlined */
2679
2709
  }
@@ -247,7 +247,7 @@ button.aura-btn--creative[aria-disabled='true']:hover { box-shadow: var(--aura-s
247
247
  /* Sortable header */
248
248
  .aura-table__sort {
249
249
  display: inline-flex; align-items: center; gap: var(--aura-space-1); min-width: 0;
250
- margin: 0 calc(-1 * var(--aura-space-1)); padding: var(--aura-space-1);
250
+ margin: calc(-1 * var(--aura-space-1)); padding: var(--aura-space-1); /* 5.75: negative on all sides, so a sortable label sits on the line of a plain one */
251
251
  border: 0; border-radius: var(--aura-radius-xs); background: transparent;
252
252
  font: inherit; color: inherit; letter-spacing: inherit; cursor: pointer; white-space: nowrap;
253
253
  transition: color var(--aura-duration-fast) var(--aura-ease);
@@ -483,6 +483,14 @@ a.aura-icon-btn { text-decoration: none; }
483
483
  .aura-pill--ready { background: var(--aura-status-ready-bg); color: var(--aura-status-ready-fg); }
484
484
  .aura-pill--warning { background: var(--aura-status-warning-bg); color: var(--aura-status-warning-fg); }
485
485
  .aura-pill--blocked { background: var(--aura-status-blocked-bg); color: var(--aura-status-blocked-fg); }
486
+ /* 5.75 (visual audit): every pill is soft — a tint and its own darker word, as Alert and Badge — so a table of
487
+ * statuses reads evenly and Blocked or Warning don't shout in every row. variant="solid" is the strong fill, for the
488
+ * one status on a screen that must catch the eye. */
489
+ .aura-pill--neutral.is-solid { background: var(--aura-status-neutral-solid-bg); color: var(--aura-status-neutral-solid-fg); }
490
+ .aura-pill--progress.is-solid { background: var(--aura-status-progress-solid-bg); color: var(--aura-status-progress-solid-fg); }
491
+ .aura-pill--ready.is-solid { background: var(--aura-status-ready-solid-bg); color: var(--aura-status-ready-solid-fg); }
492
+ .aura-pill--warning.is-solid { background: var(--aura-status-warning-solid-bg); color: var(--aura-status-warning-solid-fg); }
493
+ .aura-pill--blocked.is-solid { background: var(--aura-status-blocked-solid-bg); color: var(--aura-status-blocked-solid-fg); }
486
494
 
487
495
  /* Icon — Lucide, 24-grid, stroke in currentColor */
488
496
  .aura-icon { flex: none; display: inline-block; vertical-align: middle; }
@@ -880,7 +888,8 @@ a.aura-tab { text-decoration: none; }
880
888
  .aura-cal__popover, .aura-menu, .aura-toast, .aura-command { --aura-hover: var(--aura-bg-surface-hover); }
881
889
  /* Canvas-coloured bands inside those surfaces: SideNav in the drawer, the DataTable header, picker corner and totals. */
882
890
  .aura-drawer .aura-nav, .aura-table__head, .aura-table__picker, .aura-table__total { --aura-hover: var(--aura-bg-canvas-hover); }
883
- .aura-nav__item.is-active { background: var(--aura-bg-selected); color: var(--aura-fg-primary); }
891
+ .aura-nav__item.is-active { background: var(--aura-bg-selected); color: var(--aura-fg-primary); box-shadow: inset 3px 0 0 var(--aura-nav-active-edge); } /* 5.75: the edge, in dark */
892
+ [dir='rtl'] .aura-nav__item.is-active, .aura-nav[dir='rtl'] .aura-nav__item.is-active { box-shadow: inset -3px 0 0 var(--aura-nav-active-edge); }
884
893
  .aura-nav__item.is-active .aura-icon { color: var(--aura-fg-accent); }
885
894
  .aura-nav__item:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: -2px; }
886
895
  /* Touch (5.7): rows are 44px on coarse pointers (AppShell's phone drawer); desktop keeps 36px. */
@@ -920,7 +929,8 @@ a.aura-tab { text-decoration: none; }
920
929
  }
921
930
  .aura-nav--collapsed .aura-nav__chevron { display: none; }
922
931
  .aura-nav--collapsed .aura-nav__section + .aura-nav__section { margin-top: var(--aura-space-3); padding-top: var(--aura-space-3); border-top: 1px solid var(--aura-border-default); }
923
- .aura-nav--collapsed .aura-nav__item--group.has-active { background: var(--aura-bg-selected); }
932
+ .aura-nav--collapsed .aura-nav__item--group.has-active { background: var(--aura-bg-selected); box-shadow: inset 3px 0 0 var(--aura-nav-active-edge); } /* 5.75: as the expanded item */
933
+ [dir='rtl'] .aura-nav--collapsed .aura-nav__item--group.has-active, .aura-nav--collapsed[dir='rtl'] .aura-nav__item--group.has-active { box-shadow: inset -3px 0 0 var(--aura-nav-active-edge); }
924
934
  .aura-nav--collapsed .aura-nav__item--group.has-active .aura-icon { color: var(--aura-fg-accent); }
925
935
  .aura-nav__initial {
926
936
  display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--aura-radius-sm);
@@ -1015,6 +1025,14 @@ a.aura-tab { text-decoration: none; }
1015
1025
  }
1016
1026
  .aura-combo__option > .aura-icon { color: var(--aura-fg-secondary); }
1017
1027
  .aura-combo__option.is-active { background: var(--aura-hover, var(--aura-bg-surface-hover)); outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: calc(-1 * var(--aura-focus-ring-width)); }
1028
+ /* 5.75 (visual audit): under the pointer the option is only shaded, as a Menu item, so the list doesn't show a second
1029
+ * ring under the field's. Moving by keys brings the ring back (the field keeps focus; the option is aria-activedescendant,
1030
+ * which :focus-visible can't see). */
1031
+ :root[data-aura-modality='pointer'] .aura-combo__option.is-active { outline: none; }
1032
+ @media (forced-colors: active) {
1033
+ /* the shade is dropped in forced colours: keep a thin mark on the row under the pointer */
1034
+ :root[data-aura-modality='pointer'] .aura-combo__option.is-active { outline: 1px solid Highlight; }
1035
+ }
1018
1036
  .aura-combo__option.is-selected { font-weight: 500; }
1019
1037
  .aura-combo__option.is-disabled { opacity: var(--aura-disabled-opacity); cursor: default; }
1020
1038
  .aura-combo__text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
@@ -1607,7 +1625,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1607
1625
  .aura-badge.is-outline { background: transparent; border-color: currentColor; }
1608
1626
  .aura-badge--neutral.is-outline { border-color: var(--aura-border-control); }
1609
1627
  .aura-badge.is-solid { background: var(--aura-button-primary-bg); color: var(--aura-button-primary-fg); }
1610
- .aura-badge--danger.is-solid { background: var(--aura-status-blocked-bg); color: var(--aura-status-blocked-fg); }
1628
+ .aura-badge--danger.is-solid { background: var(--aura-status-blocked-solid-bg); color: var(--aura-status-blocked-solid-fg); }
1611
1629
 
1612
1630
  /* ---------- Tag ---------- */
1613
1631
  .aura-tag {
@@ -1622,7 +1640,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1622
1640
  .aura-tag.is-selectable:focus-visible, .aura-tag__remove:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
1623
1641
  .aura-tag.is-selectable:disabled, .aura-tag.is-disabled { opacity: var(--aura-disabled-opacity); cursor: default; }
1624
1642
  .aura-tag__remove {
1625
- display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-right: -6px; padding: 0; border: 0; border-radius: var(--aura-radius-full);
1643
+ display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-inline-end: -8px; /* 5.75: a 16px × in a 24px button (was 14 in 20) */ padding: 0; border: 0; border-radius: var(--aura-radius-full);
1626
1644
  background: transparent; color: var(--aura-fg-secondary); cursor: pointer;
1627
1645
  }
1628
1646
  .aura-tag__remove:hover { background: var(--aura-hover, var(--aura-bg-surface-hover)); color: var(--aura-fg-primary); }
@@ -1640,7 +1658,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1640
1658
  .aura-progress__key { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
1641
1659
  .aura-progress__swatch { flex: none; width: 12px; height: 8px; border-radius: 2px; background: var(--aura-fg-accent); }
1642
1660
  .aura-progress--success :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-positive); }
1643
- .aura-progress--warning :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-alert-warning-fg); }
1661
+ .aura-progress--warning :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-progress-warning-fill); } /* 5.75: amber, not rust */
1644
1662
  .aura-progress--danger :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-danger); }
1645
1663
  .aura-progress--neutral :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-secondary); }
1646
1664
  /* 5.15 (Chamber-OS 89): a reserved amount after the value — the same tone, striped, so it reads without colour. */
@@ -2036,6 +2054,11 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2036
2054
  .aura-filterbar__search .aura-input__control::-webkit-search-cancel-button { display: none; }
2037
2055
  .aura-filterbar__clear-search { margin-right: 6px; }
2038
2056
  .aura-filterbar__controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-3); }
2057
+ /* 5.75 (visual audit): a toggle Tag among the controls is as tall as the search and selects beside it. */
2058
+ .aura-filterbar__controls .aura-tag.is-selectable { min-height: var(--aura-input-height); }
2059
+ @media (max-width: 639.98px), (pointer: coarse) {
2060
+ .aura-filterbar__controls .aura-tag.aura-tag--touch { min-height: max(var(--aura-input-height), var(--aura-touch-target)); } /* a touchHeight Tag keeps 44px (5.25) */
2061
+ }
2039
2062
  .aura-filterbar__spacer { flex: 1 1 0; }
2040
2063
  .aura-filterbar__count { font-size: max(13px, .8125rem); color: var(--aura-fg-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; }
2041
2064
  .aura-filterbar__actions { display: flex; align-items: center; gap: var(--aura-space-2); }
@@ -2298,6 +2321,13 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2298
2321
  @media (max-height: 30rem) { .aura-actionbar:not(.is-idle) { position: static; } }
2299
2322
  /* …and AppShell's top bar scrolls away with the page, so Shift+Tab never lands a field under it. */
2300
2323
  @media (max-height: 30rem) { .aura-shell__bar { position: static; } }
2324
+ /* 5.76 (Chamber-OS 194): on a screen 30rem tall or less (400% zoom, a phone on its side with the keyboard up) a Dialog
2325
+ * scrolls as one box — head, body and footer in one flow, the footer at the end of the content — instead of fixing
2326
+ * its head and footer around a body squeezed to nothing. Above that height, as before. */
2327
+ @media (max-height: 30rem) {
2328
+ .aura-dialog { overflow-y: auto; overscroll-behavior: contain; scroll-padding-block: var(--aura-space-4); } /* a focused field is shown with room around it */
2329
+ .aura-dialog__body { flex: none; min-height: auto; overflow: auto; overscroll-behavior: auto; } /* as tall as its content, so only wide content (a long <pre> or URL) scrolls here, sideways; a wheel over it goes on to the dialog */
2330
+ }
2301
2331
  /* 5.48: wherever a BottomNav shows, a field scrolled into view (Tab) stops above it, not under it. */
2302
2332
  :root { scroll-padding-bottom: var(--aura-bottomnav-offset, 0px); }
2303
2333
  .aura-actionbar__inner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2) var(--aura-space-3); min-height: 60px; box-sizing: border-box; padding-block: var(--aura-space-2); padding-inline: var(--aura-space-4) var(--aura-space-3); }
@@ -2676,8 +2706,8 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2676
2706
 
2677
2707
  /* 5.30: a plain link (no class) inside AURA's surfaces takes the accent colour instead of the browser's blue. Zero
2678
2708
  * specificity, so any rule of yours for links wins. */
2679
- :where(.aura-card, .aura-stat, .aura-alert, .aura-empty, .aura-field, .aura-tabs__panel, .aura-shell__main, .aura-tbl, .aura-table, .aura-dialog) :where(a:not([class])) {
2680
- color: var(--aura-fg-accent); text-underline-offset: 2px;
2709
+ :where(.aura-card, .aura-stat, .aura-alert, .aura-empty, .aura-field, .aura-tabs__panel, .aura-shell__main, .aura-tbl, .aura-table, .aura-dialog, .aura-page-header, .aura-popover, .aura-drawer, .aura-prose, .aura-text-body, .aura-text-caption) :where(a:not([class])) {
2710
+ color: var(--aura-fg-accent); text-decoration-line: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; /* 5.75: more surfaces, and always underlined */
2681
2711
  }
2682
2712
 
2683
2713
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "5.74.0",
3
+ "version": "5.76.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",