@jirawatpyk/aura-react 5.16.1 → 5.18.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,18 @@ 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.18 — Chamber-OS addendum 19
190
+
191
+ - **Stepper step errors** (item 112): a step can take `status: 'error'` — say, Save found errors on it. It keeps its place in the order and its state (a completed step is still a button with `onStepClick`, the current one keeps `aria-current="step"`), shows the danger tone with an alert icon instead of the check or number (outlined when it is the current step), and its accessible name ends ", has errors" instead of ", completed". On a phone the line reads "Step 2 of 4 — has errors" when the current step has errors; the markers, error ones included, stay visible. Steps without `status` look the same as before.
192
+ - **Stepper step names without the stray space**: a clickable step with a text label now has `aria-label` "Basics, completed" / "Fees, has errors" — from its content Chrome read "Basics , completed" (it puts a space before an absolutely positioned visually hidden span). Its `description` moves from the name to `aria-describedby`. The markup inside is unchanged; a label that is an element keeps the name from its content.
193
+ - New string `stepError` in every locale ("has errors", "มีข้อผิดพลาด", "har fel"); a pack you wrote yourself should add it (the built-in one for the locale is used otherwise, and the 6.0 notice for Thai and Swedish shows until it does).
194
+
195
+ ## 5.17 — Chamber-OS addendum 18
196
+
197
+ - **Stat attributes, status line and a link on the label** (item 110): `id`, `data-*`, `aria-*`, `style`, `lang` and `dir` go on the `.aura-stat` root, from the root package and `/server` — a `data-testid`, or `aria-hidden` on a loading placeholder. `status` is a line under the value ("Active · renews 1 Jan" with its tone icon), hidden while loading. With `href`, `linkArea="label"` puts the link on the label (inside the heading when there is one) and stretches its hit area over the tile: a click anywhere follows it, the tile shows the focus ring and hover edge, and other controls in the tile (links, buttons, fields, anything focusable) stay clickable above it. `aria-label`, `aria-labelledby`, `aria-describedby` and `aria-current` then go on the link, and an `onClick` too. Text in such a tile can't be selected by dragging (the click belongs to the link). The default (`tile`) is unchanged. Only the documented attributes reach the root; anything else passed is dropped, as before.
198
+ - **Progress `valueText`** (item 111): what screen readers hear (`aria-valuetext`), apart from the shown `valueLabel` — show "2 of 6 used", read "2 used, 1 reserved, 3 remaining of 6".
199
+ - The CSS budget is 26 kB (was 25).
200
+
189
201
  ## 5.16.1 — Select ground (Chamber-OS 109)
190
202
 
191
203
  - **A custom Select keeps the input ground.** The read-only rule for text fields matched the Select's trigger button too (`:read-only` matches anything not editable), so every custom Select showed the disabled ground and looked disabled. It now applies to a read-only `input` only; a read-only TextField still takes the disabled ground, and a read-only Textarea now does too.
