@jirawatpyk/aura-react 5.67.0 → 5.69.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,17 @@ 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.69 — Chamber-OS addendum 86 (#185)
190
+
191
+ - **ActionBar's edge look:** a `position="viewport"` bar takes `look="edge"` (every width) or `edgeBelow="sm"` / `"md"` (the strip below 640px / 768px, the card at and above it — as PageHeader's `actionsBarBelow`) and draws PageHeader's edge strip: the screen's full width, on the bottom edge or directly on a BottomNav, a top border only (no radius, shadow or top margin), the home-indicator inset as padding inside it. `look="floating"` (default) is the inset card, unchanged; a `container` bar ignores both props.
192
+ - **Its promises hold:** the strip is `position: fixed`, so it stays on screen for the whole page, not only while its form is. As PageHeader's bar, `<html>` gets its measured height as `--aura-actionbar-height` (60px before hydration; the home-indicator padding counts), the page's end is padded by it (AppShell's content, else the `body`) and `scroll-padding-bottom` keeps a focused field clear — so the last field, and anything after the form, scroll clear. The ActionBar root is the strip, so `getBoundingClientRect()` on it measures the strip. On a screen 30rem tall or less it is static, in the flow, as every bar. An idle bulk bar (`selected={0}`) stays folded away and adds no room. Drop your own clearance (a hook adding the bar's height to `scroll-padding-bottom` or page padding) when you adopt `look` / `edgeBelow`, or it is counted twice. Buttons stay 44px on a phone; `start`, `status`, `children`, the region and its label are unchanged.
193
+ - **Mind:** a transform, filter, `will-change` or paint containment on an ancestor places a fixed strip against that ancestor instead of the screen (a dev warning says so), and `look="edge"` at desktop widths spans the screen under a side nav — use `edgeBelow` for a phone-only strip. Passing both `look="edge"` and `edgeBelow` warns in development (`look` wins). Replace a hand-built `position: fixed` pay band with `<ActionBar edgeBelow="sm">`.
194
+
195
+ ## 5.68 — Chamber-OS addendum 85 (#184)
196
+
197
+ - **A disabled group dims its legend:** `RadioGroup` and `CheckboxGroup` with `disabled` add `is-disabled` to the fieldset, so the legend takes the disabled colour (`fg-secondary`) a disabled `Field`'s label has — a form that disables its fields while submitting no longer shows group legends at full contrast over dimmed controls.
198
+ - **`errorLive` — an error line that announces:** on `CheckboxGroup`, `RadioGroup` and `Field`, `errorLive` gives the error line `aria-live="polite"` and keeps it in the page, empty and taking no room, while there is no error (the hint shows), so a message that appears as the user changes the field ("Choose at least one membership category" the moment the last box is unticked) is read once. Off by default: an error shown on submit, with focus moved to the field, is read already. Under a `Field`, your own control should still point at `{id}-error` in its `aria-describedby` while there is an error, so the message stays its description. Not on TextField and the other controls. Hand-written markup with an empty `.aura-field__error` placeholder is now hidden until it has content. Drop a hand-made `sr-only` `role="status"` beside the group.
199
+
189
200
  ## 5.67 — Chamber-OS addendum 84 (#183)
190
201
 
191
202
  - **`CheckboxGroup` — RadioGroup's many-choice twin:** one `fieldset` named by its `legend`, with RadioGroup's props — `label`, `hint`, `error`, `required` (the same `*` marker), `disabled` (every box), `orientation`, `touchHeight` (44px rows), `options` (`{ value, label, description?, disabled? }` or strings), `name` — and a set of values: `value: string[]` / `defaultValue` / `onChange(values)`, always in option order whatever order they were ticked in. The group is described by the hint, or by the error line, which also marks every box `aria-invalid` and edges the empty ones. `required` shows the `*` and adds a hidden "(required)" to the group's name (localised), since a group can't take `aria-required` and a native `required` on each box would demand all of them; "at least one" is yours to check (pass `error`, which each box then also points at). Values not among the options (archived, still loading) are kept, after the others. `focus()` on the ref lands on the first ticked enabled box. Replaces a hand-made fieldset of Checkboxes.
@@ -409,6 +409,14 @@ window.Aura = (() => {
409
409
  return !e.defaultPrevented && e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
410
410
  }
411
411
  var FilterSheet = React3.createContext(false);
412
+ function fixedTrapped(el) {
413
+ for (let p = el.parentElement; p && p !== document.documentElement; p = p.parentElement) {
414
+ const s = getComputedStyle(p);
415
+ if (s.transform !== "none" || s.filter !== "none" || (s.backdropFilter || "none") !== "none" || s.contentVisibility === "auto" || s.perspective !== "none" || /transform|filter|perspective/.test(s.willChange) || /paint|layout|strict|content/.test(s.contain))
416
+ return true;
417
+ }
418
+ return false;
419
+ }
412
420
 
413
421
  // src/display.tsx
414
422
  var React4 = __toESM(require_react(), 1);
@@ -2081,12 +2089,19 @@ window.Aura = (() => {
2081
2089
  ) : null,
2082
2090
  hasAddon(props.labelAddon) ? /* @__PURE__ */ React13.createElement("div", { className: "aura-field__addon", id: props.id + "-addon" }, props.labelAddon) : null,
2083
2091
  props.children,
2084
- props.error ? /* @__PURE__ */ React13.createElement("p", { className: "aura-field__error", id: props.id + "-error" }, /* @__PURE__ */ React13.createElement(Icon, { name: /* @__PURE__ */ React13.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React13.createElement("p", { className: "aura-field__hint", id: props.id + "-hint" }, props.hint) : null
2092
+ fieldNote(props.id, props.error, props.hint, props.errorLive)
2085
2093
  );
2086
2094
  });
2087
2095
  function describedBy(id, p) {
2088
2096
  return p.error ? id + "-error" : p.hint ? id + "-hint" : void 0;
2089
2097
  }
2098
+ function fieldNote(id, error, hint, live) {
2099
+ const err = error ? /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Icon, { name: /* @__PURE__ */ React13.createElement(IconCircleAlert, null), size: 14 }), error) : null;
2100
+ const hintLine = hint ? /* @__PURE__ */ React13.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, hint) : null;
2101
+ if (!live)
2102
+ return err ? /* @__PURE__ */ React13.createElement("p", { className: "aura-field__error", id: id + "-error" }, err) : hintLine;
2103
+ return /* @__PURE__ */ React13.createElement(React13.Fragment, null, err ? null : hintLine, /* @__PURE__ */ React13.createElement("p", { className: "aura-field__error", id: id == null ? void 0 : id + "-error", "aria-live": "polite" }, err));
2104
+ }
2090
2105
  function hasAddon(n3) {
2091
2106
  return n3 != null && typeof n3 !== "boolean" && n3 !== "";
2092
2107
  }
@@ -3872,6 +3887,7 @@ window.Aura = (() => {
3872
3887
  className: cx(
3873
3888
  "aura-field aura-radio-group",
3874
3889
  props.error && "is-invalid",
3890
+ props.disabled && "is-disabled",
3875
3891
  touchClass(props.touchHeight),
3876
3892
  props.className
3877
3893
  ),
@@ -3901,7 +3917,7 @@ window.Aura = (() => {
3901
3917
  }
3902
3918
  ), /* @__PURE__ */ React20.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React20.createElement("span", { className: "aura-choice__label", id: oid ? oid + "-label" : void 0 }, v.label), v.description ? /* @__PURE__ */ React20.createElement("span", { className: "aura-choice__desc", id: oid + "-desc" }, v.description) : null));
