@jirawatpyk/aura-react 5.18.0 → 5.20.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.20 — Chamber-OS addendum 20, items 115–116
190
+
191
+ - **Table follows the page's density** (item 115): without a `density` prop a Table takes the nearest AuraProvider's density, as its docs said — inside `AuraProvider density="compact"` the wrap carries `data-density="compact"` and cells get 8px vertical padding (were 12px). A `density` prop still wins. The padding comes from the nearest `data-density` (a provider, `<html>`, any element, or the table's own), so `comfortable` inside a compact page switches back as it does for fields. As before with an explicit `compact`, cells stay compact on touch screens. A visible change for Tables inside a compact provider or page.
192
+ - **ActionBar `start`** (item 116): buttons at the bar's start edge — Cancel in a wizard — outside the `status` live region. When the bar is 640px or wider (its own width — a bar in a narrow card or dialog counts as narrow) they sit flush with the start edge, the status (if any) next, `children` at the end; narrower, the status takes its own row and the start buttons sit before the actions. In the tab order they come before `children`. Hidden with the actions when a bulk bar is idle; an empty `start` renders nothing.
193
+ - **ActionBar in right-to-left pages**: the actions now sit at the end (left) edge — they used a physical `margin-left`, so a wrapped actions row landed next to the status. The bar is now `width: 100%` and a size container: give it no horizontal margins, and in a shrink-to-fit parent (inline-block, a grid `auto` track, a `max-content` popover) give the parent a width — otherwise the bar falls back to 20rem.
194
+
195
+ ## 5.19 — Chamber-OS addendum 20
196
+
197
+ - **Switch `readOnly`, `icon` and `aria-describedby`** (item 113): `readOnly` keeps a locked switch in the Tab order with `aria-readonly="true"`, so its name, state and notes are heard; click, Space, its label and its row change nothing. It keeps its colours (a locked "on" reads as on) and loses the pointer. `icon` puts an icon at the end of the row, the field icon's size, in fg-secondary (`icon={<IconLock />}`), hidden from screen readers. `aria-describedby` is merged with the description's id, as on TextField and Select. `disabled` wins over `readOnly`.
198
+ - **Select `readOnly`** (item 114): the field stays in the Tab order with `aria-readonly="true"` and shows the chosen option on the read-only ground of a read-only TextField, with no chevron; no click or key opens the list. Unlike `disabled`, the value is still posted with the form, and your code can still set it (`reset()`, `setValue()`). Before JavaScript runs, the options from `options` other than the value are disabled so the native control keeps it (`<option>` children aren't; with no `value` or `defaultValue` nothing is); once hydrated they are enabled again, since a disabled selected option is left out of the form's data. A `multiple` / `size` list box ignores the mouse and the keys that pick (arrows, Home/End, Page keys, letters, Space, Ctrl+A); a change that gets through anyway (a touch tap, a phone's picker, `selectOption` in a test) is put back, and your `onChange` isn't called — though a native `change` or `input` listener on the form still sees the event, with the value already restored.
199
+ - Read-only is `aria-readonly` alone; pair it with a note that says why (`aria-describedby`), which screen readers read after the state.
200
+
189
201
  ## 5.18 — Chamber-OS addendum 19
190
202
 
191
203
  - **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.
@@ -1869,10 +1869,22 @@ window.Aura = (() => {
1869
1869
  var Select = React15.forwardRef(function Select2(props, ref) {
1870
1870
  const auto = uid(), id = props.id || auto;
1871
1871
  const filter = props[FILTER_KEY];
1872
- const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY]));
1872
+ const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY, "readOnly"]));
1873
+ const ro = !!props.readOnly && !props.disabled;
1874
+ const mounted = useMounted();
1875
+ const kept = React15.useRef(null);
1876
+ const current2 = [].concat(props.value != null ? props.value : props.defaultValue != null ? props.defaultValue : []).map(String);
1873
1877
  const opts = (props.options || []).map(function(o) {
1874
1878
  const v = typeof o === "object" ? o : { value: o, label: o };
1875
- return /* @__PURE__ */ React15.createElement("option", { key: v.value, value: v.value, disabled: v.disabled }, v.label);
1879
+ return /* @__PURE__ */ React15.createElement(
1880
+ "option",
1881
+ {
1882
+ key: v.value,
1883
+ value: v.value,
1884
+ disabled: v.disabled || ro && !mounted && current2.length > 0 && current2.indexOf(v.value) < 0 || void 0
1885
+ },
1886
+ v.label
1887
+ );
1876
1888
  });
1877
1889
  if (props.placeholder)
1878
1890
  opts.unshift(
@@ -1880,7 +1892,6 @@ window.Aura = (() => {
1880
1892
  );
1881
1893
  const extra = props.value === void 0 && props.defaultValue === void 0 && props.placeholder ? { defaultValue: "" } : {};
1882
1894
  const native = !!props.multiple || props.size != null && props.size > 1;
1883
- const mounted = useMounted();
1884
1895
  const live = !native && mounted;
1885
1896
  const density = useDensity();
1886
1897
  React15.useEffect(function() {
@@ -2002,7 +2013,7 @@ window.Aura = (() => {
2002
2013
  }
2003
2014
  function choose(it) {
2004
2015
  const el = selRef.current;
2005
- if (!el || it.disabled) return;
2016
+ if (!el || it.disabled || ro) return;
2006
2017
  if (el.value !== it.value) {
2007
2018
  el.value = it.value;
2008
2019
  el.dispatchEvent(new Event("input", { bubbles: true }));
@@ -2012,7 +2023,7 @@ window.Aura = (() => {
2012
2023
  close(true);
2013
2024
  }
2014
2025
  function openList(at) {
2015
- if (props.disabled || trigRef.current && trigRef.current.disabled) return;
2026
+ if (props.disabled || ro || trigRef.current && trigRef.current.disabled) return;
2016
2027
  const list = readItems(selRef.current);
2017
2028
  const ok = choices(list);
2018
2029
  if (!ok.length) return;
@@ -2143,6 +2154,45 @@ window.Aura = (() => {
2143
2154
  [open, active, pos[0] != null]
2144
2155
  );
2145
2156
  const described = [describedBy(id, props), rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
2157
+ function keep(e) {
2158
+ kept.current = Array.prototype.filter.call(e.currentTarget.options, function(o) {
2159
+ return o.selected;
2160
+ }).map(function(o) {
2161
+ return o.value;
2162
+ });
2163
+ }
2164
+ const listBoxLock = ro && native ? {
2165
+ onFocus: function(e) {
2166
+ const user = rest.onFocus;
2167
+ if (user) user(e);
2168
+ keep(e);
2169
+ },
2170
+ onPointerDown: function(e) {
2171
+ const user = rest.onPointerDown;
2172
+ if (user) user(e);
2173
+ keep(e);
2174
+ },
2175
+ onMouseDown: function(e) {
2176
+ const user = rest.onMouseDown;
2177
+ if (user) user(e);
2178
+ e.preventDefault();
2179
+ e.currentTarget.focus();
2180
+ },
2181
+ onKeyDown: function(e) {
2182
+ const user = rest.onKeyDown;
2183
+ if (user) user(e);
2184
+ const k = e.key;
2185
+ const picks = /^(Arrow(Up|Down|Left|Right)|Home|End|Page(Up|Down))$/.test(k) || k.length === 1 && !e.altKey && (!(e.ctrlKey || e.metaKey) || k === " " || k.toLowerCase() === "a");
2186
+ if (picks) e.preventDefault();
2187
+ },
2188
+ onChange: function(e) {
2189
+ const was = kept.current;
2190
+ if (!was) return;
2191
+ Array.prototype.forEach.call(e.currentTarget.options, function(o) {
2192
+ o.selected = was.indexOf(o.value) >= 0;
2193
+ });
2194
+ }
2195
+ } : {};
2146
2196
  const field = (child) => filter ? child : /* @__PURE__ */ React15.createElement(
2147
2197
  Field,
2148
2198
  {
@@ -2175,19 +2225,25 @@ window.Aura = (() => {
2175
2225
  ));
2176
2226
  if (!live)
2177
2227
  return field(
2178
- /* @__PURE__ */ React15.createElement("div", { className: cx("aura-input aura-select", props.icon && "has-icon") }, props.icon ? /* @__PURE__ */ React15.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, h4(
2228
+ /* @__PURE__ */ React15.createElement("div", { className: cx("aura-input aura-select", props.icon && "has-icon", ro && "is-readonly") }, props.icon ? /* @__PURE__ */ React15.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, h4(
2179
2229
  "select",
2180
- Object.assign(extra, rest, {
2181
- ref: native ? ref : selMerged,
2182
- id,
2183
- className: "aura-input__control",
2184
- required: props.required,
2185
- "aria-invalid": props.error ? true : void 0,
2186
- "aria-describedby": described
2187
- }),
2230
+ Object.assign(
2231
+ extra,
2232
+ rest,
2233
+ {
2234
+ ref: native ? ref : selMerged,
2235
+ id,
2236
+ className: "aura-input__control",
2237
+ required: props.required,
2238
+ "aria-invalid": props.error ? true : void 0,
2239
+ "aria-readonly": ro || void 0,
2240
+ "aria-describedby": described
2241
+ },
2242
+ listBoxLock
2243
+ ),
2188
2244
  opts,
2189
2245
  props.children
2190
- ), /* @__PURE__ */ React15.createElement(Icon, { name: /* @__PURE__ */ React15.createElement(IconChevronDown, null), className: "aura-select__chevron" }))
2246
+ ), ro ? null : /* @__PURE__ */ React15.createElement(Icon, { name: /* @__PURE__ */ React15.createElement(IconChevronDown, null), className: "aura-select__chevron" }))
2191
2247
  );
2192
2248
  const listId = id + "-list", labelId = id + "-label";
2193
2249
  const optId = function(i) {
@@ -2276,6 +2332,7 @@ window.Aura = (() => {
2276
2332
  filter ? "aura-filterselect" : "aura-input",
2277
2333
  "aura-select aura-select--custom",
2278
2334
  props.icon && "has-icon",
2335
+ ro && "is-readonly",
2279
2336
  open && "is-open",
2280
2337
  filter && props.disabled && "is-disabled",
2281
2338
  filter && props.className
@@ -2328,6 +2385,7 @@ window.Aura = (() => {
2328
2385
  "aria-labelledby": rest["aria-labelledby"] || (filter && !rest["aria-label"] ? nameId : void 0),
2329
2386
  "aria-required": props.required || void 0,
2330
2387
  "aria-invalid": props.error ? true : void 0,
2388
+ "aria-readonly": ro || void 0,
2331
2389
  "aria-describedby": described,
2332
2390
  onFocus: function(e) {
2333
2391
  if (userFocus) userFocus(e);
@@ -2350,7 +2408,7 @@ window.Aura = (() => {
2350
2408
  },
2351
2409
  face || /* @__PURE__ */ React15.createElement("span", { id: id + "-value", className: cx("aura-select__value", shown[0].empty && "is-placeholder") }, shown[0].label || " ")
2352
2410
  ),
2353
- filter ? null : /* @__PURE__ */ React15.createElement(Icon, { name: /* @__PURE__ */ React15.createElement(IconChevronDown, null), className: "aura-select__chevron" }),
2411
+ filter || ro ? null : /* @__PURE__ */ React15.createElement(Icon, { name: /* @__PURE__ */ React15.createElement(IconChevronDown, null), className: "aura-select__chevron" }),
2354
2412
  popup
2355
2413
  )
2356
2414
  );
@@ -2429,13 +2487,15 @@ window.Aura = (() => {
2429
2487
  const auto = uid(), id = props.id || auto;
2430
2488
  const st = useMaybeControlled(props.checked, !!props.defaultChecked, props.onChange);
2431
2489
  const on = !!st[0];
2490
+ const ro = !!props.readOnly && !props.disabled;
2491
+ const described = [props.description ? id + "-desc" : "", props["aria-describedby"] || ""].filter(Boolean).join(" ");
2432
2492
  return /* @__PURE__ */ React18.createElement(
2433
2493
  "div",
2434
2494
  {
2435
- className: cx("aura-switch-row", props.disabled && "is-disabled", props.className),
2495
+ className: cx("aura-switch-row", props.disabled && "is-disabled", ro && "is-readonly", props.className),
2436
2496
  onClick: function(e) {
2437
2497
  const t = e.target;
2438
- if (props.disabled || t.closest("button, label, a, input")) return;
2498
+ if (props.disabled || ro || t.closest("button, label, a, input")) return;
2439
2499
  st[1](!on);
2440
2500
  }
2441
2501
  },
@@ -2450,15 +2510,17 @@ window.Aura = (() => {
2450
2510
  disabled: props.disabled,
2451
2511
  "aria-labelledby": props.label ? id + "-label" : void 0,
2452
2512
  "aria-label": props.label ? void 0 : props["aria-label"],
2453
- "aria-describedby": props.description ? id + "-desc" : void 0,
2513
+ "aria-readonly": ro || void 0,
2514
+ "aria-describedby": described || void 0,
2454
2515
  className: cx("aura-switch", on && "is-on"),
2455
2516
  onClick: function() {
2456
- st[1](!on);
2517
+ if (!ro) st[1](!on);
2457
2518
  }
2458
2519
  },
2459
2520
  /* @__PURE__ */ React18.createElement("span", { className: "aura-switch__thumb" })
2460
2521
  ),
2461
- props.label ? /* @__PURE__ */ React18.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React18.createElement("label", { className: "aura-choice__label", id: id + "-label", htmlFor: id }, props.label), props.description ? /* @__PURE__ */ React18.createElement("span", { className: "aura-choice__desc", id: id + "-desc" }, props.description) : null) : props.description ? /* @__PURE__ */ React18.createElement("span", { className: "aura-sr-only", id: id + "-desc" }, props.description) : null
2522
+ props.label ? /* @__PURE__ */ React18.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React18.createElement("label", { className: "aura-choice__label", id: id + "-label", htmlFor: id }, props.label), props.description ? /* @__PURE__ */ React18.createElement("span", { className: "aura-choice__desc", id: id + "-desc" }, props.description) : null) : props.description ? /* @__PURE__ */ React18.createElement("span", { className: "aura-sr-only", id: id + "-desc" }, props.description) : null,
2523
+ props.icon ? /* @__PURE__ */ React18.createElement(Icon, { name: props.icon, className: "aura-switch-row__icon" }) : null
2462
2524
  );
2463
2525
  });
2464
2526
 
@@ -7102,6 +7164,9 @@ window.Aura = (() => {
7102
7164
  const bulk = props.selected != null;
7103
7165
  const idle = bulk && !props.selected;
7104
7166
  const count = bulk && props.selected ? t.selectedCount(props.selected) : null;
7167
+ const hasStart = React42.Children.toArray(props.start).some(function(c) {
7168
+ return c !== "";
7169
+ });
7105
7170
  const cameFrom = React42.useRef(null);
7106
7171
  const own = React42.useRef(null);
7107
7172
  function clear() {
@@ -7133,7 +7198,7 @@ window.Aura = (() => {
7133
7198
  props.className
7134
7199
  )
7135
7200
  },
7136
- /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__inner" }, /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__status", role: "status" }, count, count && props.status ? " \xB7 " : null, props.status), !idle ? /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__actions" }, count && props.onClearSelection ? /* @__PURE__ */ React42.createElement(Button, { variant: "ghost", size: "sm", onClick: clear }, t.clear()) : null, props.children) : null)
7201
+ /* @__PURE__ */ React42.createElement("div", { className: cx("aura-actionbar__inner", hasStart && "has-start") }, /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__status", role: "status" }, count, count && props.status ? " \xB7 " : null, props.status), !idle && hasStart ? /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__start" }, props.start) : null, !idle ? /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__actions" }, count && props.onClearSelection ? /* @__PURE__ */ React42.createElement(Button, { variant: "ghost", size: "sm", onClick: clear }, t.clear()) : null, props.children) : null)
7137
7202
  );
7138
7203
  });
7139
7204
 
@@ -7211,6 +7276,7 @@ window.Aura = (() => {
7211
7276
  "Table stackBelow: no THead > Tr > Th found among its children, so cells have no labels. Pass `label` on each Td."
7212
7277
  );
7213
7278
  const outer = React44.useContext(StackLabels);
7279
+ const pageDensity = useDensity();
7214
7280
  const capId = uid();
7215
7281
  const wrap = React44.useRef(null);
7216
7282
  const sc = React44.useState(false), scrolls = sc[0];
@@ -7232,7 +7298,7 @@ window.Aura = (() => {
7232
7298
  {
7233
7299
  ref: wrap,
7234
7300
  className: cx("aura-tbl-wrap", stackBelow && "is-stackable", bordered === false && "is-flush"),
7235
- "data-density": density,
7301
+ "data-density": density || pageDensity,
7236
7302
  tabIndex: scrolls ? 0 : void 0,
7237
7303
  role: scrolls && (caption != null || rest["aria-label"]) ? "region" : void 0,
7238
7304
  "aria-labelledby": scrolls && caption != null ? capId : void 0,
@@ -1851,10 +1851,22 @@ function initialLabel(props, filter) {
1851
1851
  var Select = React15.forwardRef(function Select2(props, ref) {
1852
1852
  const auto = uid(), id = props.id || auto;
1853
1853
  const filter = props[FILTER_KEY];
1854
- const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY]));
1854
+ const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY, "readOnly"]));
1855
+ const ro = !!props.readOnly && !props.disabled;
1856
+ const mounted = useMounted();
1857
+ const kept = React15.useRef(null);
1858
+ const current2 = [].concat(props.value != null ? props.value : props.defaultValue != null ? props.defaultValue : []).map(String);
1855
1859
  const opts = (props.options || []).map(function(o) {
1856
1860
  const v = typeof o === "object" ? o : { value: o, label: o };
1857
- return /* @__PURE__ */ React15.createElement("option", { key: v.value, value: v.value, disabled: v.disabled }, v.label);
1861
+ return /* @__PURE__ */ React15.createElement(
1862
+ "option",
1863
+ {
1864
+ key: v.value,
1865
+ value: v.value,
1866
+ disabled: v.disabled || ro && !mounted && current2.length > 0 && current2.indexOf(v.value) < 0 || void 0
1867
+ },
1868
+ v.label
1869
+ );
1858
1870
  });
1859
1871
  if (props.placeholder)
1860
1872
  opts.unshift(
@@ -1862,7 +1874,6 @@ var Select = React15.forwardRef(function Select2(props, ref) {
1862
1874
  );
1863
1875
  const extra = props.value === void 0 && props.defaultValue === void 0 && props.placeholder ? { defaultValue: "" } : {};
1864
1876
  const native = !!props.multiple || props.size != null && props.size > 1;
1865
- const mounted = useMounted();
1866
1877
  const live = !native && mounted;
1867
1878
  const density = useDensity();
1868
1879
  React15.useEffect(function() {
@@ -1984,7 +1995,7 @@ var Select = React15.forwardRef(function Select2(props, ref) {
1984
1995
  }
1985
1996
  function choose(it) {
1986
1997
  const el = selRef.current;
1987
- if (!el || it.disabled) return;
1998
+ if (!el || it.disabled || ro) return;
1988
1999
  if (el.value !== it.value) {
1989
2000
  el.value = it.value;
1990
2001
  el.dispatchEvent(new Event("input", { bubbles: true }));
@@ -1994,7 +2005,7 @@ var Select = React15.forwardRef(function Select2(props, ref) {
1994
2005
  close(true);
1995
2006
  }
1996
2007
  function openList(at) {
1997
- if (props.disabled || trigRef.current && trigRef.current.disabled) return;
2008
+ if (props.disabled || ro || trigRef.current && trigRef.current.disabled) return;
1998
2009
  const list = readItems(selRef.current);
1999
2010
  const ok = choices(list);
2000
2011
  if (!ok.length) return;
@@ -2125,6 +2136,45 @@ var Select = React15.forwardRef(function Select2(props, ref) {
2125
2136
  [open, active, pos[0] != null]
2126
2137
  );
2127
2138
  const described = [describedBy(id, props), rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
2139
+ function keep(e) {
2140
+ kept.current = Array.prototype.filter.call(e.currentTarget.options, function(o) {
2141
+ return o.selected;
2142
+ }).map(function(o) {
2143
+ return o.value;
2144
+ });
2145
+ }
2146
+ const listBoxLock = ro && native ? {
2147
+ onFocus: function(e) {
2148
+ const user = rest.onFocus;
2149
+ if (user) user(e);
2150
+ keep(e);
2151
+ },
2152
+ onPointerDown: function(e) {
2153
+ const user = rest.onPointerDown;
2154
+ if (user) user(e);
2155
+ keep(e);
2156
+ },
2157
+ onMouseDown: function(e) {
2158
+ const user = rest.onMouseDown;
2159
+ if (user) user(e);
2160
+ e.preventDefault();
2161
+ e.currentTarget.focus();
2162
+ },
2163
+ onKeyDown: function(e) {
2164
+ const user = rest.onKeyDown;
2165
+ if (user) user(e);
2166
+ const k = e.key;
2167
+ const picks = /^(Arrow(Up|Down|Left|Right)|Home|End|Page(Up|Down))$/.test(k) || k.length === 1 && !e.altKey && (!(e.ctrlKey || e.metaKey) || k === " " || k.toLowerCase() === "a");
2168
+ if (picks) e.preventDefault();
2169
+ },
2170
+ onChange: function(e) {
2171
+ const was = kept.current;
2172
+ if (!was) return;
2173
+ Array.prototype.forEach.call(e.currentTarget.options, function(o) {
2174
+ o.selected = was.indexOf(o.value) >= 0;
2175
+ });
2176
+ }
2177
+ } : {};
2128
2178
  const field = (child) => filter ? child : /* @__PURE__ */ React15.createElement(
2129
2179
  Field,
2130
2180
  {
@@ -2157,19 +2207,25 @@ var Select = React15.forwardRef(function Select2(props, ref) {
2157
2207
  ));
2158
2208
  if (!live)
2159
2209
  return field(
2160
- /* @__PURE__ */ React15.createElement("div", { className: cx("aura-input aura-select", props.icon && "has-icon") }, props.icon ? /* @__PURE__ */ React15.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, h4(
2210
+ /* @__PURE__ */ React15.createElement("div", { className: cx("aura-input aura-select", props.icon && "has-icon", ro && "is-readonly") }, props.icon ? /* @__PURE__ */ React15.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, h4(
2161
2211
  "select",
2162
- Object.assign(extra, rest, {
2163
- ref: native ? ref : selMerged,
2164
- id,
2165
- className: "aura-input__control",
2166
- required: props.required,
2167
- "aria-invalid": props.error ? true : void 0,
2168
- "aria-describedby": described
2169
- }),
2212
+ Object.assign(
2213
+ extra,
2214
+ rest,
2215
+ {
2216
+ ref: native ? ref : selMerged,
2217
+ id,
2218
+ className: "aura-input__control",
2219
+ required: props.required,
2220
+ "aria-invalid": props.error ? true : void 0,
2221
+ "aria-readonly": ro || void 0,
2222
+ "aria-describedby": described
2223
+ },
2224
+ listBoxLock
2225
+ ),
2170
2226
  opts,
2171
2227
  props.children
2172
- ), /* @__PURE__ */ React15.createElement(Icon, { name: /* @__PURE__ */ React15.createElement(IconChevronDown, null), className: "aura-select__chevron" }))
2228
+ ), ro ? null : /* @__PURE__ */ React15.createElement(Icon, { name: /* @__PURE__ */ React15.createElement(IconChevronDown, null), className: "aura-select__chevron" }))
2173
2229
  );
2174
2230
  const listId = id + "-list", labelId = id + "-label";
2175
2231
  const optId = function(i) {
@@ -2258,6 +2314,7 @@ var Select = React15.forwardRef(function Select2(props, ref) {
2258
2314
  filter ? "aura-filterselect" : "aura-input",
2259
2315
  "aura-select aura-select--custom",
2260
2316
  props.icon && "has-icon",
2317
+ ro && "is-readonly",
2261
2318
  open && "is-open",
2262
2319
  filter && props.disabled && "is-disabled",
2263
2320
  filter && props.className
@@ -2310,6 +2367,7 @@ var Select = React15.forwardRef(function Select2(props, ref) {
2310
2367
  "aria-labelledby": rest["aria-labelledby"] || (filter && !rest["aria-label"] ? nameId : void 0),
2311
2368
  "aria-required": props.required || void 0,
2312
2369
  "aria-invalid": props.error ? true : void 0,
2370
+ "aria-readonly": ro || void 0,
2313
2371
  "aria-describedby": described,
2314
2372
  onFocus: function(e) {
2315
2373
  if (userFocus) userFocus(e);
@@ -2332,7 +2390,7 @@ var Select = React15.forwardRef(function Select2(props, ref) {
2332
2390
  },
2333
2391
  face || /* @__PURE__ */ React15.createElement("span", { id: id + "-value", className: cx("aura-select__value", shown[0].empty && "is-placeholder") }, shown[0].label || " ")
2334
2392
  ),
2335
- filter ? null : /* @__PURE__ */ React15.createElement(Icon, { name: /* @__PURE__ */ React15.createElement(IconChevronDown, null), className: "aura-select__chevron" }),
2393
+ filter || ro ? null : /* @__PURE__ */ React15.createElement(Icon, { name: /* @__PURE__ */ React15.createElement(IconChevronDown, null), className: "aura-select__chevron" }),
2336
2394
  popup
2337
2395
  )
2338
2396
  );
@@ -2411,13 +2469,15 @@ var Switch = React18.forwardRef(function Switch2(props, ref) {
2411
2469
  const auto = uid(), id = props.id || auto;
2412
2470
  const st = useMaybeControlled(props.checked, !!props.defaultChecked, props.onChange);
2413
2471
  const on = !!st[0];
2472
+ const ro = !!props.readOnly && !props.disabled;
2473
+ const described = [props.description ? id + "-desc" : "", props["aria-describedby"] || ""].filter(Boolean).join(" ");
2414
2474
  return /* @__PURE__ */ React18.createElement(
2415
2475
  "div",
2416
2476
  {
2417
- className: cx("aura-switch-row", props.disabled && "is-disabled", props.className),
2477
+ className: cx("aura-switch-row", props.disabled && "is-disabled", ro && "is-readonly", props.className),
2418
2478
  onClick: function(e) {
2419
2479
  const t = e.target;
2420
- if (props.disabled || t.closest("button, label, a, input")) return;
2480
+ if (props.disabled || ro || t.closest("button, label, a, input")) return;
2421
2481
  st[1](!on);
2422
2482
  }
2423
2483
  },
@@ -2432,15 +2492,17 @@ var Switch = React18.forwardRef(function Switch2(props, ref) {
2432
2492
  disabled: props.disabled,
2433
2493
  "aria-labelledby": props.label ? id + "-label" : void 0,
2434
2494
  "aria-label": props.label ? void 0 : props["aria-label"],
2435
- "aria-describedby": props.description ? id + "-desc" : void 0,
2495
+ "aria-readonly": ro || void 0,
2496
+ "aria-describedby": described || void 0,
2436
2497
  className: cx("aura-switch", on && "is-on"),
2437
2498
  onClick: function() {
2438
- st[1](!on);
2499
+ if (!ro) st[1](!on);
2439
2500
  }
2440
2501
  },
2441
2502
  /* @__PURE__ */ React18.createElement("span", { className: "aura-switch__thumb" })
2442
2503
  ),
2443
- props.label ? /* @__PURE__ */ React18.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React18.createElement("label", { className: "aura-choice__label", id: id + "-label", htmlFor: id }, props.label), props.description ? /* @__PURE__ */ React18.createElement("span", { className: "aura-choice__desc", id: id + "-desc" }, props.description) : null) : props.description ? /* @__PURE__ */ React18.createElement("span", { className: "aura-sr-only", id: id + "-desc" }, props.description) : null
2504
+ props.label ? /* @__PURE__ */ React18.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React18.createElement("label", { className: "aura-choice__label", id: id + "-label", htmlFor: id }, props.label), props.description ? /* @__PURE__ */ React18.createElement("span", { className: "aura-choice__desc", id: id + "-desc" }, props.description) : null) : props.description ? /* @__PURE__ */ React18.createElement("span", { className: "aura-sr-only", id: id + "-desc" }, props.description) : null,
2505
+ props.icon ? /* @__PURE__ */ React18.createElement(Icon, { name: props.icon, className: "aura-switch-row__icon" }) : null
2444
2506
  );
2445
2507
  });
2446
2508
 
@@ -7084,6 +7146,9 @@ var ActionBar = React42.forwardRef(function ActionBar2(props, ref) {
7084
7146
  const bulk = props.selected != null;
7085
7147
  const idle = bulk && !props.selected;
7086
7148
  const count = bulk && props.selected ? t.selectedCount(props.selected) : null;
7149
+ const hasStart = React42.Children.toArray(props.start).some(function(c) {
7150
+ return c !== "";
7151
+ });
7087
7152
  const cameFrom = React42.useRef(null);
7088
7153
  const own = React42.useRef(null);
7089
7154
  function clear() {
@@ -7115,7 +7180,7 @@ var ActionBar = React42.forwardRef(function ActionBar2(props, ref) {
7115
7180
  props.className
7116
7181
  )
7117
7182
  },
7118
- /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__inner" }, /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__status", role: "status" }, count, count && props.status ? " \xB7 " : null, props.status), !idle ? /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__actions" }, count && props.onClearSelection ? /* @__PURE__ */ React42.createElement(Button, { variant: "ghost", size: "sm", onClick: clear }, t.clear()) : null, props.children) : null)
7183
+ /* @__PURE__ */ React42.createElement("div", { className: cx("aura-actionbar__inner", hasStart && "has-start") }, /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__status", role: "status" }, count, count && props.status ? " \xB7 " : null, props.status), !idle && hasStart ? /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__start" }, props.start) : null, !idle ? /* @__PURE__ */ React42.createElement("div", { className: "aura-actionbar__actions" }, count && props.onClearSelection ? /* @__PURE__ */ React42.createElement(Button, { variant: "ghost", size: "sm", onClick: clear }, t.clear()) : null, props.children) : null)
7119
7184
  );
7120
7185
  });
7121
7186
 
@@ -7193,6 +7258,7 @@ var Table = React44.forwardRef(function Table2(props, ref) {
7193
7258
  "Table stackBelow: no THead > Tr > Th found among its children, so cells have no labels. Pass `label` on each Td."
7194
7259
  );
7195
7260
  const outer = React44.useContext(StackLabels);
7261
+ const pageDensity = useDensity();
7196
7262
  const capId = uid();
7197
7263
  const wrap = React44.useRef(null);
7198
7264
  const sc = React44.useState(false), scrolls = sc[0];
@@ -7214,7 +7280,7 @@ var Table = React44.forwardRef(function Table2(props, ref) {
7214
7280
  {
7215
7281
  ref: wrap,
7216
7282
  className: cx("aura-tbl-wrap", stackBelow && "is-stackable", bordered === false && "is-flush"),
7217
- "data-density": density,
7283
+ "data-density": density || pageDensity,
7218
7284
  tabIndex: scrolls ? 0 : void 0,
7219
7285
  role: scrolls && (caption != null || rest["aria-label"]) ? "region" : void 0,
7220
7286
  "aria-labelledby": scrolls && caption != null ? capId : void 0,
@@ -8,6 +8,9 @@ const ActionBar = React.forwardRef(function ActionBar2(props, ref) {
8
8
  const bulk = props.selected != null;
9
9
  const idle = bulk && !props.selected;
10
10
  const count = bulk && props.selected ? t.selectedCount(props.selected) : null;
11
+ const hasStart = React.Children.toArray(props.start).some(function(c) {
12
+ return c !== "";
13
+ });
11
14
  const cameFrom = React.useRef(null);
12
15
  const own = React.useRef(null);
13
16
  function clear() {
@@ -39,7 +42,7 @@ const ActionBar = React.forwardRef(function ActionBar2(props, ref) {
39
42
  props.className
40
43
  )
41
44
  },
42
- /* @__PURE__ */ React.createElement("div", { className: "aura-actionbar__inner" }, /* @__PURE__ */ React.createElement("div", { className: "aura-actionbar__status", role: "status" }, count, count && props.status ? " \xB7 " : null, props.status), !idle ? /* @__PURE__ */ React.createElement("div", { className: "aura-actionbar__actions" }, count && props.onClearSelection ? /* @__PURE__ */ React.createElement(Button, { variant: "ghost", size: "sm", onClick: clear }, t.clear()) : null, props.children) : null)
45
+ /* @__PURE__ */ React.createElement("div", { className: cx("aura-actionbar__inner", hasStart && "has-start") }, /* @__PURE__ */ React.createElement("div", { className: "aura-actionbar__status", role: "status" }, count, count && props.status ? " \xB7 " : null, props.status), !idle && hasStart ? /* @__PURE__ */ React.createElement("div", { className: "aura-actionbar__start" }, props.start) : null, !idle ? /* @__PURE__ */ React.createElement("div", { className: "aura-actionbar__actions" }, count && props.onClearSelection ? /* @__PURE__ */ React.createElement(Button, { variant: "ghost", size: "sm", onClick: clear }, t.clear()) : null, props.children) : null)
43
46
  );
44
47
  });
45
48
  export {
@@ -83,10 +83,22 @@ function initialLabel(props, filter) {
83
83
  const Select = React.forwardRef(function Select2(props, ref) {
84
84
  const auto = uid(), id = props.id || auto;
85
85
  const filter = props[FILTER_KEY];
86
- const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY]));
86
+ const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY, "readOnly"]));
87
+ const ro = !!props.readOnly && !props.disabled;
88
+ const mounted = useMounted();
89
+ const kept = React.useRef(null);
90
+ const current = [].concat(props.value != null ? props.value : props.defaultValue != null ? props.defaultValue : []).map(String);
87
91
  const opts = (props.options || []).map(function(o) {
88
92
  const v = typeof o === "object" ? o : { value: o, label: o };
89
- return /* @__PURE__ */ React.createElement("option", { key: v.value, value: v.value, disabled: v.disabled }, v.label);
93
+ return /* @__PURE__ */ React.createElement(
94
+ "option",
95
+ {
96
+ key: v.value,
97
+ value: v.value,
98
+ disabled: v.disabled || ro && !mounted && current.length > 0 && current.indexOf(v.value) < 0 || void 0
99
+ },
100
+ v.label
101
+ );
90
102
  });
91
103
  if (props.placeholder)
92
104
  opts.unshift(
@@ -94,7 +106,6 @@ const Select = React.forwardRef(function Select2(props, ref) {
94
106
  );
95
107
  const extra = props.value === void 0 && props.defaultValue === void 0 && props.placeholder ? { defaultValue: "" } : {};
96
108
  const native = !!props.multiple || props.size != null && props.size > 1;
97
- const mounted = useMounted();
98
109
  const live = !native && mounted;
99
110
  const density = useDensity();
100
111
  React.useEffect(function() {
@@ -216,7 +227,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
216
227
  }
217
228
  function choose(it) {
218
229
  const el = selRef.current;
219
- if (!el || it.disabled) return;
230
+ if (!el || it.disabled || ro) return;
220
231
  if (el.value !== it.value) {
221
232
  el.value = it.value;
222
233
  el.dispatchEvent(new Event("input", { bubbles: true }));
@@ -226,7 +237,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
226
237
  close(true);
227
238
  }
228
239
  function openList(at) {
229
- if (props.disabled || trigRef.current && trigRef.current.disabled) return;
240
+ if (props.disabled || ro || trigRef.current && trigRef.current.disabled) return;
230
241
  const list = readItems(selRef.current);
231
242
  const ok = choices(list);
232
243
  if (!ok.length) return;
@@ -357,6 +368,45 @@ const Select = React.forwardRef(function Select2(props, ref) {
357
368
  [open, active, pos[0] != null]
358
369
  );
359
370
  const described = [describedBy(id, props), rest["aria-describedby"]].filter(Boolean).join(" ") || void 0;
371
+ function keep(e) {
372
+ kept.current = Array.prototype.filter.call(e.currentTarget.options, function(o) {
373
+ return o.selected;
374
+ }).map(function(o) {
375
+ return o.value;
376
+ });
377
+ }
378
+ const listBoxLock = ro && native ? {
379
+ onFocus: function(e) {
380
+ const user = rest.onFocus;
381
+ if (user) user(e);
382
+ keep(e);
383
+ },
384
+ onPointerDown: function(e) {
385
+ const user = rest.onPointerDown;
386
+ if (user) user(e);
387
+ keep(e);
388
+ },
389
+ onMouseDown: function(e) {
390
+ const user = rest.onMouseDown;
391
+ if (user) user(e);
392
+ e.preventDefault();
393
+ e.currentTarget.focus();
394
+ },
395
+ onKeyDown: function(e) {
396
+ const user = rest.onKeyDown;
397
+ if (user) user(e);
398
+ const k = e.key;
399
+ const picks = /^(Arrow(Up|Down|Left|Right)|Home|End|Page(Up|Down))$/.test(k) || k.length === 1 && !e.altKey && (!(e.ctrlKey || e.metaKey) || k === " " || k.toLowerCase() === "a");
400
+ if (picks) e.preventDefault();
401
+ },
402
+ onChange: function(e) {
403
+ const was = kept.current;
404
+ if (!was) return;
405
+ Array.prototype.forEach.call(e.currentTarget.options, function(o) {
406
+ o.selected = was.indexOf(o.value) >= 0;
407
+ });
408
+ }
409
+ } : {};
360
410
  const field = (child) => filter ? child : /* @__PURE__ */ React.createElement(
361
411
  Field,
362
412
  {
@@ -389,19 +439,25 @@ const Select = React.forwardRef(function Select2(props, ref) {
389
439
  ));
390
440
  if (!live)
391
441
  return field(
392
- /* @__PURE__ */ React.createElement("div", { className: cx("aura-input aura-select", props.icon && "has-icon") }, props.icon ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, h(
442
+ /* @__PURE__ */ React.createElement("div", { className: cx("aura-input aura-select", props.icon && "has-icon", ro && "is-readonly") }, props.icon ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-input__icon" }) : null, h(
393
443
  "select",
394
- Object.assign(extra, rest, {
395
- ref: native ? ref : selMerged,
396
- id,
397
- className: "aura-input__control",
398
- required: props.required,
399
- "aria-invalid": props.error ? true : void 0,
400
- "aria-describedby": described
401
- }),
444
+ Object.assign(
445
+ extra,
446
+ rest,
447
+ {
448
+ ref: native ? ref : selMerged,
449
+ id,
450
+ className: "aura-input__control",
451
+ required: props.required,
452
+ "aria-invalid": props.error ? true : void 0,
453
+ "aria-readonly": ro || void 0,
454
+ "aria-describedby": described
455
+ },
456
+ listBoxLock
457
+ ),
402
458
  opts,
403
459
  props.children
404
- ), /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconChevronDown, null), className: "aura-select__chevron" }))
460
+ ), ro ? null : /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconChevronDown, null), className: "aura-select__chevron" }))
405
461
  );
406
462
  const listId = id + "-list", labelId = id + "-label";
407
463
  const optId = function(i) {
@@ -490,6 +546,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
490
546
  filter ? "aura-filterselect" : "aura-input",
491
547
  "aura-select aura-select--custom",
492
548
  props.icon && "has-icon",
549
+ ro && "is-readonly",
493
550
  open && "is-open",
494
551
  filter && props.disabled && "is-disabled",
495
552
  filter && props.className
@@ -542,6 +599,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
542
599
  "aria-labelledby": rest["aria-labelledby"] || (filter && !rest["aria-label"] ? nameId : void 0),
543
600
  "aria-required": props.required || void 0,
544
601
  "aria-invalid": props.error ? true : void 0,
602
+ "aria-readonly": ro || void 0,
545
603
  "aria-describedby": described,
546
604
  onFocus: function(e) {
547
605
  if (userFocus) userFocus(e);
@@ -564,7 +622,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
564
622
  },
565
623
  face || /* @__PURE__ */ React.createElement("span", { id: id + "-value", className: cx("aura-select__value", shown[0].empty && "is-placeholder") }, shown[0].label || " ")
566
624
  ),
567
- filter ? null : /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconChevronDown, null), className: "aura-select__chevron" }),
625
+ filter || ro ? null : /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconChevronDown, null), className: "aura-select__chevron" }),
568
626
  popup
569
627
  )
570
628
  );
@@ -1,17 +1,20 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
+ import { Icon } from "./Icon.js";
3
4
  import { cx, uid, useMaybeControlled } from "./internal.js";
4
5
  const Switch = React.forwardRef(function Switch2(props, ref) {
5
6
  const auto = uid(), id = props.id || auto;
6
7
  const st = useMaybeControlled(props.checked, !!props.defaultChecked, props.onChange);
7
8
  const on = !!st[0];
9
+ const ro = !!props.readOnly && !props.disabled;
10
+ const described = [props.description ? id + "-desc" : "", props["aria-describedby"] || ""].filter(Boolean).join(" ");
8
11
  return /* @__PURE__ */ React.createElement(
9
12
  "div",
10
13
  {
11
- className: cx("aura-switch-row", props.disabled && "is-disabled", props.className),
14
+ className: cx("aura-switch-row", props.disabled && "is-disabled", ro && "is-readonly", props.className),
12
15
  onClick: function(e) {
13
16
  const t = e.target;
14
- if (props.disabled || t.closest("button, label, a, input")) return;
17
+ if (props.disabled || ro || t.closest("button, label, a, input")) return;
15
18
  st[1](!on);
16
19
  }
17
20
  },
@@ -26,15 +29,17 @@ const Switch = React.forwardRef(function Switch2(props, ref) {
26
29
  disabled: props.disabled,
27
30
  "aria-labelledby": props.label ? id + "-label" : void 0,
28
31
  "aria-label": props.label ? void 0 : props["aria-label"],
29
- "aria-describedby": props.description ? id + "-desc" : void 0,
32
+ "aria-readonly": ro || void 0,
33
+ "aria-describedby": described || void 0,
30
34
  className: cx("aura-switch", on && "is-on"),
31
35
  onClick: function() {
32
- st[1](!on);
36
+ if (!ro) st[1](!on);
33
37
  }
34
38
  },
35
39
  /* @__PURE__ */ React.createElement("span", { className: "aura-switch__thumb" })
36
40
  ),
37
- props.label ? /* @__PURE__ */ React.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React.createElement("label", { className: "aura-choice__label", id: id + "-label", htmlFor: id }, props.label), props.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-choice__desc", id: id + "-desc" }, props.description) : null) : props.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-sr-only", id: id + "-desc" }, props.description) : null
41
+ props.label ? /* @__PURE__ */ React.createElement("span", { className: "aura-choice__text" }, /* @__PURE__ */ React.createElement("label", { className: "aura-choice__label", id: id + "-label", htmlFor: id }, props.label), props.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-choice__desc", id: id + "-desc" }, props.description) : null) : props.description ? /* @__PURE__ */ React.createElement("span", { className: "aura-sr-only", id: id + "-desc" }, props.description) : null,
42
+ props.icon ? /* @__PURE__ */ React.createElement(Icon, { name: props.icon, className: "aura-switch-row__icon" }) : null
38
43
  );
39
44
  });
40
45
  export {
package/dist/esm/Table.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { cx, devWarnOnce, uid, useIsoLayoutEffect } from "./internal.js";
4
+ import { useDensity } from "./locale.js";
4
5
  const StackLabels = React.createContext(null);
5
6
  const ColumnIndex = React.createContext(-1);
6
7
  function flat(children) {
@@ -56,6 +57,7 @@ const Table = React.forwardRef(function Table2(props, ref) {
56
57
  "Table stackBelow: no THead > Tr > Th found among its children, so cells have no labels. Pass `label` on each Td."
57
58
  );
58
59
  const outer = React.useContext(StackLabels);
60
+ const pageDensity = useDensity();
59
61
  const capId = uid();
60
62
  const wrap = React.useRef(null);
61
63
  const sc = React.useState(false), scrolls = sc[0];
@@ -77,7 +79,7 @@ const Table = React.forwardRef(function Table2(props, ref) {
77
79
  {
78
80
  ref: wrap,
79
81
  className: cx("aura-tbl-wrap", stackBelow && "is-stackable", bordered === false && "is-flush"),
80
- "data-density": density,
82
+ "data-density": density || pageDensity,
81
83
  tabIndex: scrolls ? 0 : void 0,
82
84
  role: scrolls && (caption != null || rest["aria-label"]) ? "region" : void 0,
83
85
  "aria-labelledby": scrolls && caption != null ? capId : void 0,
package/dist/index.d.cts CHANGED
@@ -517,6 +517,10 @@ export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.Sel
517
517
  /** Shown in fg-tertiary on the closed field until something is chosen; not listed as a choice. */
518
518
  placeholder?: string | undefined;
519
519
  icon?: IconInput | undefined;
520
+ /** 5.19 (Chamber-OS 114): a locked value. The field stays in the Tab order with `aria-readonly` and shows the chosen
521
+ * option on the read-only ground, with no chevron; no click or key opens the list. The value is still submitted
522
+ * with the form (unlike `disabled`). Before JavaScript runs, the other options from `options` are disabled. */
523
+ readOnly?: boolean | undefined;
520
524
  }
521
525
  /** A compact filter for FilterBar (5.12): "Status All ▾", as wide as its words. */
522
526
  export interface FilterSelectProps extends Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "onChange" | "value" | "defaultValue" | "multiple" | "size" | "placeholder" | "required" | "children"> {
@@ -557,8 +561,16 @@ export interface SwitchProps {
557
561
  defaultChecked?: boolean | undefined;
558
562
  onChange?: ((checked: boolean) => void) | undefined;
559
563
  disabled?: boolean | undefined;
564
+ /** 5.19 (Chamber-OS 113): a locked value. The switch stays in the Tab order with `aria-readonly`, so its name,
565
+ * state and description are heard; click, Space and the row ignore it. Its colours stay (on reads as on). */
566
+ readOnly?: boolean | undefined;
567
+ /** 5.19: an icon at the end of the row, in fg-secondary and the field icon's size — `icon={<IconLock />}` or
568
+ * `icon="lock"` on a locked row. Hidden from screen readers; say what it means in the description. */
569
+ icon?: IconInput | undefined;
560
570
  /** Required when there is no visible label. */
561
571
  "aria-label"?: string | undefined;
572
+ /** 5.19: ids of more text that describes the switch (a lock note); merged with `description`'s. */
573
+ "aria-describedby"?: string | undefined;
562
574
  id?: string | undefined;
563
575
  className?: string | undefined;
564
576
  }
@@ -1164,6 +1176,10 @@ export interface TableCellProps extends Omit<React$1.TdHTMLAttributes<HTMLTableC
1164
1176
  export interface ActionBarProps {
1165
1177
  /** Buttons, right-aligned (the primary one last). */
1166
1178
  children?: React$1.ReactNode | undefined;
1179
+ /** 5.20 (Chamber-OS 116): buttons at the start edge — Cancel in a wizard — outside the `status` live region. When
1180
+ * the bar itself is 640px or wider they sit flush with its start edge and `children` at its end; in a narrower bar a
1181
+ * status line takes its own row and they sit before the actions. In the tab order they come before `children`. */
1182
+ start?: React$1.ReactNode | undefined;
1167
1183
  /** A short line such as "Total 107,000.00 THB · due Oct 22, 2026" or "Unsaved changes". It sits in a polite live
1168
1184
  * region that is always in the page, so a change is announced. */
1169
1185
  status?: React$1.ReactNode | undefined;
package/dist/index.d.ts CHANGED
@@ -517,6 +517,10 @@ export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.Sel
517
517
  /** Shown in fg-tertiary on the closed field until something is chosen; not listed as a choice. */
518
518
  placeholder?: string | undefined;
519
519
  icon?: IconInput | undefined;
520
+ /** 5.19 (Chamber-OS 114): a locked value. The field stays in the Tab order with `aria-readonly` and shows the chosen
521
+ * option on the read-only ground, with no chevron; no click or key opens the list. The value is still submitted
522
+ * with the form (unlike `disabled`). Before JavaScript runs, the other options from `options` are disabled. */
523
+ readOnly?: boolean | undefined;
520
524
  }
521
525
  /** A compact filter for FilterBar (5.12): "Status All ▾", as wide as its words. */
522
526
  export interface FilterSelectProps extends Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "onChange" | "value" | "defaultValue" | "multiple" | "size" | "placeholder" | "required" | "children"> {
@@ -557,8 +561,16 @@ export interface SwitchProps {
557
561
  defaultChecked?: boolean | undefined;
558
562
  onChange?: ((checked: boolean) => void) | undefined;
559
563
  disabled?: boolean | undefined;
564
+ /** 5.19 (Chamber-OS 113): a locked value. The switch stays in the Tab order with `aria-readonly`, so its name,
565
+ * state and description are heard; click, Space and the row ignore it. Its colours stay (on reads as on). */
566
+ readOnly?: boolean | undefined;
567
+ /** 5.19: an icon at the end of the row, in fg-secondary and the field icon's size — `icon={<IconLock />}` or
568
+ * `icon="lock"` on a locked row. Hidden from screen readers; say what it means in the description. */
569
+ icon?: IconInput | undefined;
560
570
  /** Required when there is no visible label. */
561
571
  "aria-label"?: string | undefined;
572
+ /** 5.19: ids of more text that describes the switch (a lock note); merged with `description`'s. */
573
+ "aria-describedby"?: string | undefined;
562
574
  id?: string | undefined;
563
575
  className?: string | undefined;
564
576
  }
@@ -1164,6 +1176,10 @@ export interface TableCellProps extends Omit<React$1.TdHTMLAttributes<HTMLTableC
1164
1176
  export interface ActionBarProps {
1165
1177
  /** Buttons, right-aligned (the primary one last). */
1166
1178
  children?: React$1.ReactNode | undefined;
1179
+ /** 5.20 (Chamber-OS 116): buttons at the start edge — Cancel in a wizard — outside the `status` live region. When
1180
+ * the bar itself is 640px or wider they sit flush with its start edge and `children` at its end; in a narrower bar a
1181
+ * status line takes its own row and they sit before the actions. In the tab order they come before `children`. */
1182
+ start?: React$1.ReactNode | undefined;
1167
1183
  /** A short line such as "Total 107,000.00 THB · due Oct 22, 2026" or "Unsaved changes". It sits in a polite live
1168
1184
  * region that is always in the page, so a change is announced. */
1169
1185
  status?: React$1.ReactNode | undefined;
package/dist/styles.css CHANGED
@@ -487,6 +487,10 @@ a.aura-icon-btn { text-decoration: none; }
487
487
  .aura-select__value.is-placeholder { color: var(--aura-fg-tertiary); }
488
488
  .aura-select__group { padding: var(--aura-space-2) var(--aura-space-2) var(--aura-space-1); font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--aura-fg-tertiary); }
489
489
  .aura-select.is-open .aura-select__chevron { transform: rotate(180deg); }
490
+ /* 5.19 (Chamber-OS 114): a read-only Select looks like a read-only TextField — the same ground, no chevron (it doesn't
491
+ * open), no pointer; the value keeps full contrast. */
492
+ .aura-select.is-readonly { background: var(--aura-bg-input-disabled); }
493
+ .aura-select.is-readonly .aura-input__control { cursor: default; padding-right: var(--aura-space-3); }
490
494
 
491
495
  .aura-radio-group > legend { margin-bottom: 6px; }
492
496
  .aura-radio-group__list { display: flex; flex-direction: column; gap: var(--aura-space-3); }
@@ -528,6 +532,10 @@ a.aura-icon-btn { text-decoration: none; }
528
532
  .aura-switch.is-on .aura-switch__thumb { transform: translateX(16px); background: var(--aura-button-primary-fg); }
529
533
  .aura-switch:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
530
534
  .aura-switch:disabled { cursor: not-allowed; }
535
+ /* 5.19 (Chamber-OS 113): a read-only switch keeps its colours (a locked "on" reads as on at full contrast) and stays
536
+ * focusable; only the pointer says it won't change. The row's icon (a lock) sits at its end, like a field's icon. */
537
+ .aura-switch-row.is-readonly, .aura-switch-row.is-readonly .aura-switch, .aura-switch-row.is-readonly .aura-choice__label { cursor: default; }
538
+ .aura-switch-row__icon { flex: none; margin-inline-start: auto; margin-top: 2px; width: var(--aura-icon-sm); height: var(--aura-icon-sm); color: var(--aura-fg-secondary); }
531
539
 
532
540
  /* ---------- Alerts & toasts ---------- */
533
541
  .aura-alert {
@@ -1414,7 +1422,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1414
1422
  padding-block: calc((var(--aura-touch-target) - 20px) / 2);
1415
1423
  }
1416
1424
  .aura-check--labelled .aura-check__input { top: calc((var(--aura-touch-target) - 20px) / 2 + 3px); }
1417
- .aura-switch-row:not(.is-disabled) { cursor: pointer; }
1425
+ .aura-switch-row:not(.is-disabled):not(.is-readonly) { cursor: pointer; }
1418
1426
  .aura-radio-group__list { gap: 0; }
1419
1427
  .aura-radio-group__list.is-horizontal { gap: 0 var(--aura-space-6); }
1420
1428
  }
@@ -1566,10 +1574,14 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1566
1574
  [data-density='compact'] {
1567
1575
  --aura-input-height: 36px; --aura-button-height: 36px; --aura-table-row-height: 40px;
1568
1576
  --aura-button-padding-x: var(--aura-space-4);
1577
+ /* 5.20 (Chamber-OS 115): a static Table's vertical cell padding, and its stacked cards', inherited from the nearest
1578
+ * density — a provider's wrapper, <html>, or the table's own wrap — so "comfortable" inside compact switches back. */
1579
+ --aura-tbl-cell-py: var(--aura-space-2); --aura-tbl-card-py: var(--aura-space-2);
1569
1580
  }
1570
1581
  [data-density='comfortable'] {
1571
1582
  --aura-input-height: 44px; --aura-button-height: 44px; --aura-table-row-height: 48px;
1572
1583
  --aura-button-padding-x: var(--aura-space-6);
1584
+ --aura-tbl-cell-py: var(--aura-space-3); --aura-tbl-card-py: var(--aura-space-4);
1573
1585
  }
1574
1586
  @media (pointer: coarse) {
1575
1587
  [data-density='compact'] { --aura-input-height: 44px; --aura-button-height: 44px; --aura-table-row-height: 48px; --aura-button-padding-x: var(--aura-space-6); }
@@ -1628,20 +1640,31 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1628
1640
 
1629
1641
  /* ---------- 4.19: ActionBar ---------- */
1630
1642
  /* Sticky, not fixed: it keeps its place at the end of its parent, so it never covers the last field. */
1631
- .aura-actionbar { position: sticky; z-index: var(--aura-z-bar); box-sizing: border-box; background: var(--aura-bg-surface); color: var(--aura-fg-primary); font-family: var(--font-sans); }
1643
+ .aura-actionbar { position: sticky; z-index: var(--aura-z-bar); box-sizing: border-box; width: 100%; container: aura-actionbar / inline-size; contain-intrinsic-inline-size: auto 20rem; /* 5.20: its own width decides its layout (a bar in a card or dialog); a size container can't size to its content, so in a shrink-to-fit parent it falls back to 20rem */ background: var(--aura-bg-surface); color: var(--aura-fg-primary); font-family: var(--font-sans); }
1632
1644
  .aura-actionbar--viewport {
1633
1645
  bottom: calc(var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px)));
1634
1646
  margin-top: var(--aura-space-6);
1635
1647
  border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); box-shadow: var(--aura-shadow-overlay);
1636
1648
  }
1637
1649
  .aura-actionbar--container { bottom: 0; border-top: 1px solid var(--aura-border-default); }
1638
- .aura-actionbar__inner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2) var(--aura-space-3); min-height: 60px; box-sizing: border-box; padding: var(--aura-space-2) var(--aura-space-3) var(--aura-space-2) var(--aura-space-4); }
1650
+ .aura-actionbar__inner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2) var(--aura-space-3); min-height: 60px; box-sizing: border-box; padding-block: var(--aura-space-2); padding-inline: var(--aura-space-4) var(--aura-space-3); }
1639
1651
  .aura-actionbar__status { flex: 1 1 8rem; min-width: 0; font-size: 14px; color: var(--aura-fg-secondary); font-variant-numeric: tabular-nums; }
1640
1652
  .aura-actionbar__status:empty { flex-basis: 0; }
1641
- .aura-actionbar__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--aura-space-2); margin-left: auto; }
1653
+ /* 5.20: an empty status leaves the row (it would still take a gap and push the start slot away from its edge) but
1654
+ * stays in the page as a live region, so the next status is announced. */
1655
+ .aura-actionbar__inner.has-start > .aura-actionbar__status:empty { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
1656
+ .aura-actionbar__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--aura-space-2); margin-inline-start: auto; }
1642
1657
  /* Bulk bar with nothing selected: out of sight and out of the tab order, its live region still in the page. */
1643
1658
  .aura-actionbar.is-idle { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; box-shadow: none; }
1644
- .aura-actionbar--container > .aura-actionbar__inner { padding-left: 0; padding-right: 0; }
1659
+ .aura-actionbar--container > .aura-actionbar__inner { padding-inline: 0; }
1660
+ /* 5.20 (Chamber-OS 116): the start slot. After the status in the markup, so Tab reaches it before the actions (the
1661
+ * status is a live region, not focusable, so only reading order differs); when the bar is 640px or wider it moves to
1662
+ * the start edge, the status (if any) between. Narrower bars — a phone, a card, a dialog — put a status line on its own
1663
+ * row, so the start slot and the actions stay together on the next. */
1664
+ .aura-actionbar__start { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2); }
1665
+ .aura-actionbar__start:empty { display: none; }
1666
+ @container aura-actionbar (min-width: 640px) { .aura-actionbar__start { order: -1; } }
1667
+ @container aura-actionbar (max-width: 639.98px) { .aura-actionbar__inner.has-start > .aura-actionbar__status:not(:empty) { flex-basis: 100%; } }
1645
1668
 
1646
1669
  /* ---------- 4.20: Separator ---------- */
1647
1670
  .aura-separator { flex: none; align-self: stretch; height: 1px; margin: var(--aura-separator-space, 0) 0; background: var(--aura-border-default); border: 0; }
@@ -1655,7 +1678,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1655
1678
  }
1656
1679
  .aura-tbl { width: 100%; border-collapse: collapse; border-spacing: 0; font-size: 13px; line-height: 20px; }
1657
1680
  .aura-tbl__caption { caption-side: top; padding: var(--aura-space-4) var(--aura-space-6) 0; text-align: left; font-size: 14px; font-weight: 600; }
1658
- .aura-tbl__th, .aura-tbl__td { padding: var(--aura-space-3) var(--aura-space-4); text-align: left; vertical-align: top; overflow-wrap: anywhere; }
1681
+ .aura-tbl__th, .aura-tbl__td { padding: var(--aura-tbl-cell-py, var(--aura-space-3)) var(--aura-space-4); text-align: left; vertical-align: top; overflow-wrap: anywhere; }
1659
1682
  .aura-tbl__th:first-child, .aura-tbl__td:first-child { padding-left: var(--aura-space-6); }
1660
1683
  .aura-tbl__th:last-child, .aura-tbl__td:last-child { padding-right: var(--aura-space-6); }
1661
1684
  .aura-tbl__head .aura-tbl__th {
@@ -1670,7 +1693,6 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1670
1693
  .aura-tbl .is-end { text-align: right; }
1671
1694
  .aura-tbl .is-center { text-align: center; }
1672
1695
  .aura-tbl .is-numeric { font-variant-numeric: tabular-nums; white-space: nowrap; }
1673
- .aura-tbl-wrap[data-density='compact'] .aura-tbl__th, .aura-tbl-wrap[data-density='compact'] .aura-tbl__td { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1674
1696
  .aura-tbl-wrap:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 2px; }
1675
1697
  /* 5.13 (Chamber-OS 81): align="middle" centres body and footer cells on their row; bordered={false} drops the frame and
1676
1698
  * the outer cells' side padding, so the table lines up with what's around it (a Card's heading). */
@@ -1687,8 +1709,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1687
1709
  .aura-tbl--stack-md, .aura-tbl--stack-md > .aura-tbl__body, .aura-tbl--stack-md > .aura-tbl__foot, .aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row, .aura-tbl--stack-md > * > .aura-tbl__row > .aura-tbl__th, .aura-tbl--stack-md > * > .aura-tbl__row > .aura-tbl__td { display: block; }
1688
1710
  .aura-tbl--stack-md > .aura-tbl__head { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
1689
1711
  .aura-tbl--stack-md > .aura-tbl__caption { display: block; padding: var(--aura-space-4) var(--aura-space-4) 0; }
1690
- .aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row { padding: var(--aura-space-3) var(--aura-space-4); }
1691
- .aura-tbl-wrap[data-density='compact'] > .aura-tbl--stack-md > * > .aura-tbl__row { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1712
+ .aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row { padding: var(--aura-tbl-cell-py, var(--aura-space-3)) var(--aura-space-4); }
1692
1713
  .aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row + .aura-tbl__row, .aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row:first-child { border-top: 1px solid var(--aura-border-default); }
1693
1714
  .aura-tbl--stack-md > * > .aura-tbl__row > .aura-tbl__th, .aura-tbl--stack-md > * > .aura-tbl__row > .aura-tbl__td { padding: 0; border-top: 0; background: none; text-align: start; white-space: normal; }
1694
1715
  .aura-tbl--stack-md > * > .aura-tbl__row > * + * { margin-top: var(--aura-space-2); }
@@ -1710,8 +1731,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1710
1731
  .aura-tbl--stack-sm, .aura-tbl--stack-sm > .aura-tbl__body, .aura-tbl--stack-sm > .aura-tbl__foot, .aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row, .aura-tbl--stack-sm > * > .aura-tbl__row > .aura-tbl__th, .aura-tbl--stack-sm > * > .aura-tbl__row > .aura-tbl__td { display: block; }
1711
1732
  .aura-tbl--stack-sm > .aura-tbl__head { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
1712
1733
  .aura-tbl--stack-sm > .aura-tbl__caption { display: block; padding: var(--aura-space-4) var(--aura-space-4) 0; }
1713
- .aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row { padding: var(--aura-space-3) var(--aura-space-4); }
1714
- .aura-tbl-wrap[data-density='compact'] > .aura-tbl--stack-sm > * > .aura-tbl__row { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1734
+ .aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row { padding: var(--aura-tbl-cell-py, var(--aura-space-3)) var(--aura-space-4); }
1715
1735
  .aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row + .aura-tbl__row, .aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row:first-child { border-top: 1px solid var(--aura-border-default); }
1716
1736
  .aura-tbl--stack-sm > * > .aura-tbl__row > .aura-tbl__th, .aura-tbl--stack-sm > * > .aura-tbl__row > .aura-tbl__td { padding: 0; border-top: 0; background: none; text-align: start; white-space: normal; }
1717
1737
  .aura-tbl--stack-sm > * > .aura-tbl__row > * + * { margin-top: var(--aura-space-2); }
@@ -1737,24 +1757,22 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1737
1757
  .aura-tbl-cards--md > .aura-tbl-wrap { border: 0; border-radius: 0; background: transparent; overflow: visible; }
1738
1758
  .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__caption { padding: 0 0 var(--aura-space-3); }
1739
1759
  .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row, .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row {
1740
- margin-bottom: var(--aura-space-2); padding: var(--aura-space-4);
1760
+ margin-bottom: var(--aura-space-2); padding: var(--aura-tbl-card-py, var(--aura-space-4)) var(--aura-space-4);
1741
1761
  border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); background: var(--aura-bg-surface);
1742
1762
  }
1743
1763
  .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row { background: var(--aura-bg-canvas); }
1744
1764
  .aura-tbl--cards.aura-tbl--stack-md > :last-child > .aura-tbl__row:last-child { margin-bottom: 0; }
1745
- .aura-tbl-wrap[data-density='compact'] > .aura-tbl--cards.aura-tbl--stack-md > * > .aura-tbl__row { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1746
1765
  .aura-tbl-wrap.is-flush > .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__caption { padding: 0 0 var(--aura-space-3); }
1747
1766
  }
1748
1767
  @container aura-tbl (width < 640px) {
1749
1768
  .aura-tbl-cards--sm > .aura-tbl-wrap { border: 0; border-radius: 0; background: transparent; overflow: visible; }
1750
1769
  .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__caption { padding: 0 0 var(--aura-space-3); }
1751
1770
  .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row, .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row {
1752
- margin-bottom: var(--aura-space-2); padding: var(--aura-space-4);
1771
+ margin-bottom: var(--aura-space-2); padding: var(--aura-tbl-card-py, var(--aura-space-4)) var(--aura-space-4);
1753
1772
  border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); background: var(--aura-bg-surface);
1754
1773
  }
1755
1774
  .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row { background: var(--aura-bg-canvas); }
1756
1775
  .aura-tbl--cards.aura-tbl--stack-sm > :last-child > .aura-tbl__row:last-child { margin-bottom: 0; }
1757
- .aura-tbl-wrap[data-density='compact'] > .aura-tbl--cards.aura-tbl--stack-sm > * > .aura-tbl__row { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1758
1776
  .aura-tbl-wrap.is-flush > .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__caption { padding: 0 0 var(--aura-space-3); }
1759
1777
  }
1760
1778
 
@@ -489,6 +489,10 @@ a.aura-icon-btn { text-decoration: none; }
489
489
  .aura-select__value.is-placeholder { color: var(--aura-fg-tertiary); }
490
490
  .aura-select__group { padding: var(--aura-space-2) var(--aura-space-2) var(--aura-space-1); font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--aura-fg-tertiary); }
491
491
  .aura-select.is-open .aura-select__chevron { transform: rotate(180deg); }
492
+ /* 5.19 (Chamber-OS 114): a read-only Select looks like a read-only TextField — the same ground, no chevron (it doesn't
493
+ * open), no pointer; the value keeps full contrast. */
494
+ .aura-select.is-readonly { background: var(--aura-bg-input-disabled); }
495
+ .aura-select.is-readonly .aura-input__control { cursor: default; padding-right: var(--aura-space-3); }
492
496
 
493
497
  .aura-radio-group > legend { margin-bottom: 6px; }
494
498
  .aura-radio-group__list { display: flex; flex-direction: column; gap: var(--aura-space-3); }
@@ -530,6 +534,10 @@ a.aura-icon-btn { text-decoration: none; }
530
534
  .aura-switch.is-on .aura-switch__thumb { transform: translateX(16px); background: var(--aura-button-primary-fg); }
531
535
  .aura-switch:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
532
536
  .aura-switch:disabled { cursor: not-allowed; }
537
+ /* 5.19 (Chamber-OS 113): a read-only switch keeps its colours (a locked "on" reads as on at full contrast) and stays
538
+ * focusable; only the pointer says it won't change. The row's icon (a lock) sits at its end, like a field's icon. */
539
+ .aura-switch-row.is-readonly, .aura-switch-row.is-readonly .aura-switch, .aura-switch-row.is-readonly .aura-choice__label { cursor: default; }
540
+ .aura-switch-row__icon { flex: none; margin-inline-start: auto; margin-top: 2px; width: var(--aura-icon-sm); height: var(--aura-icon-sm); color: var(--aura-fg-secondary); }
533
541
 
534
542
  /* ---------- Alerts & toasts ---------- */
535
543
  .aura-alert {
@@ -1416,7 +1424,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1416
1424
  padding-block: calc((var(--aura-touch-target) - 20px) / 2);
1417
1425
  }
1418
1426
  .aura-check--labelled .aura-check__input { top: calc((var(--aura-touch-target) - 20px) / 2 + 3px); }
1419
- .aura-switch-row:not(.is-disabled) { cursor: pointer; }
1427
+ .aura-switch-row:not(.is-disabled):not(.is-readonly) { cursor: pointer; }
1420
1428
  .aura-radio-group__list { gap: 0; }
1421
1429
  .aura-radio-group__list.is-horizontal { gap: 0 var(--aura-space-6); }
1422
1430
  }
@@ -1568,10 +1576,14 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1568
1576
  [data-density='compact'] {
1569
1577
  --aura-input-height: 36px; --aura-button-height: 36px; --aura-table-row-height: 40px;
1570
1578
  --aura-button-padding-x: var(--aura-space-4);
1579
+ /* 5.20 (Chamber-OS 115): a static Table's vertical cell padding, and its stacked cards', inherited from the nearest
1580
+ * density — a provider's wrapper, <html>, or the table's own wrap — so "comfortable" inside compact switches back. */
1581
+ --aura-tbl-cell-py: var(--aura-space-2); --aura-tbl-card-py: var(--aura-space-2);
1571
1582
  }
1572
1583
  [data-density='comfortable'] {
1573
1584
  --aura-input-height: 44px; --aura-button-height: 44px; --aura-table-row-height: 48px;
1574
1585
  --aura-button-padding-x: var(--aura-space-6);
1586
+ --aura-tbl-cell-py: var(--aura-space-3); --aura-tbl-card-py: var(--aura-space-4);
1575
1587
  }
1576
1588
  @media (pointer: coarse) {
1577
1589
  [data-density='compact'] { --aura-input-height: 44px; --aura-button-height: 44px; --aura-table-row-height: 48px; --aura-button-padding-x: var(--aura-space-6); }
@@ -1630,20 +1642,31 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1630
1642
 
1631
1643
  /* ---------- 4.19: ActionBar ---------- */
1632
1644
  /* Sticky, not fixed: it keeps its place at the end of its parent, so it never covers the last field. */
1633
- .aura-actionbar { position: sticky; z-index: var(--aura-z-bar); box-sizing: border-box; background: var(--aura-bg-surface); color: var(--aura-fg-primary); font-family: var(--font-sans); }
1645
+ .aura-actionbar { position: sticky; z-index: var(--aura-z-bar); box-sizing: border-box; width: 100%; container: aura-actionbar / inline-size; contain-intrinsic-inline-size: auto 20rem; /* 5.20: its own width decides its layout (a bar in a card or dialog); a size container can't size to its content, so in a shrink-to-fit parent it falls back to 20rem */ background: var(--aura-bg-surface); color: var(--aura-fg-primary); font-family: var(--font-sans); }
1634
1646
  .aura-actionbar--viewport {
1635
1647
  bottom: calc(var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px)));
1636
1648
  margin-top: var(--aura-space-6);
1637
1649
  border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); box-shadow: var(--aura-shadow-overlay);
1638
1650
  }
1639
1651
  .aura-actionbar--container { bottom: 0; border-top: 1px solid var(--aura-border-default); }
1640
- .aura-actionbar__inner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2) var(--aura-space-3); min-height: 60px; box-sizing: border-box; padding: var(--aura-space-2) var(--aura-space-3) var(--aura-space-2) var(--aura-space-4); }
1652
+ .aura-actionbar__inner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2) var(--aura-space-3); min-height: 60px; box-sizing: border-box; padding-block: var(--aura-space-2); padding-inline: var(--aura-space-4) var(--aura-space-3); }
1641
1653
  .aura-actionbar__status { flex: 1 1 8rem; min-width: 0; font-size: 14px; color: var(--aura-fg-secondary); font-variant-numeric: tabular-nums; }
1642
1654
  .aura-actionbar__status:empty { flex-basis: 0; }
1643
- .aura-actionbar__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--aura-space-2); margin-left: auto; }
1655
+ /* 5.20: an empty status leaves the row (it would still take a gap and push the start slot away from its edge) but
1656
+ * stays in the page as a live region, so the next status is announced. */
1657
+ .aura-actionbar__inner.has-start > .aura-actionbar__status:empty { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
1658
+ .aura-actionbar__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--aura-space-2); margin-inline-start: auto; }
1644
1659
  /* Bulk bar with nothing selected: out of sight and out of the tab order, its live region still in the page. */
1645
1660
  .aura-actionbar.is-idle { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; box-shadow: none; }
1646
- .aura-actionbar--container > .aura-actionbar__inner { padding-left: 0; padding-right: 0; }
1661
+ .aura-actionbar--container > .aura-actionbar__inner { padding-inline: 0; }
1662
+ /* 5.20 (Chamber-OS 116): the start slot. After the status in the markup, so Tab reaches it before the actions (the
1663
+ * status is a live region, not focusable, so only reading order differs); when the bar is 640px or wider it moves to
1664
+ * the start edge, the status (if any) between. Narrower bars — a phone, a card, a dialog — put a status line on its own
1665
+ * row, so the start slot and the actions stay together on the next. */
1666
+ .aura-actionbar__start { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-2); }
1667
+ .aura-actionbar__start:empty { display: none; }
1668
+ @container aura-actionbar (min-width: 640px) { .aura-actionbar__start { order: -1; } }
1669
+ @container aura-actionbar (max-width: 639.98px) { .aura-actionbar__inner.has-start > .aura-actionbar__status:not(:empty) { flex-basis: 100%; } }
1647
1670
 
1648
1671
  /* ---------- 4.20: Separator ---------- */
1649
1672
  .aura-separator { flex: none; align-self: stretch; height: 1px; margin: var(--aura-separator-space, 0) 0; background: var(--aura-border-default); border: 0; }
@@ -1657,7 +1680,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1657
1680
  }
1658
1681
  .aura-tbl { width: 100%; border-collapse: collapse; border-spacing: 0; font-size: 13px; line-height: 20px; }
1659
1682
  .aura-tbl__caption { caption-side: top; padding: var(--aura-space-4) var(--aura-space-6) 0; text-align: left; font-size: 14px; font-weight: 600; }
1660
- .aura-tbl__th, .aura-tbl__td { padding: var(--aura-space-3) var(--aura-space-4); text-align: left; vertical-align: top; overflow-wrap: anywhere; }
1683
+ .aura-tbl__th, .aura-tbl__td { padding: var(--aura-tbl-cell-py, var(--aura-space-3)) var(--aura-space-4); text-align: left; vertical-align: top; overflow-wrap: anywhere; }
1661
1684
  .aura-tbl__th:first-child, .aura-tbl__td:first-child { padding-left: var(--aura-space-6); }
1662
1685
  .aura-tbl__th:last-child, .aura-tbl__td:last-child { padding-right: var(--aura-space-6); }
1663
1686
  .aura-tbl__head .aura-tbl__th {
@@ -1672,7 +1695,6 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1672
1695
  .aura-tbl .is-end { text-align: right; }
1673
1696
  .aura-tbl .is-center { text-align: center; }
1674
1697
  .aura-tbl .is-numeric { font-variant-numeric: tabular-nums; white-space: nowrap; }
1675
- .aura-tbl-wrap[data-density='compact'] .aura-tbl__th, .aura-tbl-wrap[data-density='compact'] .aura-tbl__td { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1676
1698
  .aura-tbl-wrap:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: 2px; }
1677
1699
  /* 5.13 (Chamber-OS 81): align="middle" centres body and footer cells on their row; bordered={false} drops the frame and
1678
1700
  * the outer cells' side padding, so the table lines up with what's around it (a Card's heading). */
@@ -1689,8 +1711,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1689
1711
  .aura-tbl--stack-md, .aura-tbl--stack-md > .aura-tbl__body, .aura-tbl--stack-md > .aura-tbl__foot, .aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row, .aura-tbl--stack-md > * > .aura-tbl__row > .aura-tbl__th, .aura-tbl--stack-md > * > .aura-tbl__row > .aura-tbl__td { display: block; }
1690
1712
  .aura-tbl--stack-md > .aura-tbl__head { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
1691
1713
  .aura-tbl--stack-md > .aura-tbl__caption { display: block; padding: var(--aura-space-4) var(--aura-space-4) 0; }
1692
- .aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row { padding: var(--aura-space-3) var(--aura-space-4); }
1693
- .aura-tbl-wrap[data-density='compact'] > .aura-tbl--stack-md > * > .aura-tbl__row { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1714
+ .aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row { padding: var(--aura-tbl-cell-py, var(--aura-space-3)) var(--aura-space-4); }
1694
1715
  .aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row + .aura-tbl__row, .aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row:first-child { border-top: 1px solid var(--aura-border-default); }
1695
1716
  .aura-tbl--stack-md > * > .aura-tbl__row > .aura-tbl__th, .aura-tbl--stack-md > * > .aura-tbl__row > .aura-tbl__td { padding: 0; border-top: 0; background: none; text-align: start; white-space: normal; }
1696
1717
  .aura-tbl--stack-md > * > .aura-tbl__row > * + * { margin-top: var(--aura-space-2); }
@@ -1712,8 +1733,7 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1712
1733
  .aura-tbl--stack-sm, .aura-tbl--stack-sm > .aura-tbl__body, .aura-tbl--stack-sm > .aura-tbl__foot, .aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row, .aura-tbl--stack-sm > * > .aura-tbl__row > .aura-tbl__th, .aura-tbl--stack-sm > * > .aura-tbl__row > .aura-tbl__td { display: block; }
1713
1734
  .aura-tbl--stack-sm > .aura-tbl__head { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
1714
1735
  .aura-tbl--stack-sm > .aura-tbl__caption { display: block; padding: var(--aura-space-4) var(--aura-space-4) 0; }
1715
- .aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row { padding: var(--aura-space-3) var(--aura-space-4); }
1716
- .aura-tbl-wrap[data-density='compact'] > .aura-tbl--stack-sm > * > .aura-tbl__row { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1736
+ .aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row, .aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row { padding: var(--aura-tbl-cell-py, var(--aura-space-3)) var(--aura-space-4); }
1717
1737
  .aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row + .aura-tbl__row, .aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row:first-child { border-top: 1px solid var(--aura-border-default); }
1718
1738
  .aura-tbl--stack-sm > * > .aura-tbl__row > .aura-tbl__th, .aura-tbl--stack-sm > * > .aura-tbl__row > .aura-tbl__td { padding: 0; border-top: 0; background: none; text-align: start; white-space: normal; }
1719
1739
  .aura-tbl--stack-sm > * > .aura-tbl__row > * + * { margin-top: var(--aura-space-2); }
@@ -1739,24 +1759,22 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
1739
1759
  .aura-tbl-cards--md > .aura-tbl-wrap { border: 0; border-radius: 0; background: transparent; overflow: visible; }
1740
1760
  .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__caption { padding: 0 0 var(--aura-space-3); }
1741
1761
  .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__body > .aura-tbl__row, .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row {
1742
- margin-bottom: var(--aura-space-2); padding: var(--aura-space-4);
1762
+ margin-bottom: var(--aura-space-2); padding: var(--aura-tbl-card-py, var(--aura-space-4)) var(--aura-space-4);
1743
1763
  border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); background: var(--aura-bg-surface);
1744
1764
  }
1745
1765
  .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__foot > .aura-tbl__row { background: var(--aura-bg-canvas); }
1746
1766
  .aura-tbl--cards.aura-tbl--stack-md > :last-child > .aura-tbl__row:last-child { margin-bottom: 0; }
1747
- .aura-tbl-wrap[data-density='compact'] > .aura-tbl--cards.aura-tbl--stack-md > * > .aura-tbl__row { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1748
1767
  .aura-tbl-wrap.is-flush > .aura-tbl--cards.aura-tbl--stack-md > .aura-tbl__caption { padding: 0 0 var(--aura-space-3); }
1749
1768
  }
1750
1769
  @container aura-tbl (width < 640px) {
1751
1770
  .aura-tbl-cards--sm > .aura-tbl-wrap { border: 0; border-radius: 0; background: transparent; overflow: visible; }
1752
1771
  .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__caption { padding: 0 0 var(--aura-space-3); }
1753
1772
  .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__body > .aura-tbl__row, .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row {
1754
- margin-bottom: var(--aura-space-2); padding: var(--aura-space-4);
1773
+ margin-bottom: var(--aura-space-2); padding: var(--aura-tbl-card-py, var(--aura-space-4)) var(--aura-space-4);
1755
1774
  border: 1px solid var(--aura-border-default); border-radius: var(--aura-radius-lg); background: var(--aura-bg-surface);
1756
1775
  }
1757
1776
  .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__foot > .aura-tbl__row { background: var(--aura-bg-canvas); }
1758
1777
  .aura-tbl--cards.aura-tbl--stack-sm > :last-child > .aura-tbl__row:last-child { margin-bottom: 0; }
1759
- .aura-tbl-wrap[data-density='compact'] > .aura-tbl--cards.aura-tbl--stack-sm > * > .aura-tbl__row { padding-top: var(--aura-space-2); padding-bottom: var(--aura-space-2); }
1760
1778
  .aura-tbl-wrap.is-flush > .aura-tbl--cards.aura-tbl--stack-sm > .aura-tbl__caption { padding: 0 0 var(--aura-space-3); }
1761
1779
  }
1762
1780
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jirawatpyk/aura-react",
3
- "version": "5.18.0",
3
+ "version": "5.20.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",