@@ -717,8 +717,17 @@ window.Aura = (() => {
717
717
  const tone2 = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
718
718
  const v = props.value;
719
719
  const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
720
- const Tag3 = props.href ? Link : props.onClick ? "button" : "div";
720
+ const labelLink = !!props.href && props.linkArea === "label";
721
+ const Tag3 = labelLink ? "div" : props.href ? Link : props.onClick ? "button" : "div";
721
722
  const interactive = !!(props.href || props.onClick);
723
+ const LINK_ARIA = ["aria-label", "aria-labelledby", "aria-describedby", "aria-current"];
724
+ const root = {}, toLink = {};
725
+ Object.keys(props).forEach(function(k) {
726
+ const v2 = props[k];
727
+ if (labelLink && LINK_ARIA.indexOf(k) >= 0) toLink[k] = v2;
728
+ else if (k === "id" || k === "style" || k === "lang" || k === "dir" || /^(aria|data)-/.test(k)) root[k] = v2;
729
+ });
730
+ const busy = props.loading || props["aria-busy"] || void 0;
722
731
  if (props.headingLevel && Tag3 === "button")
723
732
  devWarnOnce(
724
733
  "stat-heading-button",
@@ -729,15 +738,23 @@ window.Aura = (() => {
729
738
  return /* @__PURE__ */ React4.createElement(
730
739
  Tag3,
731
740
  {
741
+ ...root,
732
742
  ref,
733
- className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
734
- href: props.href,
735
- onClick: props.onClick,
743
+ className: cx(
744
+ "aura-stat",
745
+ interactive && "is-interactive",
746
+ labelLink && "aura-stat--label-link",
747
+ props.loading && "is-loading",
748
+ props.className
749
+ ),
750
+ href: labelLink ? void 0 : props.href,
751
+ onClick: labelLink ? void 0 : props.onClick,
736
752
  type: Tag3 === "button" ? "button" : void 0,
737
- "aria-busy": props.loading || void 0
753
+ "aria-busy": busy
738
754
  },
739
- /* @__PURE__ */ React4.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React4.createElement(LabelTag, { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React4.createElement(Icon, { name: props.icon })) : null),
755
+ /* @__PURE__ */ React4.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React4.createElement(LabelTag, { className: "aura-stat__label" }, labelLink ? /* @__PURE__ */ React4.createElement(Link, { ...toLink, href: props.href, onClick: props.onClick, className: "aura-stat__link" }, props.label) : props.label), props.icon ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React4.createElement(Icon, { name: props.icon })) : null),
740
756
  props.loading ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React4.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React4.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
757
+ props.status != null && props.status !== false && props.status !== "" && !props.loading ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__status" }, props.status) : null,
741
758
  ch && !props.loading || props.caption ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React4.createElement("span", { className: cx("aura-stat__change", "is-" + tone2) }, /* @__PURE__ */ React4.createElement(
742
759
  Icon,
743
760
  {
@@ -911,6 +928,7 @@ window.Aura = (() => {
911
928
  return "\u0E43\u0E0A\u0E49\u0E41\u0E25\u0E49\u0E27 " + n(v) + " \u0E08\u0E32\u0E01 " + n(max) + " \u0E08\u0E2D\u0E07\u0E44\u0E27\u0E49 " + n(r);
912
929
  },
913
930
  breadcrumbMore: "\u0E41\u0E2A\u0E14\u0E07\u0E40\u0E2A\u0E49\u0E19\u0E17\u0E32\u0E07\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
931
+ stepError: "\u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14",
914
932
  accepts: function(list, max) {
915
933
  return [list, max && "\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 " + max + " \u0E15\u0E48\u0E2D\u0E44\u0E1F\u0E25\u0E4C"].filter(Boolean).join(" \xB7 ");
916
934
  }
@@ -1041,6 +1059,7 @@ window.Aura = (() => {
1041
1059
  return nsv(v) + " av " + nsv(max) + " anv\xE4nda, " + nsv(r) + " reserverade";
1042
1060
  },
1043
1061
  breadcrumbMore: "Visa hela s\xF6kv\xE4gen",
1062
+ stepError: "har fel",
1044
1063
  accepts: function(list, max) {
1045
1064
  return [list, max && "h\xF6gst " + max + " per fil"].filter(Boolean).join(", ");
1046
1065
  }
@@ -1172,6 +1191,7 @@ window.Aura = (() => {
1172
1191
  return n2(v) + " of " + n2(max) + " used, " + n2(r) + " reserved";
1173
1192
  },
1174
1193
  breadcrumbMore: "Show the full path",
1194
+ stepError: "has errors",
1175
1195
  accepts: function(list, max) {
1176
1196
  return [list, max && "up to " + max + " each"].filter(Boolean).join(", ");
1177
1197
  }
@@ -8344,7 +8364,7 @@ window.Aura = (() => {
8344
8364
  const pct = det ? Math.max(0, Math.min(100, props.value / max * 100)) : 0;
8345
8365
  const sec = det && props.secondaryValue != null && props.secondaryValue > 0 ? props.secondaryValue : 0;
8346
8366
  const spct = sec ? Math.max(0, Math.min(100 - pct, sec / max * 100)) : 0;
8347
- const valueText = props.valueLabel != null ? String(props.valueLabel) : sec ? t.progressReserved(props.value, sec, max) : void 0;
8367
+ const valueText = props.valueText != null ? props.valueText : props.valueLabel != null ? String(props.valueLabel) : sec ? t.progressReserved(props.value, sec, max) : void 0;
8348
8368
  const shown = props.valueLabel != null ? props.valueLabel : det ? Math.round(pct) + "%" : null;
8349
8369
  return /* @__PURE__ */ React53.createElement(
8350
8370
  "div",
@@ -8895,6 +8915,7 @@ window.Aura = (() => {
8895
8915
  var React60 = __toESM(require_react(), 1);
8896
8916
  var Stepper = React60.forwardRef(function Stepper2(props, ref) {
8897
8917
  const t = useStrings();
8918
+ const idBase = uid();
8898
8919
  const steps = props.steps || [];
8899
8920
  let at = -1;
8900
8921
  steps.forEach(function(s, i) {
@@ -8912,14 +8933,18 @@ window.Aura = (() => {
8912
8933
  },
8913
8934
  /* @__PURE__ */ React60.createElement("ol", { className: "aura-stepper__list" }, steps.map(function(s, i) {
8914
8935
  const state = i < at ? "done" : i === at ? "current" : "upcoming";
8915
- const marker = /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__marker", "aria-hidden": true }, state === "done" ? /* @__PURE__ */ React60.createElement(Icon, { name: /* @__PURE__ */ React60.createElement(IconCheck, null) }) : i + 1);
8916
- const text2 = /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__text" }, /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__label" }, s.label, state === "done" ? /* @__PURE__ */ React60.createElement("span", { className: "aura-sr-only" }, ", " + t.stepDone) : null), s.description ? /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__desc" }, s.description) : null);
8936
+ const error = s.status === "error";
8937
+ const marker = /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__marker", "aria-hidden": true }, error ? /* @__PURE__ */ React60.createElement(Icon, { name: /* @__PURE__ */ React60.createElement(IconCircleAlert, null) }) : state === "done" ? /* @__PURE__ */ React60.createElement(Icon, { name: /* @__PURE__ */ React60.createElement(IconCheck, null) }) : i + 1);
8938
+ const note = error ? t.stepError : state === "done" ? t.stepDone : "";
8917
8939
  const clickable = state === "done" && !!props.onStepClick;
8940
+ const named = clickable && (typeof s.label === "string" || typeof s.label === "number") && String(s.label).trim() !== "";
8941
+ const descId = idBase + "-step-" + i;
8942
+ const text2 = /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__text" }, /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__label" }, s.label, note ? /* @__PURE__ */ React60.createElement("span", { className: "aura-sr-only" }, ", " + note) : null), s.description ? /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__desc", id: named ? descId : void 0 }, s.description) : null);
8918
8943
  return /* @__PURE__ */ React60.createElement(
8919
8944
  "li",
8920
8945
  {
8921
8946
  key: s.id,
8922
- className: cx("aura-stepper__item", "is-" + state),
8947
+ className: cx("aura-stepper__item", "is-" + state, error && "is-error"),
8923
8948
  "aria-current": state === "current" ? "step" : void 0
8924
8949
  },
8925
8950
  clickable ? /* @__PURE__ */ React60.createElement(
@@ -8927,6 +8952,8 @@ window.Aura = (() => {
8927
8952
  {
8928
8953
  type: "button",
8929
8954
  className: "aura-stepper__step aura-focusable",
8955
+ "aria-label": named ? String(s.label) + (note ? ", " + note : "") : void 0,
8956
+ "aria-describedby": named && s.description ? descId : void 0,
8930
8957
  onClick: function() {
8931
8958
  props.onStepClick(s.id);
8932
8959
  }
@@ -8936,7 +8963,7 @@ window.Aura = (() => {
8936
8963
  ) : /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__step" }, marker, text2)
8937
8964
  );
8938
8965
  })),
8939
- !vertical && cur ? /* @__PURE__ */ React60.createElement("p", { className: "aura-stepper__compact", "aria-hidden": true }, /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__count" }, t.stepOf(at + 1, steps.length)), /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__compact-label" }, cur.label)) : null
8966
+ !vertical && cur ? /* @__PURE__ */ React60.createElement("p", { className: "aura-stepper__compact", "aria-hidden": true }, /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__count" }, t.stepOf(at + 1, steps.length), cur.status === "error" ? /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__count-error" }, " \u2014 " + t.stepError) : null), /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__compact-label" }, cur.label)) : null
8940
8967
  );
8941
8968
  });
8942
8969
 
@@ -699,8 +699,17 @@ function statElement(props, ref, Link) {
699
699
  const tone2 = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
700
700
  const v = props.value;
701
701
  const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
702
- const Tag3 = props.href ? Link : props.onClick ? "button" : "div";
702
+ const labelLink = !!props.href && props.linkArea === "label";
703
+ const Tag3 = labelLink ? "div" : props.href ? Link : props.onClick ? "button" : "div";
703
704
  const interactive = !!(props.href || props.onClick);
705
+ const LINK_ARIA = ["aria-label", "aria-labelledby", "aria-describedby", "aria-current"];
706
+ const root = {}, toLink = {};
707
+ Object.keys(props).forEach(function(k) {
708
+ const v2 = props[k];
709
+ if (labelLink && LINK_ARIA.indexOf(k) >= 0) toLink[k] = v2;
710
+ else if (k === "id" || k === "style" || k === "lang" || k === "dir" || /^(aria|data)-/.test(k)) root[k] = v2;
711
+ });
712
+ const busy = props.loading || props["aria-busy"] || void 0;
704
713
  if (props.headingLevel && Tag3 === "button")
705
714
  devWarnOnce(
706
715
  "stat-heading-button",
@@ -711,15 +720,23 @@ function statElement(props, ref, Link) {
711
720
  return /* @__PURE__ */ React4.createElement(
712
721
  Tag3,
713
722
  {
723
+ ...root,
714
724
  ref,
715
- className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
716
- href: props.href,
717
- onClick: props.onClick,
725
+ className: cx(
726
+ "aura-stat",
727
+ interactive && "is-interactive",
728
+ labelLink && "aura-stat--label-link",
729
+ props.loading && "is-loading",
730
+ props.className
731
+ ),
732
+ href: labelLink ? void 0 : props.href,
733
+ onClick: labelLink ? void 0 : props.onClick,
718
734
  type: Tag3 === "button" ? "button" : void 0,
719
- "aria-busy": props.loading || void 0
735
+ "aria-busy": busy
720
736
  },
721
- /* @__PURE__ */ React4.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React4.createElement(LabelTag, { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React4.createElement(Icon, { name: props.icon })) : null),
737
+ /* @__PURE__ */ React4.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React4.createElement(LabelTag, { className: "aura-stat__label" }, labelLink ? /* @__PURE__ */ React4.createElement(Link, { ...toLink, href: props.href, onClick: props.onClick, className: "aura-stat__link" }, props.label) : props.label), props.icon ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React4.createElement(Icon, { name: props.icon })) : null),
722
738
  props.loading ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React4.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React4.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
739
+ props.status != null && props.status !== false && props.status !== "" && !props.loading ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__status" }, props.status) : null,
723
740
  ch && !props.loading || props.caption ? /* @__PURE__ */ React4.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React4.createElement("span", { className: cx("aura-stat__change", "is-" + tone2) }, /* @__PURE__ */ React4.createElement(
724
741
  Icon,
725
742
  {
@@ -893,6 +910,7 @@ var th = {
893
910
  return "\u0E43\u0E0A\u0E49\u0E41\u0E25\u0E49\u0E27 " + n(v) + " \u0E08\u0E32\u0E01 " + n(max) + " \u0E08\u0E2D\u0E07\u0E44\u0E27\u0E49 " + n(r);
894
911
  },
895
912
  breadcrumbMore: "\u0E41\u0E2A\u0E14\u0E07\u0E40\u0E2A\u0E49\u0E19\u0E17\u0E32\u0E07\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
913
+ stepError: "\u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14",
896
914
  accepts: function(list, max) {
897
915
  return [list, max && "\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 " + max + " \u0E15\u0E48\u0E2D\u0E44\u0E1F\u0E25\u0E4C"].filter(Boolean).join(" \xB7 ");
898
916
  }
@@ -1023,6 +1041,7 @@ var sv = {
1023
1041
  return nsv(v) + " av " + nsv(max) + " anv\xE4nda, " + nsv(r) + " reserverade";
1024
1042
  },
1025
1043
  breadcrumbMore: "Visa hela s\xF6kv\xE4gen",
1044
+ stepError: "har fel",
1026
1045
  accepts: function(list, max) {
1027
1046
  return [list, max && "h\xF6gst " + max + " per fil"].filter(Boolean).join(", ");
1028
1047
  }
@@ -1154,6 +1173,7 @@ var STRINGS = {
1154
1173
  return n2(v) + " of " + n2(max) + " used, " + n2(r) + " reserved";
1155
1174
  },
1156
1175
  breadcrumbMore: "Show the full path",
1176
+ stepError: "has errors",
1157
1177
  accepts: function(list, max) {
1158
1178
  return [list, max && "up to " + max + " each"].filter(Boolean).join(", ");
1159
1179
  }
@@ -8326,7 +8346,7 @@ var Progress = React53.forwardRef(function Progress2(props, ref) {
8326
8346
  const pct = det ? Math.max(0, Math.min(100, props.value / max * 100)) : 0;
8327
8347
  const sec = det && props.secondaryValue != null && props.secondaryValue > 0 ? props.secondaryValue : 0;
8328
8348
  const spct = sec ? Math.max(0, Math.min(100 - pct, sec / max * 100)) : 0;
8329
- const valueText = props.valueLabel != null ? String(props.valueLabel) : sec ? t.progressReserved(props.value, sec, max) : void 0;
8349
+ const valueText = props.valueText != null ? props.valueText : props.valueLabel != null ? String(props.valueLabel) : sec ? t.progressReserved(props.value, sec, max) : void 0;
8330
8350
  const shown = props.valueLabel != null ? props.valueLabel : det ? Math.round(pct) + "%" : null;
8331
8351
  return /* @__PURE__ */ React53.createElement(
8332
8352
  "div",
@@ -8877,6 +8897,7 @@ var NumberField = React59.forwardRef(function NumberField2(props, ref) {
8877
8897
  var React60 = __toESM(require("react"), 1);
8878
8898
  var Stepper = React60.forwardRef(function Stepper2(props, ref) {
8879
8899
  const t = useStrings();
8900
+ const idBase = uid();
8880
8901
  const steps = props.steps || [];
8881
8902
  let at = -1;
8882
8903
  steps.forEach(function(s, i) {
@@ -8894,14 +8915,18 @@ var Stepper = React60.forwardRef(function Stepper2(props, ref) {
8894
8915
  },
8895
8916
  /* @__PURE__ */ React60.createElement("ol", { className: "aura-stepper__list" }, steps.map(function(s, i) {
8896
8917
  const state = i < at ? "done" : i === at ? "current" : "upcoming";
8897
- const marker = /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__marker", "aria-hidden": true }, state === "done" ? /* @__PURE__ */ React60.createElement(Icon, { name: /* @__PURE__ */ React60.createElement(IconCheck, null) }) : i + 1);
8898
- const text2 = /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__text" }, /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__label" }, s.label, state === "done" ? /* @__PURE__ */ React60.createElement("span", { className: "aura-sr-only" }, ", " + t.stepDone) : null), s.description ? /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__desc" }, s.description) : null);
8918
+ const error = s.status === "error";
8919
+ const marker = /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__marker", "aria-hidden": true }, error ? /* @__PURE__ */ React60.createElement(Icon, { name: /* @__PURE__ */ React60.createElement(IconCircleAlert, null) }) : state === "done" ? /* @__PURE__ */ React60.createElement(Icon, { name: /* @__PURE__ */ React60.createElement(IconCheck, null) }) : i + 1);
8920
+ const note = error ? t.stepError : state === "done" ? t.stepDone : "";
8899
8921
  const clickable = state === "done" && !!props.onStepClick;
8922
+ const named = clickable && (typeof s.label === "string" || typeof s.label === "number") && String(s.label).trim() !== "";
8923
+ const descId = idBase + "-step-" + i;
8924
+ const text2 = /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__text" }, /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__label" }, s.label, note ? /* @__PURE__ */ React60.createElement("span", { className: "aura-sr-only" }, ", " + note) : null), s.description ? /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__desc", id: named ? descId : void 0 }, s.description) : null);
8900
8925
  return /* @__PURE__ */ React60.createElement(
8901
8926
  "li",
8902
8927
  {
8903
8928
  key: s.id,
8904
- className: cx("aura-stepper__item", "is-" + state),
8929
+ className: cx("aura-stepper__item", "is-" + state, error && "is-error"),
8905
8930
  "aria-current": state === "current" ? "step" : void 0
8906
8931
  },
8907
8932
  clickable ? /* @__PURE__ */ React60.createElement(
@@ -8909,6 +8934,8 @@ var Stepper = React60.forwardRef(function Stepper2(props, ref) {
8909
8934
  {
8910
8935
  type: "button",
8911
8936
  className: "aura-stepper__step aura-focusable",
8937
+ "aria-label": named ? String(s.label) + (note ? ", " + note : "") : void 0,
8938
+ "aria-describedby": named && s.description ? descId : void 0,
8912
8939
  onClick: function() {
8913
8940
  props.onStepClick(s.id);
8914
8941
  }
@@ -8918,7 +8945,7 @@ var Stepper = React60.forwardRef(function Stepper2(props, ref) {
8918
8945
  ) : /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__step" }, marker, text2)
8919
8946
  );
8920
8947
  })),
8921
- !vertical && cur ? /* @__PURE__ */ React60.createElement("p", { className: "aura-stepper__compact", "aria-hidden": true }, /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__count" }, t.stepOf(at + 1, steps.length)), /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__compact-label" }, cur.label)) : null
8948
+ !vertical && cur ? /* @__PURE__ */ React60.createElement("p", { className: "aura-stepper__compact", "aria-hidden": true }, /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__count" }, t.stepOf(at + 1, steps.length), cur.status === "error" ? /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__count-error" }, " \u2014 " + t.stepError) : null), /* @__PURE__ */ React60.createElement("span", { className: "aura-stepper__compact-label" }, cur.label)) : null
8922
8949
  );
8923
8950
  });
8924
8951
 
@@ -9,7 +9,7 @@ const Progress = React.forwardRef(function Progress2(props, ref) {
9
9
  const pct = det ? Math.max(0, Math.min(100, props.value / max * 100)) : 0;
10
10
  const sec = det && props.secondaryValue != null && props.secondaryValue > 0 ? props.secondaryValue : 0;
11
11
  const spct = sec ? Math.max(0, Math.min(100 - pct, sec / max * 100)) : 0;
12
- const valueText = props.valueLabel != null ? String(props.valueLabel) : sec ? t.progressReserved(props.value, sec, max) : void 0;
12
+ const valueText = props.valueText != null ? props.valueText : props.valueLabel != null ? String(props.valueLabel) : sec ? t.progressReserved(props.value, sec, max) : void 0;
13
13
  const shown = props.valueLabel != null ? props.valueLabel : det ? Math.round(pct) + "%" : null;
14
14
  return /* @__PURE__ */ React.createElement(
15
15
  "div",
@@ -1,11 +1,12 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { Icon } from "./Icon.js";
4
- import { cx } from "./internal.js";
4
+ import { cx, uid } from "./internal.js";
5
5
  import { useStrings } from "./locale.js";
6
- import { IconCheck } from "./icons.js";
6
+ import { IconCheck, IconCircleAlert } from "./icons.js";
7
7
  const Stepper = React.forwardRef(function Stepper2(props, ref) {
8
8
  const t = useStrings();
9
+ const idBase = uid();
9
10
  const steps = props.steps || [];
10
11
  let at = -1;
11
12
  steps.forEach(function(s, i) {
@@ -23,14 +24,18 @@ const Stepper = React.forwardRef(function Stepper2(props, ref) {
23
24
  },
24
25
  /* @__PURE__ */ React.createElement("ol", { className: "aura-stepper__list" }, steps.map(function(s, i) {
25
26
  const state = i < at ? "done" : i === at ? "current" : "upcoming";
26
- const marker = /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__marker", "aria-hidden": true }, state === "done" ? /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconCheck, null) }) : i + 1);
27
- const text = /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__text" }, /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__label" }, s.label, state === "done" ? /* @__PURE__ */ React.createElement("span", { className: "aura-sr-only" }, ", " + t.stepDone) : null), s.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__desc" }, s.description) : null);
27
+ const error = s.status === "error";
28
+ const marker = /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__marker", "aria-hidden": true }, error ? /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconCircleAlert, null) }) : state === "done" ? /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconCheck, null) }) : i + 1);
29
+ const note = error ? t.stepError : state === "done" ? t.stepDone : "";
28
30
  const clickable = state === "done" && !!props.onStepClick;
31
+ const named = clickable && (typeof s.label === "string" || typeof s.label === "number") && String(s.label).trim() !== "";
32
+ const descId = idBase + "-step-" + i;
33
+ const text = /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__text" }, /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__label" }, s.label, note ? /* @__PURE__ */ React.createElement("span", { className: "aura-sr-only" }, ", " + note) : null), s.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__desc", id: named ? descId : void 0 }, s.description) : null);
29
34
  return /* @__PURE__ */ React.createElement(
30
35
  "li",
31
36
  {
32
37
  key: s.id,
33
- className: cx("aura-stepper__item", "is-" + state),
38
+ className: cx("aura-stepper__item", "is-" + state, error && "is-error"),
34
39
  "aria-current": state === "current" ? "step" : void 0
35
40
  },
36
41
  clickable ? /* @__PURE__ */ React.createElement(
@@ -38,6 +43,8 @@ const Stepper = React.forwardRef(function Stepper2(props, ref) {
38
43
  {
39
44
  type: "button",
40
45
  className: "aura-stepper__step aura-focusable",
46
+ "aria-label": named ? String(s.label) + (note ? ", " + note : "") : void 0,
47
+ "aria-describedby": named && s.description ? descId : void 0,
41
48
  onClick: function() {
42
49
  props.onStepClick(s.id);
43
50
  }
@@ -47,7 +54,7 @@ const Stepper = React.forwardRef(function Stepper2(props, ref) {
47
54
  ) : /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__step" }, marker, text)
48
55
  );
49
56
  })),
50
- !vertical && cur ? /* @__PURE__ */ React.createElement("p", { className: "aura-stepper__compact", "aria-hidden": true }, /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__count" }, t.stepOf(at + 1, steps.length)), /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__compact-label" }, cur.label)) : null
57
+ !vertical && cur ? /* @__PURE__ */ React.createElement("p", { className: "aura-stepper__compact", "aria-hidden": true }, /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__count" }, t.stepOf(at + 1, steps.length), cur.status === "error" ? /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__count-error" }, " \u2014 " + t.stepError) : null), /* @__PURE__ */ React.createElement("span", { className: "aura-stepper__compact-label" }, cur.label)) : null
51
58
  );
52
59
  });
53
60
  export {
@@ -146,8 +146,17 @@ function statElement(props, ref, Link) {
146
146
  const tone = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
147
147
  const v = props.value;
148
148
  const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
149
- const Tag = props.href ? Link : props.onClick ? "button" : "div";
149
+ const labelLink = !!props.href && props.linkArea === "label";
150
+ const Tag = labelLink ? "div" : props.href ? Link : props.onClick ? "button" : "div";
150
151
  const interactive = !!(props.href || props.onClick);
152
+ const LINK_ARIA = ["aria-label", "aria-labelledby", "aria-describedby", "aria-current"];
153
+ const root = {}, toLink = {};
154
+ Object.keys(props).forEach(function(k) {
155
+ const v2 = props[k];
156
+ if (labelLink && LINK_ARIA.indexOf(k) >= 0) toLink[k] = v2;
157
+ else if (k === "id" || k === "style" || k === "lang" || k === "dir" || /^(aria|data)-/.test(k)) root[k] = v2;
158
+ });
159
+ const busy = props.loading || props["aria-busy"] || void 0;
151
160
  if (props.headingLevel && Tag === "button")
152
161
  devWarnOnce(
153
162
  "stat-heading-button",
@@ -158,15 +167,23 @@ function statElement(props, ref, Link) {
158
167
  return /* @__PURE__ */ React.createElement(
159
168
  Tag,
160
169
  {
170
+ ...root,
161
171
  ref,
162
- className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
163
- href: props.href,
164
- onClick: props.onClick,
172
+ className: cx(
173
+ "aura-stat",
174
+ interactive && "is-interactive",
175
+ labelLink && "aura-stat--label-link",
176
+ props.loading && "is-loading",
177
+ props.className
178
+ ),
179
+ href: labelLink ? void 0 : props.href,
180
+ onClick: labelLink ? void 0 : props.onClick,
165
181
  type: Tag === "button" ? "button" : void 0,
166
- "aria-busy": props.loading || void 0
182
+ "aria-busy": busy
167
183
  },
168
- /* @__PURE__ */ React.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React.createElement(LabelTag, { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React.createElement(Icon, { name: props.icon })) : null),
184
+ /* @__PURE__ */ React.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React.createElement(LabelTag, { className: "aura-stat__label" }, labelLink ? /* @__PURE__ */ React.createElement(Link, { ...toLink, href: props.href, onClick: props.onClick, className: "aura-stat__link" }, props.label) : props.label), props.icon ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React.createElement(Icon, { name: props.icon })) : null),
169
185
  props.loading ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
186
+ props.status != null && props.status !== false && props.status !== "" && !props.loading ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__status" }, props.status) : null,
170
187
  ch && !props.loading || props.caption ? /* @__PURE__ */ React.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React.createElement("span", { className: cx("aura-stat__change", "is-" + tone) }, /* @__PURE__ */ React.createElement(
171
188
  Icon,
172
189
  {
@@ -126,6 +126,7 @@ const STRINGS = {
126
126
  return n(v) + " of " + n(max) + " used, " + n(r) + " reserved";
127
127
  },
128
128
  breadcrumbMore: "Show the full path",
129
+ stepError: "has errors",
129
130
  accepts: function(list, max) {
130
131
  return [list, max && "up to " + max + " each"].filter(Boolean).join(", ");
131
132
  }
@@ -122,6 +122,7 @@ const sv = {
122
122
  return nsv(v) + " av " + nsv(max) + " anv\xE4nda, " + nsv(r) + " reserverade";
123
123
  },
124
124
  breadcrumbMore: "Visa hela s\xF6kv\xE4gen",
125
+ stepError: "har fel",
125
126
  accepts: function(list, max) {
126
127
  return [list, max && "h\xF6gst " + max + " per fil"].filter(Boolean).join(", ");
127
128
  }
@@ -122,6 +122,7 @@ const th = {
122
122
  return "\u0E43\u0E0A\u0E49\u0E41\u0E25\u0E49\u0E27 " + n(v) + " \u0E08\u0E32\u0E01 " + n(max) + " \u0E08\u0E2D\u0E07\u0E44\u0E27\u0E49 " + n(r);
123
123
  },
124
124
  breadcrumbMore: "\u0E41\u0E2A\u0E14\u0E07\u0E40\u0E2A\u0E49\u0E19\u0E17\u0E32\u0E07\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
125
+ stepError: "\u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14",
125
126
  accepts: function(list, max) {
126
127
  return [list, max && "\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 " + max + " \u0E15\u0E48\u0E2D\u0E44\u0E1F\u0E25\u0E4C"].filter(Boolean).join(" \xB7 ");
127
128
  }
package/dist/index.d.cts CHANGED
@@ -914,6 +914,10 @@ export interface StepItem {
914
914
  label: React$1.ReactNode;
915
915
  /** Short line under the label, e.g. what the step asks for. */
916
916
  description?: React$1.ReactNode | undefined;
917
+ /** `'error'` (5.18, Chamber-OS 112): the step has a problem — say, Save found errors on it. It keeps its place in the
918
+ * order and whether it can be clicked, shows the danger tone with an alert icon instead of the check or number, and
919
+ * adds "has errors" to its accessible name (in place of "completed"). */
920
+ status?: "error" | undefined;
917
921
  }
918
922
  /** Progress through a multi-step flow (wizard, checkout, booking). */
919
923
  export interface StepperProps {
@@ -924,7 +928,8 @@ export interface StepperProps {
924
928
  label?: string | undefined;
925
929
  /** Makes completed steps buttons that go back to them. Upcoming steps never are. */
926
930
  onStepClick?: ((id: string) => void) | undefined;
927
- /** Default `horizontal`. Below `sm` a horizontal stepper shows only the current step ("Step 2 of 4"). */
931
+ /** Default `horizontal`. Below `sm` a horizontal stepper shows only the current step ("Step 2 of 4"; "Step 2 of 4 —
932
+ * has errors" when that step has `status: 'error'`). */
928
933
  orientation?: "horizontal" | "vertical" | undefined;
929
934
  className?: string | undefined;
930
935
  }
@@ -1215,7 +1220,8 @@ export interface StatChange {
1215
1220
  /** e.g. "vs last month". */
1216
1221
  label?: React$1.ReactNode | undefined;
1217
1222
  }
1218
- export interface StatProps {
1223
+ /** `id`, `data-*`, `aria-*`, `style`, `lang` and `dir` go on the `.aura-stat` root (5.17, Chamber-OS 110). */
1224
+ export interface StatProps extends Pick<React$1.HTMLAttributes<HTMLElement>, "id" | "style" | "lang" | "dir">, React$1.AriaAttributes, DataAttributes {
1219
1225
  /** Your router's link (e.g. `Link` from `next/link`) for this component; defaults to AuraProvider's `linkComponent`, then `<a>`. */
1220
1226
  linkComponent?: React$1.ElementType | undefined;
1221
1227
  label: React$1.ReactNode;
@@ -1233,6 +1239,13 @@ export interface StatProps {
1233
1239
  /** The label as a heading of this level (5.14), when the tile titles its section. Not with `onClick` (a heading
1234
1240
  * can't sit in a button); a link is fine. Default: a plain label. */
1235
1241
  headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
1242
+ /** A status line under the value — "Active · renews 1 Jan" with its tone icon (5.17, Chamber-OS 110). Hidden while
1243
+ * `loading`. */
1244
+ status?: React$1.ReactNode | undefined;
1245
+ /** With `href`: `tile` (default) makes the whole tile the link; `label` puts the link on the label (inside its
1246
+ * heading) and stretches its hit area over the tile, so a click anywhere follows it and the tile shows the focus
1247
+ * ring (5.17, Chamber-OS 110). Other links or buttons in the tile stay clickable above it. */
1248
+ linkArea?: "tile" | "label" | undefined;
1236
1249
  className?: string | undefined;
1237
1250
  }
1238
1251
  export interface TimePickerProps extends FieldProps {
@@ -1316,6 +1329,9 @@ export interface ProgressProps {
1316
1329
  showValue?: boolean | undefined;
1317
1330
  /** Replaces the % text, e.g. "3 of 5 files". Also read out as aria-valuetext. */
1318
1331
  valueLabel?: React$1.ReactNode | undefined;
1332
+ /** What screen readers hear (`aria-valuetext`), apart from the shown `valueLabel` (5.17, Chamber-OS 111): show
1333
+ * "2 of 6 used", read "2 used, 1 reserved, 3 remaining of 6". */
1334
+ valueText?: string | undefined;
1319
1335
  /** A reserved amount drawn after `value` as a striped segment of the same tone — queued sends on a quota
1320
1336
  * (5.15, Chamber-OS 89). Both counts are read out ("2 of 6 used, 1 reserved") unless `valueLabel` is given. */
1321
1337
  secondaryValue?: number | undefined;
@@ -1727,6 +1743,8 @@ export interface AuraStrings {
1727
1743
  progressReserved: (value: number, reserved: number, max: number) => string;
1728
1744
  /** The collapsed middle of a Breadcrumb (5.15). */
1729
1745
  breadcrumbMore: string;
1746
+ /** A Stepper step with `status: 'error'` (5.18). */
1747
+ stepError: string;
1730
1748
  }
1731
1749
  /** Built-in strings by locale. th and sv also ship as packs (`@jirawatpyk/aura-react/locales/th`, `/sv`, 5.9); in 6.0
1732
1750
  * only en stays built in and a Thai or Swedish app passes its pack to AuraProvider `strings`. */
package/dist/index.d.ts CHANGED
@@ -914,6 +914,10 @@ export interface StepItem {
914
914
  label: React$1.ReactNode;
915
915
  /** Short line under the label, e.g. what the step asks for. */
916
916
  description?: React$1.ReactNode | undefined;
917
+ /** `'error'` (5.18, Chamber-OS 112): the step has a problem — say, Save found errors on it. It keeps its place in the
918
+ * order and whether it can be clicked, shows the danger tone with an alert icon instead of the check or number, and
919
+ * adds "has errors" to its accessible name (in place of "completed"). */
920
+ status?: "error" | undefined;
917
921
  }
918
922
  /** Progress through a multi-step flow (wizard, checkout, booking). */
919
923
  export interface StepperProps {
@@ -924,7 +928,8 @@ export interface StepperProps {
924
928
  label?: string | undefined;
925
929
  /** Makes completed steps buttons that go back to them. Upcoming steps never are. */
926
930
  onStepClick?: ((id: string) => void) | undefined;
927
- /** Default `horizontal`. Below `sm` a horizontal stepper shows only the current step ("Step 2 of 4"). */
931
+ /** Default `horizontal`. Below `sm` a horizontal stepper shows only the current step ("Step 2 of 4"; "Step 2 of 4 —
932
+ * has errors" when that step has `status: 'error'`). */
928
933
  orientation?: "horizontal" | "vertical" | undefined;
929
934
  className?: string | undefined;
930
935
  }
@@ -1215,7 +1220,8 @@ export interface StatChange {
1215
1220
  /** e.g. "vs last month". */
1216
1221
  label?: React$1.ReactNode | undefined;
1217
1222
  }
1218
- export interface StatProps {
1223
+ /** `id`, `data-*`, `aria-*`, `style`, `lang` and `dir` go on the `.aura-stat` root (5.17, Chamber-OS 110). */
1224
+ export interface StatProps extends Pick<React$1.HTMLAttributes<HTMLElement>, "id" | "style" | "lang" | "dir">, React$1.AriaAttributes, DataAttributes {
1219
1225
  /** Your router's link (e.g. `Link` from `next/link`) for this component; defaults to AuraProvider's `linkComponent`, then `<a>`. */
1220
1226
  linkComponent?: React$1.ElementType | undefined;
1221
1227
  label: React$1.ReactNode;
@@ -1233,6 +1239,13 @@ export interface StatProps {
1233
1239
  /** The label as a heading of this level (5.14), when the tile titles its section. Not with `onClick` (a heading
1234
1240
  * can't sit in a button); a link is fine. Default: a plain label. */
1235
1241
  headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
1242
+ /** A status line under the value — "Active · renews 1 Jan" with its tone icon (5.17, Chamber-OS 110). Hidden while
1243
+ * `loading`. */
1244
+ status?: React$1.ReactNode | undefined;
1245
+ /** With `href`: `tile` (default) makes the whole tile the link; `label` puts the link on the label (inside its
1246
+ * heading) and stretches its hit area over the tile, so a click anywhere follows it and the tile shows the focus
1247
+ * ring (5.17, Chamber-OS 110). Other links or buttons in the tile stay clickable above it. */
1248
+ linkArea?: "tile" | "label" | undefined;
1236
1249
  className?: string | undefined;
1237
1250
  }
1238
1251
  export interface TimePickerProps extends FieldProps {
@@ -1316,6 +1329,9 @@ export interface ProgressProps {
1316
1329
  showValue?: boolean | undefined;
1317
1330
  /** Replaces the % text, e.g. "3 of 5 files". Also read out as aria-valuetext. */
1318
1331
  valueLabel?: React$1.ReactNode | undefined;
1332
+ /** What screen readers hear (`aria-valuetext`), apart from the shown `valueLabel` (5.17, Chamber-OS 111): show
1333
+ * "2 of 6 used", read "2 used, 1 reserved, 3 remaining of 6". */
1334
+ valueText?: string | undefined;
1319
1335
  /** A reserved amount drawn after `value` as a striped segment of the same tone — queued sends on a quota
1320
1336
  * (5.15, Chamber-OS 89). Both counts are read out ("2 of 6 used, 1 reserved") unless `valueLabel` is given. */
1321
1337
  secondaryValue?: number | undefined;
@@ -1727,6 +1743,8 @@ export interface AuraStrings {
1727
1743
  progressReserved: (value: number, reserved: number, max: number) => string;
1728
1744
  /** The collapsed middle of a Breadcrumb (5.15). */
1729
1745
  breadcrumbMore: string;
1746
+ /** A Stepper step with `status: 'error'` (5.18). */
1747
+ stepError: string;
1730
1748
  }
1731
1749
  /** Built-in strings by locale. th and sv also ship as packs (`@jirawatpyk/aura-react/locales/th`, `/sv`, 5.9); in 6.0
1732
1750
  * only en stays built in and a Thai or Swedish app passes its pack to AuraProvider `strings`. */
@@ -147,6 +147,7 @@ var sv = {
147
147
  return nsv(v) + " av " + nsv(max) + " anv\xE4nda, " + nsv(r) + " reserverade";
148
148
  },
149
149
  breadcrumbMore: "Visa hela s\xF6kv\xE4gen",
150
+ stepError: "har fel",
150
151
  accepts: function(list, max) {
151
152
  return [list, max && "h\xF6gst " + max + " per fil"].filter(Boolean).join(", ");
152
153
  }
@@ -87,6 +87,8 @@ export interface AuraStrings {
87
87
  progressReserved: (value: number, reserved: number, max: number) => string;
88
88
  /** The collapsed middle of a Breadcrumb (5.15). */
89
89
  breadcrumbMore: string;
90
+ /** A Stepper step with `status: 'error'` (5.18). */
91
+ stepError: string;
90
92
  }
91
93
  /** Built-in strings by locale. th and sv also ship as packs (`@jirawatpyk/aura-react/locales/th`, `/sv`, 5.9); in 6.0
92
94
  * only en stays built in and a Thai or Swedish app passes its pack to AuraProvider `strings`. */
@@ -87,6 +87,8 @@ export interface AuraStrings {
87
87
  progressReserved: (value: number, reserved: number, max: number) => string;
88
88
  /** The collapsed middle of a Breadcrumb (5.15). */
89
89
  breadcrumbMore: string;
90
+ /** A Stepper step with `status: 'error'` (5.18). */
91
+ stepError: string;
90
92
  }
91
93
  /** Built-in strings by locale. th and sv also ship as packs (`@jirawatpyk/aura-react/locales/th`, `/sv`, 5.9); in 6.0
92
94
  * only en stays built in and a Thai or Swedish app passes its pack to AuraProvider `strings`. */
@@ -147,6 +147,7 @@ var th = {
147
147
  return "\u0E43\u0E0A\u0E49\u0E41\u0E25\u0E49\u0E27 " + n(v) + " \u0E08\u0E32\u0E01 " + n(max) + " \u0E08\u0E2D\u0E07\u0E44\u0E27\u0E49 " + n(r);
148
148
  },
149
149
  breadcrumbMore: "\u0E41\u0E2A\u0E14\u0E07\u0E40\u0E2A\u0E49\u0E19\u0E17\u0E32\u0E07\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
150
+ stepError: "\u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14",
150
151
  accepts: function(list, max) {
151
152
  return [list, max && "\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 " + max + " \u0E15\u0E48\u0E2D\u0E44\u0E1F\u0E25\u0E4C"].filter(Boolean).join(" \xB7 ");
152
153
  }
@@ -87,6 +87,8 @@ export interface AuraStrings {
87
87
  progressReserved: (value: number, reserved: number, max: number) => string;
88
88
  /** The collapsed middle of a Breadcrumb (5.15). */
89
89
  breadcrumbMore: string;
90
+ /** A Stepper step with `status: 'error'` (5.18). */
91
+ stepError: string;
90
92
  }
91
93
  /** Built-in strings by locale. th and sv also ship as packs (`@jirawatpyk/aura-react/locales/th`, `/sv`, 5.9); in 6.0
92
94
  * only en stays built in and a Thai or Swedish app passes its pack to AuraProvider `strings`. */
@@ -87,6 +87,8 @@ export interface AuraStrings {
87
87
  progressReserved: (value: number, reserved: number, max: number) => string;
88
88
  /** The collapsed middle of a Breadcrumb (5.15). */
89
89
  breadcrumbMore: string;
90
+ /** A Stepper step with `status: 'error'` (5.18). */
91
+ stepError: string;
90
92
  }
91
93
  /** Built-in strings by locale. th and sv also ship as packs (`@jirawatpyk/aura-react/locales/th`, `/sv`, 5.9); in 6.0
92
94
  * only en stays built in and a Thai or Swedish app passes its pack to AuraProvider `strings`. */
@@ -331,6 +331,7 @@ var th = {
331
331
  return "\u0E43\u0E0A\u0E49\u0E41\u0E25\u0E49\u0E27 " + n(v) + " \u0E08\u0E32\u0E01 " + n(max) + " \u0E08\u0E2D\u0E07\u0E44\u0E27\u0E49 " + n(r);
332
332
  },
333
333
  breadcrumbMore: "\u0E41\u0E2A\u0E14\u0E07\u0E40\u0E2A\u0E49\u0E19\u0E17\u0E32\u0E07\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
334
+ stepError: "\u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14",
334
335
  accepts: function(list, max) {
335
336
  return [list, max && "\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 " + max + " \u0E15\u0E48\u0E2D\u0E44\u0E1F\u0E25\u0E4C"].filter(Boolean).join(" \xB7 ");
336
337
  }
@@ -461,6 +462,7 @@ var sv = {
461
462
  return nsv(v) + " av " + nsv(max) + " anv\xE4nda, " + nsv(r) + " reserverade";
462
463
  },
463
464
  breadcrumbMore: "Visa hela s\xF6kv\xE4gen",
465
+ stepError: "har fel",
464
466
  accepts: function(list, max) {
465
467
  return [list, max && "h\xF6gst " + max + " per fil"].filter(Boolean).join(", ");
466
468
  }
@@ -592,6 +594,7 @@ var STRINGS = {
592
594
  return n2(v) + " of " + n2(max) + " used, " + n2(r) + " reserved";
593
595
  },
594
596
  breadcrumbMore: "Show the full path",
597
+ stepError: "has errors",
595
598
  accepts: function(list, max) {
596
599
  return [list, max && "up to " + max + " each"].filter(Boolean).join(", ");
597
600
  }
@@ -1212,8 +1215,17 @@ function statElement(props, ref, Link) {
1212
1215
  const tone2 = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
1213
1216
  const v = props.value;
1214
1217
  const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
1215
- const Tag = props.href ? Link : props.onClick ? "button" : "div";
1218
+ const labelLink = !!props.href && props.linkArea === "label";
1219
+ const Tag = labelLink ? "div" : props.href ? Link : props.onClick ? "button" : "div";
1216
1220
  const interactive = !!(props.href || props.onClick);
1221
+ const LINK_ARIA = ["aria-label", "aria-labelledby", "aria-describedby", "aria-current"];
1222
+ const root = {}, toLink = {};
1223
+ Object.keys(props).forEach(function(k) {
1224
+ const v2 = props[k];
1225
+ if (labelLink && LINK_ARIA.indexOf(k) >= 0) toLink[k] = v2;
1226
+ else if (k === "id" || k === "style" || k === "lang" || k === "dir" || /^(aria|data)-/.test(k)) root[k] = v2;
1227
+ });
1228
+ const busy = props.loading || props["aria-busy"] || void 0;
1217
1229
  if (props.headingLevel && Tag === "button")
1218
1230
  devWarnOnce(
1219
1231
  "stat-heading-button",
@@ -1224,15 +1236,23 @@ function statElement(props, ref, Link) {
1224
1236
  return /* @__PURE__ */ React3.createElement(
1225
1237
  Tag,
1226
1238
  {
1239
+ ...root,
1227
1240
  ref,
1228
- className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
1229
- href: props.href,
1230
- onClick: props.onClick,
1241
+ className: cx(
1242
+ "aura-stat",
1243
+ interactive && "is-interactive",
1244
+ labelLink && "aura-stat--label-link",
1245
+ props.loading && "is-loading",
1246
+ props.className
1247
+ ),
1248
+ href: labelLink ? void 0 : props.href,
1249
+ onClick: labelLink ? void 0 : props.onClick,
1231
1250
  type: Tag === "button" ? "button" : void 0,
1232
- "aria-busy": props.loading || void 0
1251
+ "aria-busy": busy
1233
1252
  },
1234
- /* @__PURE__ */ React3.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React3.createElement(LabelTag, { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React3.createElement(Icon, { name: props.icon })) : null),
1253
+ /* @__PURE__ */ React3.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React3.createElement(LabelTag, { className: "aura-stat__label" }, labelLink ? /* @__PURE__ */ React3.createElement(Link, { ...toLink, href: props.href, onClick: props.onClick, className: "aura-stat__link" }, props.label) : props.label), props.icon ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React3.createElement(Icon, { name: props.icon })) : null),
1235
1254
  props.loading ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React3.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React3.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
1255
+ props.status != null && props.status !== false && props.status !== "" && !props.loading ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__status" }, props.status) : null,
1236
1256
  ch && !props.loading || props.caption ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React3.createElement("span", { className: cx("aura-stat__change", "is-" + tone2) }, /* @__PURE__ */ React3.createElement(
1237
1257
  Icon,
1238
1258
  {
@@ -107,6 +107,10 @@ export interface DateDisplayOptions {
107
107
  /** `buddhist` (พ.ศ.; default for th) or `gregory` (ค.ศ.; default for en and sv). */
108
108
  calendar?: "buddhist" | "gregory" | undefined;
109
109
  }
110
+ /** `data-*` attributes in an object prop (TypeScript checks object literals, where JSX would allow them anyway). */
111
+ export type DataAttributes = {
112
+ [key: `data-${string}`]: string | number | boolean | undefined;
113
+ };
110
114
  export interface StatChange {
111
115
  /** Shown as written, e.g. "+12%" or "−3". */
112
116
  value: React$1.ReactNode;
@@ -116,7 +120,8 @@ export interface StatChange {
116
120
  /** e.g. "vs last month". */
117
121
  label?: React$1.ReactNode | undefined;
118
122
  }
119
- export interface StatProps {
123
+ /** `id`, `data-*`, `aria-*`, `style`, `lang` and `dir` go on the `.aura-stat` root (5.17, Chamber-OS 110). */
124
+ export interface StatProps extends Pick<React$1.HTMLAttributes<HTMLElement>, "id" | "style" | "lang" | "dir">, React$1.AriaAttributes, DataAttributes {
120
125
  /** Your router's link (e.g. `Link` from `next/link`) for this component; defaults to AuraProvider's `linkComponent`, then `<a>`. */
121
126
  linkComponent?: React$1.ElementType | undefined;
122
127
  label: React$1.ReactNode;
@@ -134,6 +139,13 @@ export interface StatProps {
134
139
  /** The label as a heading of this level (5.14), when the tile titles its section. Not with `onClick` (a heading
135
140
  * can't sit in a button); a link is fine. Default: a plain label. */
136
141
  headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
142
+ /** A status line under the value — "Active · renews 1 Jan" with its tone icon (5.17, Chamber-OS 110). Hidden while
143
+ * `loading`. */
144
+ status?: React$1.ReactNode | undefined;
145
+ /** With `href`: `tile` (default) makes the whole tile the link; `label` puts the link on the label (inside its
146
+ * heading) and stretches its hit area over the tile, so a click anywhere follows it and the tile shows the focus
147
+ * ring (5.17, Chamber-OS 110). Other links or buttons in the tile stay clickable above it. */
148
+ linkArea?: "tile" | "label" | undefined;
137
149
  className?: string | undefined;
138
150
  }
139
151
  export type Tone = "neutral" | "accent" | "success" | "warning" | "danger";
@@ -294,6 +306,8 @@ export interface AuraStrings {
294
306
  progressReserved: (value: number, reserved: number, max: number) => string;
295
307
  /** The collapsed middle of a Breadcrumb (5.15). */
296
308
  breadcrumbMore: string;
309
+ /** A Stepper step with `status: 'error'` (5.18). */
310
+ stepError: string;
297
311
  }
298
312
  /** Built-in strings by locale. th and sv also ship as packs (`@jirawatpyk/aura-react/locales/th`, `/sv`, 5.9); in 6.0
299
313
  * only en stays built in and a Thai or Swedish app passes its pack to AuraProvider `strings`. */
@@ -107,6 +107,10 @@ export interface DateDisplayOptions {
107
107
  /** `buddhist` (พ.ศ.; default for th) or `gregory` (ค.ศ.; default for en and sv). */
108
108
  calendar?: "buddhist" | "gregory" | undefined;
109
109
  }
110
+ /** `data-*` attributes in an object prop (TypeScript checks object literals, where JSX would allow them anyway). */
111
+ export type DataAttributes = {
112
+ [key: `data-${string}`]: string | number | boolean | undefined;
113
+ };
110
114
  export interface StatChange {
111
115
  /** Shown as written, e.g. "+12%" or "−3". */
112
116
  value: React$1.ReactNode;
@@ -116,7 +120,8 @@ export interface StatChange {
116
120
  /** e.g. "vs last month". */
117
121
  label?: React$1.ReactNode | undefined;
118
122
  }
119
- export interface StatProps {
123
+ /** `id`, `data-*`, `aria-*`, `style`, `lang` and `dir` go on the `.aura-stat` root (5.17, Chamber-OS 110). */
124
+ export interface StatProps extends Pick<React$1.HTMLAttributes<HTMLElement>, "id" | "style" | "lang" | "dir">, React$1.AriaAttributes, DataAttributes {
120
125
  /** Your router's link (e.g. `Link` from `next/link`) for this component; defaults to AuraProvider's `linkComponent`, then `<a>`. */
121
126
  linkComponent?: React$1.ElementType | undefined;
122
127
  label: React$1.ReactNode;
@@ -134,6 +139,13 @@ export interface StatProps {
134
139
  /** The label as a heading of this level (5.14), when the tile titles its section. Not with `onClick` (a heading
135
140
  * can't sit in a button); a link is fine. Default: a plain label. */
136
141
  headingLevel?: 2 | 3 | 4 | 5 | 6 | undefined;
142
+ /** A status line under the value — "Active · renews 1 Jan" with its tone icon (5.17, Chamber-OS 110). Hidden while
143
+ * `loading`. */
144
+ status?: React$1.ReactNode | undefined;
145
+ /** With `href`: `tile` (default) makes the whole tile the link; `label` puts the link on the label (inside its
146
+ * heading) and stretches its hit area over the tile, so a click anywhere follows it and the tile shows the focus
147
+ * ring (5.17, Chamber-OS 110). Other links or buttons in the tile stay clickable above it. */
148
+ linkArea?: "tile" | "label" | undefined;
137
149
  className?: string | undefined;
138
150
  }
139
151
  export type Tone = "neutral" | "accent" | "success" | "warning" | "danger";
@@ -294,6 +306,8 @@ export interface AuraStrings {
294
306
  progressReserved: (value: number, reserved: number, max: number) => string;
295
307
  /** The collapsed middle of a Breadcrumb (5.15). */
296
308
  breadcrumbMore: string;
309
+ /** A Stepper step with `status: 'error'` (5.18). */
310
+ stepError: string;
297
311
  }
298
312
  /** Built-in strings by locale. th and sv also ship as packs (`@jirawatpyk/aura-react/locales/th`, `/sv`, 5.9); in 6.0
299
313
  * only en stays built in and a Thai or Swedish app passes its pack to AuraProvider `strings`. */
@@ -272,6 +272,7 @@ var th = {
272
272
  return "\u0E43\u0E0A\u0E49\u0E41\u0E25\u0E49\u0E27 " + n(v) + " \u0E08\u0E32\u0E01 " + n(max) + " \u0E08\u0E2D\u0E07\u0E44\u0E27\u0E49 " + n(r);
273
273
  },
274
274
  breadcrumbMore: "\u0E41\u0E2A\u0E14\u0E07\u0E40\u0E2A\u0E49\u0E19\u0E17\u0E32\u0E07\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
275
+ stepError: "\u0E21\u0E35\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14",
275
276
  accepts: function(list, max) {
276
277
  return [list, max && "\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 " + max + " \u0E15\u0E48\u0E2D\u0E44\u0E1F\u0E25\u0E4C"].filter(Boolean).join(" \xB7 ");
277
278
  }
@@ -402,6 +403,7 @@ var sv = {
402
403
  return nsv(v) + " av " + nsv(max) + " anv\xE4nda, " + nsv(r) + " reserverade";
403
404
  },
404
405
  breadcrumbMore: "Visa hela s\xF6kv\xE4gen",
406
+ stepError: "har fel",
405
407
  accepts: function(list, max) {
406
408
  return [list, max && "h\xF6gst " + max + " per fil"].filter(Boolean).join(", ");
407
409
  }
@@ -533,6 +535,7 @@ var STRINGS = {
533
535
  return n2(v) + " of " + n2(max) + " used, " + n2(r) + " reserved";
534
536
  },
535
537
  breadcrumbMore: "Show the full path",
538
+ stepError: "has errors",
536
539
  accepts: function(list, max) {
537
540
  return [list, max && "up to " + max + " each"].filter(Boolean).join(", ");
538
541
  }
@@ -1153,8 +1156,17 @@ function statElement(props, ref, Link) {
1153
1156
  const tone2 = ch && (ch.tone || (dir === "up" ? "positive" : dir === "down" ? "negative" : "neutral"));
1154
1157
  const v = props.value;
1155
1158
  const numeric = typeof v === "number" || typeof v === "string" && /\d/.test(v) && /^[\s\d.,:+\-\u2212%()\u0E3F$\u20AC\u00A3\u00A5kKmMbB]+$/.test(v.replace(/\b[A-Z]{3}\b/g, ""));
1156
- const Tag = props.href ? Link : props.onClick ? "button" : "div";
1159
+ const labelLink = !!props.href && props.linkArea === "label";
1160
+ const Tag = labelLink ? "div" : props.href ? Link : props.onClick ? "button" : "div";
1157
1161
  const interactive = !!(props.href || props.onClick);
1162
+ const LINK_ARIA = ["aria-label", "aria-labelledby", "aria-describedby", "aria-current"];
1163
+ const root = {}, toLink = {};
1164
+ Object.keys(props).forEach(function(k) {
1165
+ const v2 = props[k];
1166
+ if (labelLink && LINK_ARIA.indexOf(k) >= 0) toLink[k] = v2;
1167
+ else if (k === "id" || k === "style" || k === "lang" || k === "dir" || /^(aria|data)-/.test(k)) root[k] = v2;
1168
+ });
1169
+ const busy = props.loading || props["aria-busy"] || void 0;
1158
1170
  if (props.headingLevel && Tag === "button")
1159
1171
  devWarnOnce(
1160
1172
  "stat-heading-button",
@@ -1165,15 +1177,23 @@ function statElement(props, ref, Link) {
1165
1177
  return /* @__PURE__ */ React3.createElement(
1166
1178
  Tag,
1167
1179
  {
1180
+ ...root,
1168
1181
  ref,
1169
- className: cx("aura-stat", interactive && "is-interactive", props.loading && "is-loading", props.className),
1170
- href: props.href,
1171
- onClick: props.onClick,
1182
+ className: cx(
1183
+ "aura-stat",
1184
+ interactive && "is-interactive",
1185
+ labelLink && "aura-stat--label-link",
1186
+ props.loading && "is-loading",
1187
+ props.className
1188
+ ),
1189
+ href: labelLink ? void 0 : props.href,
1190
+ onClick: labelLink ? void 0 : props.onClick,
1172
1191
  type: Tag === "button" ? "button" : void 0,
1173
- "aria-busy": props.loading || void 0
1192
+ "aria-busy": busy
1174
1193
  },
1175
- /* @__PURE__ */ React3.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React3.createElement(LabelTag, { className: "aura-stat__label" }, props.label), props.icon ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React3.createElement(Icon, { name: props.icon })) : null),
1194
+ /* @__PURE__ */ React3.createElement(HeadTag, { className: "aura-stat__head" }, /* @__PURE__ */ React3.createElement(LabelTag, { className: "aura-stat__label" }, labelLink ? /* @__PURE__ */ React3.createElement(Link, { ...toLink, href: props.href, onClick: props.onClick, className: "aura-stat__link" }, props.label) : props.label), props.icon ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__icon" }, /* @__PURE__ */ React3.createElement(Icon, { name: props.icon })) : null),
1176
1195
  props.loading ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__value" }, /* @__PURE__ */ React3.createElement("span", { className: "aura-skel aura-stat__skel" })) : /* @__PURE__ */ React3.createElement("span", { className: cx("aura-stat__value", numeric && "is-numeric") }, props.value, props.unit ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__unit" }, props.unit) : null),
1196
+ props.status != null && props.status !== false && props.status !== "" && !props.loading ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__status" }, props.status) : null,
1177
1197
  ch && !props.loading || props.caption ? /* @__PURE__ */ React3.createElement("span", { className: "aura-stat__foot" }, ch && !props.loading ? /* @__PURE__ */ React3.createElement("span", { className: cx("aura-stat__change", "is-" + tone2) }, /* @__PURE__ */ React3.createElement(
1178
1198
  Icon,
1179
1199
  {
package/dist/styles.css CHANGED
@@ -1032,6 +1032,20 @@ button.aura-stat { font: inherit; width: 100%; cursor: pointer; }
1032
1032
  .aura-stat__change.is-negative { color: var(--aura-fg-danger); }
1033
1033
  .aura-stat__change.is-neutral { color: var(--aura-fg-secondary); }
1034
1034
  .aura-stat__caption { color: var(--aura-fg-secondary); }
1035
+ /* 5.17 (Chamber-OS 110): a status line under the value, and a link on the label that covers the tile. */
1036
+ .aura-stat__status { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-1) var(--aura-space-2); font-size: 13px; color: var(--aura-fg-secondary); }
1037
+ .aura-stat--label-link { position: relative; }
1038
+ .aura-stat__link { color: inherit; text-decoration: none; }
1039
+ .aura-stat__link::after { content: ''; position: absolute; inset: 0; border-radius: var(--aura-card-radius); }
1040
+ /* The ring is drawn on the tile where :has() works; elsewhere the link keeps its own. */
1041
+ .aura-stat__link:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 2px; }
1042
+ @supports selector(:has(*)) {
1043
+ .aura-stat__link:focus-visible { outline: none; }
1044
+ .aura-stat--label-link:has(.aura-stat__link:focus-visible) { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
1045
+ }
1046
+ .aura-stat--label-link:hover .aura-stat__link { text-decoration: underline; text-underline-offset: 3px; }
1047
+ /* Anything else interactive in the tile sits above the stretched link. No specificity, so a control's own position wins. */
1048
+ :where(.aura-stat--label-link) :where(a, button, input, select, textarea, label, summary, [tabindex]):not(:where(.aura-stat__link)) { position: relative; z-index: 1; }
1035
1049
 
1036
1050
  /* ---------- TimePicker ---------- */
1037
1051
  .aura-time__popover { min-width: 160px; }
@@ -1280,6 +1294,10 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1280
1294
  .aura-stepper__compact { display: none; margin: var(--aura-space-2) 0 0; font-size: 13px; }
1281
1295
  .aura-stepper__count { color: var(--aura-fg-secondary); margin-right: var(--aura-space-2); }
1282
1296
  .aura-stepper__compact-label { font-weight: 600; }
1297
+ /* 5.18 (Chamber-OS 112): a step with errors — the danger fill and an alert icon; outlined when it is the current step. */
1298
+ .aura-stepper__item.is-error .aura-stepper__marker { background: var(--aura-button-danger-bg); border-color: var(--aura-button-danger-bg); color: var(--aura-button-danger-fg); }
1299
+ .aura-stepper__item.is-current.is-error .aura-stepper__marker { background: var(--aura-bg-surface); border: 2px solid var(--aura-fg-danger); color: var(--aura-fg-danger); }
1300
+ .aura-stepper__item.is-error .aura-stepper__label, .aura-stepper__count-error { color: var(--aura-fg-danger); }
1283
1301
 
1284
1302
  /* ---------- SegmentedControl ---------- */
1285
1303
  .aura-segmented {
@@ -1034,6 +1034,20 @@ button.aura-stat { font: inherit; width: 100%; cursor: pointer; }
1034
1034
  .aura-stat__change.is-negative { color: var(--aura-fg-danger); }
1035
1035
  .aura-stat__change.is-neutral { color: var(--aura-fg-secondary); }
1036
1036
  .aura-stat__caption { color: var(--aura-fg-secondary); }
1037
+ /* 5.17 (Chamber-OS 110): a status line under the value, and a link on the label that covers the tile. */
1038
+ .aura-stat__status { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-1) var(--aura-space-2); font-size: 13px; color: var(--aura-fg-secondary); }
1039
+ .aura-stat--label-link { position: relative; }
1040
+ .aura-stat__link { color: inherit; text-decoration: none; }
1041
+ .aura-stat__link::after { content: ''; position: absolute; inset: 0; border-radius: var(--aura-card-radius); }
1042
+ /* The ring is drawn on the tile where :has() works; elsewhere the link keeps its own. */
1043
+ .aura-stat__link:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 2px; }
1044
+ @supports selector(:has(*)) {
1045
+ .aura-stat__link:focus-visible { outline: none; }
1046
+ .aura-stat--label-link:has(.aura-stat__link:focus-visible) { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
1047
+ }
1048
+ .aura-stat--label-link:hover .aura-stat__link { text-decoration: underline; text-underline-offset: 3px; }
1049
+ /* Anything else interactive in the tile sits above the stretched link. No specificity, so a control's own position wins. */
1050
+ :where(.aura-stat--label-link) :where(a, button, input, select, textarea, label, summary, [tabindex]):not(:where(.aura-stat__link)) { position: relative; z-index: 1; }
1037
1051
 
1038
1052
  /* ---------- TimePicker ---------- */
1039
1053
  .aura-time__popover { min-width: 160px; }
@@ -1282,6 +1296,10 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1282
1296
  .aura-stepper__compact { display: none; margin: var(--aura-space-2) 0 0; font-size: 13px; }
1283
1297
  .aura-stepper__count { color: var(--aura-fg-secondary); margin-right: var(--aura-space-2); }
1284
1298
  .aura-stepper__compact-label { font-weight: 600; }
1299
+ /* 5.18 (Chamber-OS 112): a step with errors — the danger fill and an alert icon; outlined when it is the current step. */
1300
+ .aura-stepper__item.is-error .aura-stepper__marker { background: var(--aura-button-danger-bg); border-color: var(--aura-button-danger-bg); color: var(--aura-button-danger-fg); }
1301
+ .aura-stepper__item.is-current.is-error .aura-stepper__marker { background: var(--aura-bg-surface); border: 2px solid var(--aura-fg-danger); color: var(--aura-fg-danger); }
1302
+ .aura-stepper__item.is-error .aura-stepper__label, .aura-stepper__count-error { color: var(--aura-fg-danger); }
1285
1303
 
1286
1304
  /* ---------- SegmentedControl ---------- */
1287
1305
  .aura-segmented {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "5.16.1",
3
+ "version": "5.18.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",