3903
3919
  })),
3904
- props.error ? /* @__PURE__ */ React20.createElement("p", { className: "aura-field__error", id: id + "-error" }, /* @__PURE__ */ React20.createElement(Icon, { name: /* @__PURE__ */ React20.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React20.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, props.hint) : null
3920
+ fieldNote(id, props.error, props.hint, props.errorLive)
3905
3921
  );
3906
3922
  });
3907
3923
 
@@ -3936,6 +3952,7 @@ window.Aura = (() => {
3936
3952
  className: cx(
3937
3953
  "aura-field aura-radio-group aura-checkbox-group",
3938
3954
  props.error && "is-invalid",
3955
+ props.disabled && "is-disabled",
3939
3956
  touchClass(props.touchHeight),
3940
3957
  props.className
3941
3958
  ),
@@ -3973,7 +3990,7 @@ window.Aura = (() => {
3973
3990
  v.label
3974
3991
  );
3975
3992
  })),
3976
- props.error ? /* @__PURE__ */ React21.createElement("p", { className: "aura-field__error", id: id + "-error" }, /* @__PURE__ */ React21.createElement(Icon, { name: /* @__PURE__ */ React21.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React21.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, props.hint) : null
3993
+ fieldNote(id, props.error, props.hint, props.errorLive)
3977
3994
  );
3978
3995
  }
3979
3996
  );
@@ -8104,14 +8121,6 @@ window.Aura = (() => {
8104
8121
  // src/PageHeaderClient.tsx
8105
8122
  var NAME = "--aura-page-actions-height";
8106
8123
  var bars = [];
8107
- function trapped(el) {
8108
- for (let p = el.parentElement; p && p !== document.documentElement; p = p.parentElement) {
8109
- const s = getComputedStyle(p);
8110
- if (s.transform !== "none" || s.filter !== "none" || (s.backdropFilter || "none") !== "none" || s.contentVisibility === "auto" || s.perspective !== "none" || /transform|filter|perspective/.test(s.willChange) || /paint|layout|strict|content/.test(s.contain))
8111
- return true;
8112
- }
8113
- return false;
8114
- }
8115
8124
  var PageHeader3 = React45.forwardRef(function PageHeader4(props, ref) {
8116
8125
  const own = React45.useRef(null);
8117
8126
  const merged = useMergedRef(ref, own);
@@ -8128,7 +8137,7 @@ window.Aura = (() => {
8128
8137
  if (bars[bars.length - 1] !== sync) return;
8129
8138
  if (getComputedStyle(el).position === "fixed") {
8130
8139
  root.style.setProperty(NAME, el.offsetHeight + "px");
8131
- if (!checked && (checked = true, trapped(el)))
8140
+ if (!checked && (checked = true, fixedTrapped(el)))
8132
8141
  devWarnOnce(
8133
8142
  "pageheader-bar-trapped",
8134
8143
  "PageHeader actionsBarBelow: an ancestor has a transform, filter, will-change or paint containment, so the fixed bar is placed against it instead of the screen. Remove it from the page header's ancestors."
@@ -8671,6 +8680,17 @@ window.Aura = (() => {
8671
8680
 
8672
8681
  // src/ActionBar.tsx
8673
8682
  var React50 = __toESM(require_react(), 1);
8683
+ var HEIGHT = "--aura-actionbar-height";
8684
+ var edges = [];
8685
+ function writeHeight() {
8686
+ const root = document.documentElement;
8687
+ for (let i = edges.length - 1; i >= 0; i--)
8688
+ if (getComputedStyle(edges[i]).position === "fixed") {
8689
+ root.style.setProperty(HEIGHT, edges[i].offsetHeight + "px");
8690
+ return;
8691
+ }
8692
+ root.style.removeProperty(HEIGHT);
8693
+ }
8674
8694
  var ActionBar = React50.forwardRef(function ActionBar2(props, ref) {
8675
8695
  const t = useStrings();
8676
8696
  const bulk = props.selected != null;
@@ -8681,6 +8701,40 @@ window.Aura = (() => {
8681
8701
  });
8682
8702
  const cameFrom = React50.useRef(null);
8683
8703
  const own = React50.useRef(null);
8704
+ const viewport = (props.position || "viewport") === "viewport";
8705
+ const edge = !viewport ? null : props.look === "edge" ? "all" : props.edgeBelow || null;
8706
+ if (props.look === "edge" && props.edgeBelow && viewport)
8707
+ devWarnOnce(
8708
+ "actionbar-edge-both",
8709
+ 'ActionBar: look="edge" draws the edge strip at every width, so edgeBelow does nothing. Pass edgeBelow alone for a strip on a phone and the floating card above it.'
8710
+ );
8711
+ React50.useEffect(
8712
+ function() {
8713
+ const el = own.current;
8714
+ if (!edge || !el) return;
8715
+ let checked = false;
8716
+ function sync() {
8717
+ writeHeight();
8718
+ if (!checked && getComputedStyle(el).position === "fixed" && (checked = true, fixedTrapped(el)))
8719
+ devWarnOnce(
8720
+ "actionbar-edge-trapped",
8721
+ `ActionBar look="edge": an ancestor has a transform, filter, will-change or paint containment, so the fixed strip is placed against it instead of the screen. Remove it from the bar's ancestors.`
8722
+ );
8723
+ }
8724
+ edges.push(el);
8725
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(sync);
8726
+ if (ro) ro.observe(el, { box: "border-box" });
8727
+ window.addEventListener("resize", sync);
8728
+ sync();
8729
+ return function() {
8730
+ if (ro) ro.disconnect();
8731
+ window.removeEventListener("resize", sync);
8732
+ edges.splice(edges.indexOf(el), 1);
8733
+ writeHeight();
8734
+ };
8735
+ },
8736
+ [edge, idle]
8737
+ );
8684
8738
  function clear() {
8685
8739
  props.onClearSelection();
8686
8740
  setTimeout(function() {
@@ -8707,6 +8761,7 @@ window.Aura = (() => {
8707
8761
  className: cx(
8708
8762
  "aura-actionbar",
8709
8763
  "aura-actionbar--" + (props.position || "viewport"),
8764
+ edge && (edge === "all" ? "aura-actionbar--edge" : "aura-actionbar--edge-below-" + edge),
8710
8765
  idle && "is-idle",
8711
8766
  props.className
8712
8767
  )
@@ -391,6 +391,14 @@ function plainClick(e) {
391
391
  return !e.defaultPrevented && e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
392
392
  }
393
393
  var FilterSheet = React3.createContext(false);
394
+ function fixedTrapped(el) {
395
+ for (let p = el.parentElement; p && p !== document.documentElement; p = p.parentElement) {
396
+ const s = getComputedStyle(p);
397
+ if (s.transform !== "none" || s.filter !== "none" || (s.backdropFilter || "none") !== "none" || s.contentVisibility === "auto" || s.perspective !== "none" || /transform|filter|perspective/.test(s.willChange) || /paint|layout|strict|content/.test(s.contain))
398
+ return true;
399
+ }
400
+ return false;
401
+ }
394
402
 
395
403
  // src/display.tsx
396
404
  var React4 = __toESM(require("react"), 1);
@@ -2063,12 +2071,19 @@ var Field = React13.forwardRef(function Field2(props, ref) {
2063
2071
  ) : null,
2064
2072
  hasAddon(props.labelAddon) ? /* @__PURE__ */ React13.createElement("div", { className: "aura-field__addon", id: props.id + "-addon" }, props.labelAddon) : null,
2065
2073
  props.children,
2066
- props.error ? /* @__PURE__ */ React13.createElement("p", { className: "aura-field__error", id: props.id + "-error" }, /* @__PURE__ */ React13.createElement(Icon, { name: /* @__PURE__ */ React13.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React13.createElement("p", { className: "aura-field__hint", id: props.id + "-hint" }, props.hint) : null
2074
+ fieldNote(props.id, props.error, props.hint, props.errorLive)
2067
2075
  );
2068
2076
  });
2069
2077
  function describedBy(id, p) {
2070
2078
  return p.error ? id + "-error" : p.hint ? id + "-hint" : void 0;
2071
2079
  }
2080
+ function fieldNote(id, error, hint, live) {
2081
+ const err = error ? /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Icon, { name: /* @__PURE__ */ React13.createElement(IconCircleAlert, null), size: 14 }), error) : null;
2082
+ const hintLine = hint ? /* @__PURE__ */ React13.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, hint) : null;
2083
+ if (!live)
2084
+ return err ? /* @__PURE__ */ React13.createElement("p", { className: "aura-field__error", id: id + "-error" }, err) : hintLine;
2085
+ return /* @__PURE__ */ React13.createElement(React13.Fragment, null, err ? null : hintLine, /* @__PURE__ */ React13.createElement("p", { className: "aura-field__error", id: id == null ? void 0 : id + "-error", "aria-live": "polite" }, err));
2086
+ }
2072
2087
  function hasAddon(n3) {
2073
2088
  return n3 != null && typeof n3 !== "boolean" && n3 !== "";
2074
2089
  }
@@ -3854,6 +3869,7 @@ var RadioGroup = React20.forwardRef(function RadioGroup2(props, ref) {
3854
3869
  className: cx(
3855
3870
  "aura-field aura-radio-group",
3856
3871
  props.error && "is-invalid",
3872
+ props.disabled && "is-disabled",
3857
3873
  touchClass(props.touchHeight),
3858
3874
  props.className
3859
3875
  ),
@@ -3883,7 +3899,7 @@ var RadioGroup = React20.forwardRef(function RadioGroup2(props, ref) {
3883
3899
  }
3884
3900
  ), /* @__PURE__ */ React20.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React20.createElement("span", { className: "aura-choice__label", id: oid ? oid + "-label" : void 0 }, v.label), v.description ? /* @__PURE__ */ React20.createElement("span", { className: "aura-choice__desc", id: oid + "-desc" }, v.description) : null));
3885
3901
  })),
3886
- props.error ? /* @__PURE__ */ React20.createElement("p", { className: "aura-field__error", id: id + "-error" }, /* @__PURE__ */ React20.createElement(Icon, { name: /* @__PURE__ */ React20.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React20.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, props.hint) : null
3902
+ fieldNote(id, props.error, props.hint, props.errorLive)
3887
3903
  );
3888
3904
  });
3889
3905
 
@@ -3918,6 +3934,7 @@ var CheckboxGroup = React21.forwardRef(
3918
3934
  className: cx(
3919
3935
  "aura-field aura-radio-group aura-checkbox-group",
3920
3936
  props.error && "is-invalid",
3937
+ props.disabled && "is-disabled",
3921
3938
  touchClass(props.touchHeight),
3922
3939
  props.className
3923
3940
  ),
@@ -3955,7 +3972,7 @@ var CheckboxGroup = React21.forwardRef(
3955
3972
  v.label
3956
3973
  );
3957
3974
  })),
3958
- props.error ? /* @__PURE__ */ React21.createElement("p", { className: "aura-field__error", id: id + "-error" }, /* @__PURE__ */ React21.createElement(Icon, { name: /* @__PURE__ */ React21.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React21.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, props.hint) : null
3975
+ fieldNote(id, props.error, props.hint, props.errorLive)
3959
3976
  );
3960
3977
  }
3961
3978
  );
@@ -8086,14 +8103,6 @@ var PageHeader = React44.forwardRef(function PageHeader2(props, ref) {
8086
8103
  // src/PageHeaderClient.tsx
8087
8104
  var NAME = "--aura-page-actions-height";
8088
8105
  var bars = [];
8089
- function trapped(el) {
8090
- for (let p = el.parentElement; p && p !== document.documentElement; p = p.parentElement) {
8091
- const s = getComputedStyle(p);
8092
- if (s.transform !== "none" || s.filter !== "none" || (s.backdropFilter || "none") !== "none" || s.contentVisibility === "auto" || s.perspective !== "none" || /transform|filter|perspective/.test(s.willChange) || /paint|layout|strict|content/.test(s.contain))
8093
- return true;
8094
- }
8095
- return false;
8096
- }
8097
8106
  var PageHeader3 = React45.forwardRef(function PageHeader4(props, ref) {
8098
8107
  const own = React45.useRef(null);
8099
8108
  const merged = useMergedRef(ref, own);
@@ -8110,7 +8119,7 @@ var PageHeader3 = React45.forwardRef(function PageHeader4(props, ref) {
8110
8119
  if (bars[bars.length - 1] !== sync) return;
8111
8120
  if (getComputedStyle(el).position === "fixed") {
8112
8121
  root.style.setProperty(NAME, el.offsetHeight + "px");
8113
- if (!checked && (checked = true, trapped(el)))
8122
+ if (!checked && (checked = true, fixedTrapped(el)))
8114
8123
  devWarnOnce(
8115
8124
  "pageheader-bar-trapped",
8116
8125
  "PageHeader actionsBarBelow: an ancestor has a transform, filter, will-change or paint containment, so the fixed bar is placed against it instead of the screen. Remove it from the page header's ancestors."
@@ -8653,6 +8662,17 @@ var AppShell = React49.forwardRef(function AppShell2(props, ref) {
8653
8662
 
8654
8663
  // src/ActionBar.tsx
8655
8664
  var React50 = __toESM(require("react"), 1);
8665
+ var HEIGHT = "--aura-actionbar-height";
8666
+ var edges = [];
8667
+ function writeHeight() {
8668
+ const root = document.documentElement;
8669
+ for (let i = edges.length - 1; i >= 0; i--)
8670
+ if (getComputedStyle(edges[i]).position === "fixed") {
8671
+ root.style.setProperty(HEIGHT, edges[i].offsetHeight + "px");
8672
+ return;
8673
+ }
8674
+ root.style.removeProperty(HEIGHT);
8675
+ }
8656
8676
  var ActionBar = React50.forwardRef(function ActionBar2(props, ref) {
8657
8677
  const t = useStrings();
8658
8678
  const bulk = props.selected != null;
@@ -8663,6 +8683,40 @@ var ActionBar = React50.forwardRef(function ActionBar2(props, ref) {
8663
8683
  });
8664
8684
  const cameFrom = React50.useRef(null);
8665
8685
  const own = React50.useRef(null);
8686
+ const viewport = (props.position || "viewport") === "viewport";
8687
+ const edge = !viewport ? null : props.look === "edge" ? "all" : props.edgeBelow || null;
8688
+ if (props.look === "edge" && props.edgeBelow && viewport)
8689
+ devWarnOnce(
8690
+ "actionbar-edge-both",
8691
+ 'ActionBar: look="edge" draws the edge strip at every width, so edgeBelow does nothing. Pass edgeBelow alone for a strip on a phone and the floating card above it.'
8692
+ );
8693
+ React50.useEffect(
8694
+ function() {
8695
+ const el = own.current;
8696
+ if (!edge || !el) return;
8697
+ let checked = false;
8698
+ function sync() {
8699
+ writeHeight();
8700
+ if (!checked && getComputedStyle(el).position === "fixed" && (checked = true, fixedTrapped(el)))
8701
+ devWarnOnce(
8702
+ "actionbar-edge-trapped",
8703
+ `ActionBar look="edge": an ancestor has a transform, filter, will-change or paint containment, so the fixed strip is placed against it instead of the screen. Remove it from the bar's ancestors.`
8704
+ );
8705
+ }
8706
+ edges.push(el);
8707
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(sync);
8708
+ if (ro) ro.observe(el, { box: "border-box" });
8709
+ window.addEventListener("resize", sync);
8710
+ sync();
8711
+ return function() {
8712
+ if (ro) ro.disconnect();
8713
+ window.removeEventListener("resize", sync);
8714
+ edges.splice(edges.indexOf(el), 1);
8715
+ writeHeight();
8716
+ };
8717
+ },
8718
+ [edge, idle]
8719
+ );
8666
8720
  function clear() {
8667
8721
  props.onClearSelection();
8668
8722
  setTimeout(function() {
@@ -8689,6 +8743,7 @@ var ActionBar = React50.forwardRef(function ActionBar2(props, ref) {
8689
8743
  className: cx(
8690
8744
  "aura-actionbar",
8691
8745
  "aura-actionbar--" + (props.position || "viewport"),
8746
+ edge && (edge === "all" ? "aura-actionbar--edge" : "aura-actionbar--edge-below-" + edge),
8692
8747
  idle && "is-idle",
8693
8748
  props.className
8694
8749
  )
@@ -1,8 +1,19 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { Button } from "./Button.js";
4
- import { cx } from "./internal.js";
4
+ import { cx, devWarnOnce, fixedTrapped } from "./internal.js";
5
5
  import { useStrings } from "./locale.js";
6
+ const HEIGHT = "--aura-actionbar-height";
7
+ const edges = [];
8
+ function writeHeight() {
9
+ const root = document.documentElement;
10
+ for (let i = edges.length - 1; i >= 0; i--)
11
+ if (getComputedStyle(edges[i]).position === "fixed") {
12
+ root.style.setProperty(HEIGHT, edges[i].offsetHeight + "px");
13
+ return;
14
+ }
15
+ root.style.removeProperty(HEIGHT);
16
+ }
6
17
  const ActionBar = React.forwardRef(function ActionBar2(props, ref) {
7
18
  const t = useStrings();
8
19
  const bulk = props.selected != null;
@@ -13,6 +24,40 @@ const ActionBar = React.forwardRef(function ActionBar2(props, ref) {
13
24
  });
14
25
  const cameFrom = React.useRef(null);
15
26
  const own = React.useRef(null);
27
+ const viewport = (props.position || "viewport") === "viewport";
28
+ const edge = !viewport ? null : props.look === "edge" ? "all" : props.edgeBelow || null;
29
+ if (props.look === "edge" && props.edgeBelow && viewport)
30
+ devWarnOnce(
31
+ "actionbar-edge-both",
32
+ 'ActionBar: look="edge" draws the edge strip at every width, so edgeBelow does nothing. Pass edgeBelow alone for a strip on a phone and the floating card above it.'
33
+ );
34
+ React.useEffect(
35
+ function() {
36
+ const el = own.current;
37
+ if (!edge || !el) return;
38
+ let checked = false;
39
+ function sync() {
40
+ writeHeight();
41
+ if (!checked && getComputedStyle(el).position === "fixed" && (checked = true, fixedTrapped(el)))
42
+ devWarnOnce(
43
+ "actionbar-edge-trapped",
44
+ `ActionBar look="edge": an ancestor has a transform, filter, will-change or paint containment, so the fixed strip is placed against it instead of the screen. Remove it from the bar's ancestors.`
45
+ );
46
+ }
47
+ edges.push(el);
48
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(sync);
49
+ if (ro) ro.observe(el, { box: "border-box" });
50
+ window.addEventListener("resize", sync);
51
+ sync();
52
+ return function() {
53
+ if (ro) ro.disconnect();
54
+ window.removeEventListener("resize", sync);
55
+ edges.splice(edges.indexOf(el), 1);
56
+ writeHeight();
57
+ };
58
+ },
59
+ [edge, idle]
60
+ );
16
61
  function clear() {
17
62
  props.onClearSelection();
18
63
  setTimeout(function() {
@@ -39,6 +84,7 @@ const ActionBar = React.forwardRef(function ActionBar2(props, ref) {
39
84
  className: cx(
40
85
  "aura-actionbar",
41
86
  "aura-actionbar--" + (props.position || "viewport"),
87
+ edge && (edge === "all" ? "aura-actionbar--edge" : "aura-actionbar--edge-below-" + edge),
42
88
  idle && "is-idle",
43
89
  props.className
44
90
  )
@@ -1,11 +1,9 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { Checkbox } from "./Checkbox.js";
4
- import { describedBy } from "./Field.js";
5
- import { Icon } from "./Icon.js";
4
+ import { describedBy, fieldNote } from "./Field.js";
6
5
  import { cx, touchClass, uid, useMaybeControlled } from "./internal.js";
7
6
  import { useStrings } from "./locale.js";
8
- import { IconCircleAlert } from "./icons.js";
9
7
  const CheckboxGroup = React.forwardRef(
10
8
  function CheckboxGroup2(props, ref) {
11
9
  const t = useStrings();
@@ -35,6 +33,7 @@ const CheckboxGroup = React.forwardRef(
35
33
  className: cx(
36
34
  "aura-field aura-radio-group aura-checkbox-group",
37
35
  props.error && "is-invalid",
36
+ props.disabled && "is-disabled",
38
37
  touchClass(props.touchHeight),
39
38
  props.className
40
39
  ),
@@ -72,7 +71,7 @@ const CheckboxGroup = React.forwardRef(
72
71
  v.label
73
72
  );
74
73
  })),
75
- props.error ? /* @__PURE__ */ React.createElement("p", { className: "aura-field__error", id: id + "-error" }, /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, props.hint) : null
74
+ fieldNote(id, props.error, props.hint, props.errorLive)
76
75
  );
77
76
  }
78
77
  );
package/dist/esm/Field.js CHANGED
@@ -27,12 +27,19 @@ const Field = React.forwardRef(function Field2(props, ref) {
27
27
  ) : null,
28
28
  hasAddon(props.labelAddon) ? /* @__PURE__ */ React.createElement("div", { className: "aura-field__addon", id: props.id + "-addon" }, props.labelAddon) : null,
29
29
  props.children,
30
- props.error ? /* @__PURE__ */ React.createElement("p", { className: "aura-field__error", id: props.id + "-error" }, /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React.createElement("p", { className: "aura-field__hint", id: props.id + "-hint" }, props.hint) : null
30
+ fieldNote(props.id, props.error, props.hint, props.errorLive)
31
31
  );
32
32
  });
33
33
  function describedBy(id, p) {
34
34
  return p.error ? id + "-error" : p.hint ? id + "-hint" : void 0;
35
35
  }
36
+ function fieldNote(id, error, hint, live) {
37
+ const err = error ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconCircleAlert, null), size: 14 }), error) : null;
38
+ const hintLine = hint ? /* @__PURE__ */ React.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, hint) : null;
39
+ if (!live)
40
+ return err ? /* @__PURE__ */ React.createElement("p", { className: "aura-field__error", id: id + "-error" }, err) : hintLine;
41
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, err ? null : hintLine, /* @__PURE__ */ React.createElement("p", { className: "aura-field__error", id: id == null ? void 0 : id + "-error", "aria-live": "polite" }, err));
42
+ }
36
43
  function hasAddon(n) {
37
44
  return n != null && typeof n !== "boolean" && n !== "";
38
45
  }
@@ -68,6 +75,7 @@ export {
68
75
  FIELD_KEYS,
69
76
  Field,
70
77
  describedBy,
78
+ fieldNote,
71
79
  hasAddon,
72
80
  joinDescribedBy
73
81
  };
@@ -1,17 +1,9 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { PageHeader as StaticPageHeader } from "./PageHeader.js";
4
- import { devWarnOnce, useMergedRef } from "./internal.js";
4
+ import { devWarnOnce, fixedTrapped as trapped, useMergedRef } from "./internal.js";
5
5
  const NAME = "--aura-page-actions-height";
6
6
  const bars = [];
7
- function trapped(el) {
8
- for (let p = el.parentElement; p && p !== document.documentElement; p = p.parentElement) {
9
- const s = getComputedStyle(p);
10
- if (s.transform !== "none" || s.filter !== "none" || (s.backdropFilter || "none") !== "none" || s.contentVisibility === "auto" || s.perspective !== "none" || /transform|filter|perspective/.test(s.willChange) || /paint|layout|strict|content/.test(s.contain))
11
- return true;
12
- }
13
- return false;
14
- }
15
7
  const PageHeader = React.forwardRef(function PageHeader2(props, ref) {
16
8
  const own = React.useRef(null);
17
9
  const merged = useMergedRef(ref, own);
@@ -1,9 +1,7 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
- import { describedBy } from "./Field.js";
4
- import { Icon } from "./Icon.js";
3
+ import { describedBy, fieldNote } from "./Field.js";
5
4
  import { cx, touchClass, uid, useMaybeControlled } from "./internal.js";
6
- import { IconCircleAlert } from "./icons.js";
7
5
  const RadioGroup = React.forwardRef(function RadioGroup2(props, ref) {
8
6
  const auto = uid(), id = props.id || auto;
9
7
  const st = useMaybeControlled(
@@ -31,6 +29,7 @@ const RadioGroup = React.forwardRef(function RadioGroup2(props, ref) {
31
29
  className: cx(
32
30
  "aura-field aura-radio-group",
33
31
  props.error && "is-invalid",
32
+ props.disabled && "is-disabled",
34
33
  touchClass(props.touchHeight),
35
34
  props.className
36
35
  ),
@@ -60,7 +59,7 @@ const RadioGroup = React.forwardRef(function RadioGroup2(props, ref) {
60
59
  }
61
60
  ), /* @__PURE__ */ React.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React.createElement("span", { className: "aura-choice__label", id: oid ? oid + "-label" : void 0 }, v.label), v.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-choice__desc", id: oid + "-desc" }, v.description) : null));
62
61
  })),
63
- props.error ? /* @__PURE__ */ React.createElement("p", { className: "aura-field__error", id: id + "-error" }, /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconCircleAlert, null), size: 14 }), props.error) : props.hint ? /* @__PURE__ */ React.createElement("p", { className: "aura-field__hint", id: id + "-hint" }, props.hint) : null
62
+ fieldNote(id, props.error, props.hint, props.errorLive)
64
63
  );
65
64
  });
66
65
  export {
@@ -109,6 +109,14 @@ function plainClick(e) {
109
109
  return !e.defaultPrevented && e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
110
110
  }
111
111
  const FilterSheet = React.createContext(false);
112
+ function fixedTrapped(el) {
113
+ for (let p = el.parentElement; p && p !== document.documentElement; p = p.parentElement) {
114
+ const s = getComputedStyle(p);
115
+ if (s.transform !== "none" || s.filter !== "none" || (s.backdropFilter || "none") !== "none" || s.contentVisibility === "auto" || s.perspective !== "none" || /transform|filter|perspective/.test(s.willChange) || /paint|layout|strict|content/.test(s.contain))
116
+ return true;
117
+ }
118
+ return false;
119
+ }
112
120
  export {
113
121
  FOCUSABLE,
114
122
  FilterSheet,
@@ -117,6 +125,7 @@ export {
117
125
  compare,
118
126
  cx,
119
127
  devWarnOnce,
128
+ fixedTrapped,
120
129
  focusFirst,
121
130
  inClosedDetails,
122
131
  isTabStop,
package/dist/index.d.cts CHANGED
@@ -680,7 +680,12 @@ export interface RadioGroupProps extends FieldProps {
680
680
  name?: string | undefined;
681
681
  /** Default `vertical`. */
682
682
  orientation?: "vertical" | "horizontal" | undefined;
683
+ /** Disables every option and (5.68) dims the legend, as Field does its label. */
683
684
  disabled?: boolean | undefined;
685
+ /** 5.68 (Chamber-OS 184): announce the error line when it appears (`aria-live="polite"`) — for an error shown as
686
+ * the user changes the group ("Choose at least one…" when the last box is unticked), not on submit. The error line
687
+ * stays in the page, empty, while there is none, so the first message is read too. Off by default. */
688
+ errorLive?: boolean | undefined;
684
689
  id?: string | undefined;
685
690
  className?: string | undefined;
686
691
  /** 44px rows (5.30, Chamber-OS 135), the one-line label centred: `true` below 640px or on a coarse pointer (touch
@@ -700,7 +705,12 @@ export interface CheckboxGroupProps extends FieldProps {
700
705
  name?: string | undefined;
701
706
  /** Default `vertical`. */
702
707
  orientation?: "vertical" | "horizontal" | undefined;
708
+ /** Disables every option and (5.68) dims the legend, as Field does its label. */
703
709
  disabled?: boolean | undefined;
710
+ /** 5.68 (Chamber-OS 184): announce the error line when it appears (`aria-live="polite"`) — for an error shown as
711
+ * the user changes the group ("Choose at least one…" when the last box is unticked), not on submit. The error line
712
+ * stays in the page, empty, while there is none, so the first message is read too. Off by default. */
713
+ errorLive?: boolean | undefined;
704
714
  id?: string | undefined;
705
715
  className?: string | undefined;
706
716
  /** 44px rows, as RadioGroup: `true` below 640px or on a coarse pointer, `'always'` at every width. */
@@ -1504,6 +1514,16 @@ export interface ActionBarProps {
1504
1514
  * parent, so they never cover the last field. Make it the last child of the form or card it belongs to: like any
1505
1515
  * `position: sticky` element it sticks only while that parent is on screen. */
1506
1516
  position?: "viewport" | "container" | undefined;
1517
+ /** 5.69 (Chamber-OS 185), `position="viewport"` only. `floating` (default): the inset card, bordered all round,
1518
+ * rounded, with a shadow. `edge`: PageHeader's edge strip at every width — the screen's full width, on the bottom
1519
+ * edge or on a BottomNav, a top border only, the home-indicator inset inside it. The strip is `position: fixed` and
1520
+ * stays on screen for the whole page (not only while its form is), so, as PageHeader's bar, the page gains its height
1521
+ * at the end (`--aura-actionbar-height` on `<html>`) and a focused field scrolls clear; on a screen 30rem tall or
1522
+ * less it is static, in the flow. For a strip on a phone only, use `edgeBelow`. */
1523
+ look?: "floating" | "edge" | undefined;
1524
+ /** 5.69 (Chamber-OS 185), `position="viewport"` only: the edge strip below this width (`sm` 640px, `md` 768px),
1525
+ * the `look` card at and above it — as PageHeader's `actionsBarBelow`. */
1526
+ edgeBelow?: "sm" | "md" | undefined;
1507
1527
  /** 5.35 (DxT Monitor #14): while this bar is on the page, a BottomNav (and its spacer) is hidden and the bar takes the
1508
1528
  * bottom edge — for a page whose own actions (Acknowledge, Visit site) replace the tab bar. Any element can do the
1509
1529
  * same with the `data-aura-hide-bottomnav` attribute. */
@@ -1927,6 +1947,12 @@ export interface FieldComponentProps extends Omit<FieldPropsPublic, "label"> {
1927
1947
  /** The control's id: the label points at it, and the hint/error ids derive from it. */
1928
1948
  id?: string | undefined;
1929
1949
  children?: React$1.ReactNode | undefined;
1950
+ /** 5.68 (Chamber-OS 184): announce the error line when it appears — for an error shown as the user types or ticks,
1951
+ * not on submit. The error `<p>` gets `aria-live="polite"` and stays in the page, empty, while there is no error, so
1952
+ * the first message is read too. Off by default: an error shown on submit, with focus moved to the field, is read
1953
+ * already, and a live line would read it twice. Your control should still point at `{id}-error` in its
1954
+ * `aria-describedby` while there is an error (TextField and the others do), so the message stays its description. */
1955
+ errorLive?: boolean | undefined;
1930
1956
  /** 5.31 (Chamber-OS addendum 41): a node between the label and the control, in a `.aura-field__addon` box with the
1931
1957
  * id `{id}-addon`. TextField and Textarea add that id to the input's `aria-describedby`; a custom control should too. */
1932
1958
  labelAddon?: React$1.ReactNode | undefined;
@@ -2280,8 +2306,6 @@ export declare const STRINGS: {
2280
2306
  export declare const TileGrid: React$1.ForwardRefExoticComponent<TileGridProps & React$1.RefAttributes<HTMLUListElement>>;
2281
2307
  export declare const Sparkline: React$1.ForwardRefExoticComponent<SparklineProps & React$1.RefAttributes<SVGSVGElement>>;
2282
2308
  export declare const AppShell: React$1.ForwardRefExoticComponent<AppShellProps & React$1.RefAttributes<HTMLDivElement>>;
2283
- /** ActionBar — a bar stuck to the bottom of the screen or of a card: a status line and the form's or selection's
2284
- * actions. It is `position: sticky`, so it stays in the flow at the end of its parent and never covers the last field. */
2285
2309
  export declare const ActionBar: React$1.ForwardRefExoticComponent<ActionBarProps & React$1.RefAttributes<HTMLDivElement>>;
2286
2310
  /** Separator — a 1px `aura-border-default` rule between groups of content (4.20). Decorative by default (hidden from
2287
2311
  * screen readers); `decorative={false}` makes it a `role="separator"` that is announced. */
package/dist/index.d.ts CHANGED
@@ -680,7 +680,12 @@ export interface RadioGroupProps extends FieldProps {
680
680
  name?: string | undefined;
681
681
  /** Default `vertical`. */
682
682
  orientation?: "vertical" | "horizontal" | undefined;
683
+ /** Disables every option and (5.68) dims the legend, as Field does its label. */
683
684
  disabled?: boolean | undefined;
685
+ /** 5.68 (Chamber-OS 184): announce the error line when it appears (`aria-live="polite"`) — for an error shown as
686
+ * the user changes the group ("Choose at least one…" when the last box is unticked), not on submit. The error line
687
+ * stays in the page, empty, while there is none, so the first message is read too. Off by default. */
688
+ errorLive?: boolean | undefined;
684
689
  id?: string | undefined;
685
690
  className?: string | undefined;
686
691
  /** 44px rows (5.30, Chamber-OS 135), the one-line label centred: `true` below 640px or on a coarse pointer (touch
@@ -700,7 +705,12 @@ export interface CheckboxGroupProps extends FieldProps {
700
705
  name?: string | undefined;
701
706
  /** Default `vertical`. */
702
707
  orientation?: "vertical" | "horizontal" | undefined;
708
+ /** Disables every option and (5.68) dims the legend, as Field does its label. */
703
709
  disabled?: boolean | undefined;
710
+ /** 5.68 (Chamber-OS 184): announce the error line when it appears (`aria-live="polite"`) — for an error shown as
711
+ * the user changes the group ("Choose at least one…" when the last box is unticked), not on submit. The error line
712
+ * stays in the page, empty, while there is none, so the first message is read too. Off by default. */
713
+ errorLive?: boolean | undefined;
704
714
  id?: string | undefined;
705
715
  className?: string | undefined;
706
716
  /** 44px rows, as RadioGroup: `true` below 640px or on a coarse pointer, `'always'` at every width. */
@@ -1504,6 +1514,16 @@ export interface ActionBarProps {
1504
1514
  * parent, so they never cover the last field. Make it the last child of the form or card it belongs to: like any
1505
1515
  * `position: sticky` element it sticks only while that parent is on screen. */
1506
1516
  position?: "viewport" | "container" | undefined;
1517
+ /** 5.69 (Chamber-OS 185), `position="viewport"` only. `floating` (default): the inset card, bordered all round,
1518
+ * rounded, with a shadow. `edge`: PageHeader's edge strip at every width — the screen's full width, on the bottom
1519
+ * edge or on a BottomNav, a top border only, the home-indicator inset inside it. The strip is `position: fixed` and
1520
+ * stays on screen for the whole page (not only while its form is), so, as PageHeader's bar, the page gains its height
1521
+ * at the end (`--aura-actionbar-height` on `<html>`) and a focused field scrolls clear; on a screen 30rem tall or
1522
+ * less it is static, in the flow. For a strip on a phone only, use `edgeBelow`. */
1523
+ look?: "floating" | "edge" | undefined;
1524
+ /** 5.69 (Chamber-OS 185), `position="viewport"` only: the edge strip below this width (`sm` 640px, `md` 768px),
1525
+ * the `look` card at and above it — as PageHeader's `actionsBarBelow`. */
1526
+ edgeBelow?: "sm" | "md" | undefined;
1507
1527
  /** 5.35 (DxT Monitor #14): while this bar is on the page, a BottomNav (and its spacer) is hidden and the bar takes the
1508
1528
  * bottom edge — for a page whose own actions (Acknowledge, Visit site) replace the tab bar. Any element can do the
1509
1529
  * same with the `data-aura-hide-bottomnav` attribute. */
@@ -1927,6 +1947,12 @@ export interface FieldComponentProps extends Omit<FieldPropsPublic, "label"> {
1927
1947
  /** The control's id: the label points at it, and the hint/error ids derive from it. */
1928
1948
  id?: string | undefined;
1929
1949
  children?: React$1.ReactNode | undefined;
1950
+ /** 5.68 (Chamber-OS 184): announce the error line when it appears — for an error shown as the user types or ticks,
1951
+ * not on submit. The error `<p>` gets `aria-live="polite"` and stays in the page, empty, while there is no error, so
1952
+ * the first message is read too. Off by default: an error shown on submit, with focus moved to the field, is read
1953
+ * already, and a live line would read it twice. Your control should still point at `{id}-error` in its
1954
+ * `aria-describedby` while there is an error (TextField and the others do), so the message stays its description. */
1955
+ errorLive?: boolean | undefined;
1930
1956
  /** 5.31 (Chamber-OS addendum 41): a node between the label and the control, in a `.aura-field__addon` box with the
1931
1957
  * id `{id}-addon`. TextField and Textarea add that id to the input's `aria-describedby`; a custom control should too. */
1932
1958
  labelAddon?: React$1.ReactNode | undefined;
@@ -2280,8 +2306,6 @@ export declare const STRINGS: {
2280
2306
  export declare const TileGrid: React$1.ForwardRefExoticComponent<TileGridProps & React$1.RefAttributes<HTMLUListElement>>;
2281
2307
  export declare const Sparkline: React$1.ForwardRefExoticComponent<SparklineProps & React$1.RefAttributes<SVGSVGElement>>;
2282
2308
  export declare const AppShell: React$1.ForwardRefExoticComponent<AppShellProps & React$1.RefAttributes<HTMLDivElement>>;
2283
- /** ActionBar — a bar stuck to the bottom of the screen or of a card: a status line and the form's or selection's
2284
- * actions. It is `position: sticky`, so it stays in the flow at the end of its parent and never covers the last field. */
2285
2309
  export declare const ActionBar: React$1.ForwardRefExoticComponent<ActionBarProps & React$1.RefAttributes<HTMLDivElement>>;
2286
2310
  /** Separator — a 1px `aura-border-default` rule between groups of content (4.20). Decorative by default (hidden from
2287
2311
  * screen readers); `decorative={false}` makes it a `role="separator"` that is announced. */
package/dist/styles.css CHANGED
@@ -525,6 +525,9 @@ a.aura-icon-btn { text-decoration: none; }
525
525
  .aura-field__hint, .aura-field__error { display: flex; align-items: flex-start; gap: var(--aura-space-1); margin: 0; font-size: max(12px, .75rem); line-height: 1.5; color: var(--aura-fg-secondary); }
526
526
  .aura-field__error { color: var(--aura-fg-danger); }
527
527
  .aura-field__error .aura-icon { margin-top: 2px; }
528
+ /* 5.68 (Chamber-OS 184): an `errorLive` error line waits in the page, empty, until there is an error. Out of the
529
+ * flow (an absolute child takes no gap) and clipped; the screen reader still has the live region. */
530
+ .aura-field__error:empty { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
528
531
  /* 5.31 (Chamber-OS addendum 41): TextField/Textarea `labelAddon`, between the label and the box, in the field's gap. */
529
532
  .aura-field__addon { min-width: 0; font-size: max(13px, .8125rem); line-height: 1.5; color: var(--aura-fg-primary); }
530
533
  .aura-field.is-disabled .aura-field__label { color: var(--aura-fg-secondary); }
@@ -2267,6 +2270,32 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2267
2270
  .aura-actionbar__start:empty { display: none; }
2268
2271
  @container aura-actionbar (min-width: 640px) { .aura-actionbar__start { order: -1; } }
2269
2272
  @container aura-actionbar (max-width: 639.98px) { .aura-actionbar__inner.has-start > .aura-actionbar__status:not(:empty) { flex-basis: 100%; } }
2273
+ /* 5.69 (Chamber-OS 185): look="edge" / edgeBelow — PageHeader's edge strip (5.48): the screen's full width, on the
2274
+ * bottom edge or on a BottomNav, a top border only, the home-indicator inset inside it. Fixed, so it spans the screen
2275
+ * and not its parent; as PageHeader's bar, the page gains its height at the end (the shell's content, else the body)
2276
+ * and as scroll-padding — --aura-actionbar-height, measured on the client, 60px until then — so the page's end and a
2277
+ * focused field scroll clear of it. On a screen 30rem tall or less it is static, in the flow, as every bar (5.48/5.49). */
2278
+ .aura-actionbar--edge:not(.is-idle) { border: 0; border-top: 1px solid var(--aura-border-default); border-radius: 0; box-shadow: none; }
2279
+ @media (min-height: 30.00125rem) {
2280
+ .aura-actionbar--edge:not(.is-idle) { position: fixed; left: 0; right: 0; width: auto; bottom: var(--aura-bottomnav-offset, 0px); margin: 0; padding-bottom: max(0px, env(safe-area-inset-bottom, 0px) - var(--aura-bottomnav-offset, 0px)); }
2281
+ :root:has(.aura-actionbar--edge:not(.is-idle)) { --aura-actionbar-space: calc(var(--aura-actionbar-height, 60px) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-actionbar-space); }
2282
+ :root:has(.aura-actionbar--edge:not(.is-idle)) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-actionbar-space)); }
2283
+ :root:has(.aura-actionbar--edge:not(.is-idle)):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-actionbar-space); }
2284
+ }
2285
+ @media (max-width: 639.98px) { .aura-actionbar--edge-below-sm:not(.is-idle) { border: 0; border-top: 1px solid var(--aura-border-default); border-radius: 0; box-shadow: none; } }
2286
+ @media (max-width: 639.98px) and (min-height: 30.00125rem) {
2287
+ .aura-actionbar--edge-below-sm:not(.is-idle) { position: fixed; left: 0; right: 0; width: auto; bottom: var(--aura-bottomnav-offset, 0px); margin: 0; padding-bottom: max(0px, env(safe-area-inset-bottom, 0px) - var(--aura-bottomnav-offset, 0px)); }
2288
+ :root:has(.aura-actionbar--edge-below-sm:not(.is-idle)) { --aura-actionbar-space: calc(var(--aura-actionbar-height, 60px) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-actionbar-space); }
2289
+ :root:has(.aura-actionbar--edge-below-sm:not(.is-idle)) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-actionbar-space)); }
2290
+ :root:has(.aura-actionbar--edge-below-sm:not(.is-idle)):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-actionbar-space); }
2291
+ }
2292
+ @media (max-width: 767.98px) { .aura-actionbar--edge-below-md:not(.is-idle) { border: 0; border-top: 1px solid var(--aura-border-default); border-radius: 0; box-shadow: none; } }
2293
+ @media (max-width: 767.98px) and (min-height: 30.00125rem) {
2294
+ .aura-actionbar--edge-below-md:not(.is-idle) { position: fixed; left: 0; right: 0; width: auto; bottom: var(--aura-bottomnav-offset, 0px); margin: 0; padding-bottom: max(0px, env(safe-area-inset-bottom, 0px) - var(--aura-bottomnav-offset, 0px)); }
2295
+ :root:has(.aura-actionbar--edge-below-md:not(.is-idle)) { --aura-actionbar-space: calc(var(--aura-actionbar-height, 60px) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-actionbar-space); }
2296
+ :root:has(.aura-actionbar--edge-below-md:not(.is-idle)) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-actionbar-space)); }
2297
+ :root:has(.aura-actionbar--edge-below-md:not(.is-idle)):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-actionbar-space); }
2298
+ }
2270
2299
 
2271
2300
  /* ---------- 4.20: Separator ---------- */
2272
2301
  .aura-separator { flex: none; align-self: stretch; height: 1px; margin: var(--aura-separator-space, 0) 0; background: var(--aura-border-default); border: 0; }
@@ -527,6 +527,9 @@ a.aura-icon-btn { text-decoration: none; }
527
527
  .aura-field__hint, .aura-field__error { display: flex; align-items: flex-start; gap: var(--aura-space-1); margin: 0; font-size: max(12px, .75rem); line-height: 1.5; color: var(--aura-fg-secondary); }
528
528
  .aura-field__error { color: var(--aura-fg-danger); }
529
529
  .aura-field__error .aura-icon { margin-top: 2px; }
530
+ /* 5.68 (Chamber-OS 184): an `errorLive` error line waits in the page, empty, until there is an error. Out of the
531
+ * flow (an absolute child takes no gap) and clipped; the screen reader still has the live region. */
532
+ .aura-field__error:empty { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
530
533
  /* 5.31 (Chamber-OS addendum 41): TextField/Textarea `labelAddon`, between the label and the box, in the field's gap. */
531
534
  .aura-field__addon { min-width: 0; font-size: max(13px, .8125rem); line-height: 1.5; color: var(--aura-fg-primary); }
532
535
  .aura-field.is-disabled .aura-field__label { color: var(--aura-fg-secondary); }
@@ -2269,6 +2272,32 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2269
2272
  .aura-actionbar__start:empty { display: none; }
2270
2273
  @container aura-actionbar (min-width: 640px) { .aura-actionbar__start { order: -1; } }
2271
2274
  @container aura-actionbar (max-width: 639.98px) { .aura-actionbar__inner.has-start > .aura-actionbar__status:not(:empty) { flex-basis: 100%; } }
2275
+ /* 5.69 (Chamber-OS 185): look="edge" / edgeBelow — PageHeader's edge strip (5.48): the screen's full width, on the
2276
+ * bottom edge or on a BottomNav, a top border only, the home-indicator inset inside it. Fixed, so it spans the screen
2277
+ * and not its parent; as PageHeader's bar, the page gains its height at the end (the shell's content, else the body)
2278
+ * and as scroll-padding — --aura-actionbar-height, measured on the client, 60px until then — so the page's end and a
2279
+ * focused field scroll clear of it. On a screen 30rem tall or less it is static, in the flow, as every bar (5.48/5.49). */
2280
+ .aura-actionbar--edge:not(.is-idle) { border: 0; border-top: 1px solid var(--aura-border-default); border-radius: 0; box-shadow: none; }
2281
+ @media (min-height: 30.00125rem) {
2282
+ .aura-actionbar--edge:not(.is-idle) { position: fixed; left: 0; right: 0; width: auto; bottom: var(--aura-bottomnav-offset, 0px); margin: 0; padding-bottom: max(0px, env(safe-area-inset-bottom, 0px) - var(--aura-bottomnav-offset, 0px)); }
2283
+ :root:has(.aura-actionbar--edge:not(.is-idle)) { --aura-actionbar-space: calc(var(--aura-actionbar-height, 60px) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-actionbar-space); }
2284
+ :root:has(.aura-actionbar--edge:not(.is-idle)) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-actionbar-space)); }
2285
+ :root:has(.aura-actionbar--edge:not(.is-idle)):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-actionbar-space); }
2286
+ }
2287
+ @media (max-width: 639.98px) { .aura-actionbar--edge-below-sm:not(.is-idle) { border: 0; border-top: 1px solid var(--aura-border-default); border-radius: 0; box-shadow: none; } }
2288
+ @media (max-width: 639.98px) and (min-height: 30.00125rem) {
2289
+ .aura-actionbar--edge-below-sm:not(.is-idle) { position: fixed; left: 0; right: 0; width: auto; bottom: var(--aura-bottomnav-offset, 0px); margin: 0; padding-bottom: max(0px, env(safe-area-inset-bottom, 0px) - var(--aura-bottomnav-offset, 0px)); }
2290
+ :root:has(.aura-actionbar--edge-below-sm:not(.is-idle)) { --aura-actionbar-space: calc(var(--aura-actionbar-height, 60px) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-actionbar-space); }
2291
+ :root:has(.aura-actionbar--edge-below-sm:not(.is-idle)) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-actionbar-space)); }
2292
+ :root:has(.aura-actionbar--edge-below-sm:not(.is-idle)):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-actionbar-space); }
2293
+ }
2294
+ @media (max-width: 767.98px) { .aura-actionbar--edge-below-md:not(.is-idle) { border: 0; border-top: 1px solid var(--aura-border-default); border-radius: 0; box-shadow: none; } }
2295
+ @media (max-width: 767.98px) and (min-height: 30.00125rem) {
2296
+ .aura-actionbar--edge-below-md:not(.is-idle) { position: fixed; left: 0; right: 0; width: auto; bottom: var(--aura-bottomnav-offset, 0px); margin: 0; padding-bottom: max(0px, env(safe-area-inset-bottom, 0px) - var(--aura-bottomnav-offset, 0px)); }
2297
+ :root:has(.aura-actionbar--edge-below-md:not(.is-idle)) { --aura-actionbar-space: calc(var(--aura-actionbar-height, 60px) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-actionbar-space); }
2298
+ :root:has(.aura-actionbar--edge-below-md:not(.is-idle)) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-actionbar-space)); }
2299
+ :root:has(.aura-actionbar--edge-below-md:not(.is-idle)):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-actionbar-space); }
2300
+ }
2272
2301
 
2273
2302
  /* ---------- 4.20: Separator ---------- */
2274
2303
  .aura-separator { flex: none; align-self: stretch; height: 1px; margin: var(--aura-separator-space, 0) 0; background: var(--aura-border-default); border: 0; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "5.67.0",
3
+ "version": "5.69.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",