@jirawatpyk/aura-react 5.29.0 → 5.31.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.
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
- import { FIELD_KEYS, Field, describedBy } from "./Field.js";
4
- import { omit, uid } from "./internal.js";
3
+ import { FIELD_KEYS, Field, joinDescribedBy } from "./Field.js";
4
+ import { cx, omit, touchClass, uid } from "./internal.js";
5
5
  const Textarea = React.forwardRef(function Textarea2(props, ref) {
6
6
  const auto = uid(), id = props.id || auto;
7
7
  const rest = omit(props, FIELD_KEYS);
@@ -14,8 +14,9 @@ const Textarea = React.forwardRef(function Textarea2(props, ref) {
14
14
  error: props.error,
15
15
  required: props.required,
16
16
  optional: props.optional,
17
+ labelAddon: props.labelAddon,
17
18
  disabled: props.disabled,
18
- className: props.className
19
+ className: cx(touchClass(props.touchHeight), props.className)
19
20
  },
20
21
  /* @__PURE__ */ React.createElement(
21
22
  "textarea",
@@ -28,7 +29,7 @@ const Textarea = React.forwardRef(function Textarea2(props, ref) {
28
29
  placeholder: props.placeholder,
29
30
  required: props.required,
30
31
  "aria-invalid": props.error ? true : rest["aria-invalid"],
31
- "aria-describedby": [describedBy(id, props), rest["aria-describedby"]].filter(Boolean).join(" ") || void 0
32
+ "aria-describedby": joinDescribedBy(id, props, rest["aria-describedby"])
32
33
  }
33
34
  )
34
35
  );
@@ -8,6 +8,9 @@ function omit(src, keys) {
8
8
  out[k] = src[k];
9
9
  return out;
10
10
  }
11
+ function touchClass(t) {
12
+ return t === "always" ? "is-touch-always" : !!t && "is-touch";
13
+ }
11
14
  const TONES = ["neutral", "accent", "success", "warning", "danger"];
12
15
  function tone(t) {
13
16
  return TONES.indexOf(t) >= 0 ? t : "neutral";
@@ -29,5 +32,6 @@ export {
29
32
  cx,
30
33
  devWarnOnce,
31
34
  omit,
32
- tone
35
+ tone,
36
+ touchClass
33
37
  };
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
- import { cx, omit, tone, devWarnOnce } from "./classes.js";
3
+ import { cx, omit, tone, devWarnOnce, touchClass } from "./classes.js";
4
4
  function useMaybeControlled(value, initial, onChange) {
5
5
  const s = React.useState(initial);
6
6
  const wasSet = React.useState(value !== void 0);
@@ -89,6 +89,7 @@ export {
89
89
  omit,
90
90
  plainClick,
91
91
  tone,
92
+ touchClass,
92
93
  trapTab,
93
94
  uid,
94
95
  useIsoLayoutEffect,
package/dist/esm/theme.js CHANGED
@@ -162,6 +162,13 @@ function createTheme(opts) {
162
162
  D["accent-lime"] = s[200];
163
163
  D["mesh-to"] = s[200];
164
164
  }
165
+ function onFill(bg) {
166
+ return contrast(ZINC[0], bg) >= contrast(INK, bg) ? ZINC[0] : INK;
167
+ }
168
+ L["control-checked-bg"] = L["accent-violet"];
169
+ L["control-checked-fg"] = onFill(L["control-checked-bg"]);
170
+ D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
171
+ D["control-checked-fg"] = onFill(D["control-checked-bg"]);
165
172
  const shadowLight = INK, shadowDark = D["accent-violet"];
166
173
  if (o.primary === "brand") {
167
174
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -244,6 +251,30 @@ function createTheme(opts) {
244
251
  );
245
252
  }
246
253
  if (s) check("light", "ink", "accent-lime (signal)", 4.5, INK, s[200]);
254
+ [
255
+ [
256
+ "light",
257
+ L,
258
+ [
259
+ ["bg-surface", ZINC[0]],
260
+ ["bg-canvas", ZINC[50]]
261
+ ]
262
+ ],
263
+ [
264
+ "dark",
265
+ D,
266
+ [
267
+ ["bg-surface", ZINC[900]],
268
+ ["bg-canvas", ZINC[950]]
269
+ ]
270
+ ]
271
+ ].forEach(function(g) {
272
+ const t = g[0], m = g[1];
273
+ g[2].forEach(function(gr) {
274
+ check(t, "control-checked-bg", gr[0], 3, m["control-checked-bg"], gr[1]);
275
+ });
276
+ check(t, "control-checked-fg", "control-checked-bg", 4.5, m["control-checked-fg"], m["control-checked-bg"]);
277
+ });
247
278
  function css(selector) {
248
279
  if (selector !== void 0 && /[<{};@\r\n]|\/\*|\*\//.test(selector))
249
280
  throw new Error('createTheme: "' + selector + '" is not a plain CSS selector');
package/dist/index.d.cts CHANGED
@@ -335,6 +335,9 @@ export interface CheckboxProps extends Omit<React$1.InputHTMLAttributes<HTMLInpu
335
335
  x: number;
336
336
  y: number;
337
337
  } | undefined;
338
+ /** A labelled checkbox's row is 44px (5.30, Chamber-OS 135), the one-line label centred: `true` below 640px or on a
339
+ * coarse pointer, `'always'` at every width. A bare box (no visible label) uses `hitArea` instead. */
340
+ touchHeight?: TouchHeight | undefined;
338
341
  }
339
342
  /** 32px round button holding one icon. */
340
343
  export interface IconButtonProps extends React$1.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -393,6 +396,8 @@ export interface MenuProps {
393
396
  * accessible description, read once when the menu opens. */
394
397
  header?: React$1.ReactNode | undefined;
395
398
  }
399
+ /** `true`: 44px below 640px or on a coarse pointer; `'always'`: 44px at every width (5.30). */
400
+ export type TouchHeight = boolean | "always";
396
401
  export interface FieldProps {
397
402
  /** Visible label; also the accessible name. */
398
403
  label: string;
@@ -410,6 +415,17 @@ export interface TextFieldProps extends FieldProps, Omit<React$1.InputHTMLAttrib
410
415
  icon?: IconInput | undefined;
411
416
  /** Trailing unit, e.g. "THB". */
412
417
  suffix?: React$1.ReactNode | undefined;
418
+ /** 5.31 (Chamber-OS addendum 41): a node between the label and the box — e.g. the phrase to type, with a copy
419
+ * button, on a typed confirmation. The hint and error stay below. Its box's id (`{id}-addon`) comes first in the
420
+ * input's `aria-describedby`, so its text is read after the label; keep any button names in it short. */
421
+ labelAddon?: React$1.ReactNode | undefined;
422
+ /** `false` keeps the addon out of `aria-describedby` — for one that isn't a description (a "Forgot password?"
423
+ * link), or to name only part of it: give that part an id and pass it in `aria-describedby`. Default `true`. */
424
+ labelAddonDescribes?: boolean | undefined;
425
+ /** 44px box (5.30, Chamber-OS 135): `true` below 640px or on a coarse pointer, as Button's `touchHeight`; `'always'`
426
+ * at every width, also with a mouse in compact density — for a form whose spec asks for 44px targets everywhere.
427
+ * Default: the density's input height. */
428
+ touchHeight?: TouchHeight | undefined;
413
429
  }
414
430
  /** A password input with a show/hide button. All TextField props; `ref` reaches the input (react-hook-form `register`). */
415
431
  export interface PasswordFieldProps extends Omit<TextFieldProps, "type" | "suffix"> {
@@ -531,6 +547,16 @@ export interface CommandProps {
531
547
  className?: string | undefined;
532
548
  }
533
549
  export interface TextareaProps extends FieldProps, Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElement>, "required"> {
550
+ /** 5.31 (Chamber-OS addendum 41): a node between the label and the box — e.g. the phrase to type, with a copy
551
+ * button, on a typed confirmation. The hint and error stay below. Its box's id (`{id}-addon`) comes first in the
552
+ * input's `aria-describedby`, so its text is read after the label; keep any button names in it short. */
553
+ labelAddon?: React$1.ReactNode | undefined;
554
+ /** `false` keeps the addon out of `aria-describedby` — for one that isn't a description (a "Forgot password?"
555
+ * link), or to name only part of it: give that part an id and pass it in `aria-describedby`. Default `true`. */
556
+ labelAddonDescribes?: boolean | undefined;
557
+ /** Accepted like the other fields' (5.30, Chamber-OS 135), so a form can pass it to every field. A Textarea is
558
+ * always at least 96px tall, well over the 44px target, so it changes nothing you can see. */
559
+ touchHeight?: TouchHeight | undefined;
534
560
  }
535
561
  export type SelectOption = string | {
536
562
  value: string;
@@ -552,6 +578,10 @@ export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.Sel
552
578
  * option on the read-only ground, with no chevron; no click or key opens the list. The value is still submitted
553
579
  * with the form (unlike `disabled`). Before JavaScript runs, the other options from `options` are disabled. */
554
580
  readOnly?: boolean | undefined;
581
+ /** 44px box (5.30, Chamber-OS 135): `true` below 640px or on a coarse pointer, as Button's `touchHeight`; `'always'`
582
+ * at every width, also with a mouse in compact density — for a form whose spec asks for 44px targets everywhere.
583
+ * Default: the density's input height. */
584
+ touchHeight?: TouchHeight | undefined;
555
585
  }
556
586
  /** A compact filter for FilterBar (5.12): "Status All ▾", as wide as its words. */
557
587
  export interface FilterSelectProps extends Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "onChange" | "value" | "defaultValue" | "multiple" | "size" | "placeholder" | "required" | "children"> {
@@ -586,6 +616,9 @@ export interface RadioGroupProps extends FieldProps {
586
616
  disabled?: boolean | undefined;
587
617
  id?: string | undefined;
588
618
  className?: string | undefined;
619
+ /** 44px rows (5.30, Chamber-OS 135), the one-line label centred: `true` below 640px or on a coarse pointer (touch
620
+ * screens already get it), `'always'` at every width, also with a mouse. Default: as tall as the control and label. */
621
+ touchHeight?: TouchHeight | undefined;
589
622
  }
590
623
  export interface SwitchProps {
591
624
  label?: string | undefined;
@@ -1615,6 +1648,9 @@ export interface FieldComponentProps extends Omit<FieldPropsPublic, "label"> {
1615
1648
  /** The control's id: the label points at it, and the hint/error ids derive from it. */
1616
1649
  id?: string | undefined;
1617
1650
  children?: React$1.ReactNode | undefined;
1651
+ /** 5.31 (Chamber-OS addendum 41): a node between the label and the control, in a `.aura-field__addon` box with the
1652
+ * id `{id}-addon`. TextField and Textarea add that id to the input's `aria-describedby`; a custom control should too. */
1653
+ labelAddon?: React$1.ReactNode | undefined;
1618
1654
  /** Render the label as another element (e.g. `span` for a group of controls) with this id. */
1619
1655
  labelAs?: string | undefined;
1620
1656
  labelId?: string | undefined;
package/dist/index.d.ts CHANGED
@@ -335,6 +335,9 @@ export interface CheckboxProps extends Omit<React$1.InputHTMLAttributes<HTMLInpu
335
335
  x: number;
336
336
  y: number;
337
337
  } | undefined;
338
+ /** A labelled checkbox's row is 44px (5.30, Chamber-OS 135), the one-line label centred: `true` below 640px or on a
339
+ * coarse pointer, `'always'` at every width. A bare box (no visible label) uses `hitArea` instead. */
340
+ touchHeight?: TouchHeight | undefined;
338
341
  }
339
342
  /** 32px round button holding one icon. */
340
343
  export interface IconButtonProps extends React$1.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -393,6 +396,8 @@ export interface MenuProps {
393
396
  * accessible description, read once when the menu opens. */
394
397
  header?: React$1.ReactNode | undefined;
395
398
  }
399
+ /** `true`: 44px below 640px or on a coarse pointer; `'always'`: 44px at every width (5.30). */
400
+ export type TouchHeight = boolean | "always";
396
401
  export interface FieldProps {
397
402
  /** Visible label; also the accessible name. */
398
403
  label: string;
@@ -410,6 +415,17 @@ export interface TextFieldProps extends FieldProps, Omit<React$1.InputHTMLAttrib
410
415
  icon?: IconInput | undefined;
411
416
  /** Trailing unit, e.g. "THB". */
412
417
  suffix?: React$1.ReactNode | undefined;
418
+ /** 5.31 (Chamber-OS addendum 41): a node between the label and the box — e.g. the phrase to type, with a copy
419
+ * button, on a typed confirmation. The hint and error stay below. Its box's id (`{id}-addon`) comes first in the
420
+ * input's `aria-describedby`, so its text is read after the label; keep any button names in it short. */
421
+ labelAddon?: React$1.ReactNode | undefined;
422
+ /** `false` keeps the addon out of `aria-describedby` — for one that isn't a description (a "Forgot password?"
423
+ * link), or to name only part of it: give that part an id and pass it in `aria-describedby`. Default `true`. */
424
+ labelAddonDescribes?: boolean | undefined;
425
+ /** 44px box (5.30, Chamber-OS 135): `true` below 640px or on a coarse pointer, as Button's `touchHeight`; `'always'`
426
+ * at every width, also with a mouse in compact density — for a form whose spec asks for 44px targets everywhere.
427
+ * Default: the density's input height. */
428
+ touchHeight?: TouchHeight | undefined;
413
429
  }
414
430
  /** A password input with a show/hide button. All TextField props; `ref` reaches the input (react-hook-form `register`). */
415
431
  export interface PasswordFieldProps extends Omit<TextFieldProps, "type" | "suffix"> {
@@ -531,6 +547,16 @@ export interface CommandProps {
531
547
  className?: string | undefined;
532
548
  }
533
549
  export interface TextareaProps extends FieldProps, Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElement>, "required"> {
550
+ /** 5.31 (Chamber-OS addendum 41): a node between the label and the box — e.g. the phrase to type, with a copy
551
+ * button, on a typed confirmation. The hint and error stay below. Its box's id (`{id}-addon`) comes first in the
552
+ * input's `aria-describedby`, so its text is read after the label; keep any button names in it short. */
553
+ labelAddon?: React$1.ReactNode | undefined;
554
+ /** `false` keeps the addon out of `aria-describedby` — for one that isn't a description (a "Forgot password?"
555
+ * link), or to name only part of it: give that part an id and pass it in `aria-describedby`. Default `true`. */
556
+ labelAddonDescribes?: boolean | undefined;
557
+ /** Accepted like the other fields' (5.30, Chamber-OS 135), so a form can pass it to every field. A Textarea is
558
+ * always at least 96px tall, well over the 44px target, so it changes nothing you can see. */
559
+ touchHeight?: TouchHeight | undefined;
534
560
  }
535
561
  export type SelectOption = string | {
536
562
  value: string;
@@ -552,6 +578,10 @@ export interface SelectProps extends Omit<FieldProps, "label">, Omit<React$1.Sel
552
578
  * option on the read-only ground, with no chevron; no click or key opens the list. The value is still submitted
553
579
  * with the form (unlike `disabled`). Before JavaScript runs, the other options from `options` are disabled. */
554
580
  readOnly?: boolean | undefined;
581
+ /** 44px box (5.30, Chamber-OS 135): `true` below 640px or on a coarse pointer, as Button's `touchHeight`; `'always'`
582
+ * at every width, also with a mouse in compact density — for a form whose spec asks for 44px targets everywhere.
583
+ * Default: the density's input height. */
584
+ touchHeight?: TouchHeight | undefined;
555
585
  }
556
586
  /** A compact filter for FilterBar (5.12): "Status All ▾", as wide as its words. */
557
587
  export interface FilterSelectProps extends Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "onChange" | "value" | "defaultValue" | "multiple" | "size" | "placeholder" | "required" | "children"> {
@@ -586,6 +616,9 @@ export interface RadioGroupProps extends FieldProps {
586
616
  disabled?: boolean | undefined;
587
617
  id?: string | undefined;
588
618
  className?: string | undefined;
619
+ /** 44px rows (5.30, Chamber-OS 135), the one-line label centred: `true` below 640px or on a coarse pointer (touch
620
+ * screens already get it), `'always'` at every width, also with a mouse. Default: as tall as the control and label. */
621
+ touchHeight?: TouchHeight | undefined;
589
622
  }
590
623
  export interface SwitchProps {
591
624
  label?: string | undefined;
@@ -1615,6 +1648,9 @@ export interface FieldComponentProps extends Omit<FieldPropsPublic, "label"> {
1615
1648
  /** The control's id: the label points at it, and the hint/error ids derive from it. */
1616
1649
  id?: string | undefined;
1617
1650
  children?: React$1.ReactNode | undefined;
1651
+ /** 5.31 (Chamber-OS addendum 41): a node between the label and the control, in a `.aura-field__addon` box with the
1652
+ * id `{id}-addon`. TextField and Textarea add that id to the input's `aria-describedby`; a custom control should too. */
1653
+ labelAddon?: React$1.ReactNode | undefined;
1618
1654
  /** Render the label as another element (e.g. `span` for a group of controls) with this id. */
1619
1655
  labelAs?: string | undefined;
1620
1656
  labelId?: string | undefined;
@@ -768,6 +768,13 @@ function createTheme(opts) {
768
768
  D["accent-lime"] = s[200];
769
769
  D["mesh-to"] = s[200];
770
770
  }
771
+ function onFill(bg) {
772
+ return contrast(ZINC[0], bg) >= contrast(INK, bg) ? ZINC[0] : INK;
773
+ }
774
+ L["control-checked-bg"] = L["accent-violet"];
775
+ L["control-checked-fg"] = onFill(L["control-checked-bg"]);
776
+ D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
777
+ D["control-checked-fg"] = onFill(D["control-checked-bg"]);
771
778
  const shadowLight = INK, shadowDark = D["accent-violet"];
772
779
  if (o.primary === "brand") {
773
780
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -850,6 +857,30 @@ function createTheme(opts) {
850
857
  );
851
858
  }
852
859
  if (s) check("light", "ink", "accent-lime (signal)", 4.5, INK, s[200]);
860
+ [
861
+ [
862
+ "light",
863
+ L,
864
+ [
865
+ ["bg-surface", ZINC[0]],
866
+ ["bg-canvas", ZINC[50]]
867
+ ]
868
+ ],
869
+ [
870
+ "dark",
871
+ D,
872
+ [
873
+ ["bg-surface", ZINC[900]],
874
+ ["bg-canvas", ZINC[950]]
875
+ ]
876
+ ]
877
+ ].forEach(function(g) {
878
+ const t = g[0], m = g[1];
879
+ g[2].forEach(function(gr) {
880
+ check(t, "control-checked-bg", gr[0], 3, m["control-checked-bg"], gr[1]);
881
+ });
882
+ check(t, "control-checked-fg", "control-checked-bg", 4.5, m["control-checked-fg"], m["control-checked-bg"]);
883
+ });
853
884
  function css(selector) {
854
885
  if (selector !== void 0 && /[<{};@\r\n]|\/\*|\*\//.test(selector))
855
886
  throw new Error('createTheme: "' + selector + '" is not a plain CSS selector');
@@ -708,6 +708,13 @@ function createTheme(opts) {
708
708
  D["accent-lime"] = s[200];
709
709
  D["mesh-to"] = s[200];
710
710
  }
711
+ function onFill(bg) {
712
+ return contrast(ZINC[0], bg) >= contrast(INK, bg) ? ZINC[0] : INK;
713
+ }
714
+ L["control-checked-bg"] = L["accent-violet"];
715
+ L["control-checked-fg"] = onFill(L["control-checked-bg"]);
716
+ D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
717
+ D["control-checked-fg"] = onFill(D["control-checked-bg"]);
711
718
  const shadowLight = INK, shadowDark = D["accent-violet"];
712
719
  if (o.primary === "brand") {
713
720
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -790,6 +797,30 @@ function createTheme(opts) {
790
797
  );
791
798
  }
792
799
  if (s) check("light", "ink", "accent-lime (signal)", 4.5, INK, s[200]);
800
+ [
801
+ [
802
+ "light",
803
+ L,
804
+ [
805
+ ["bg-surface", ZINC[0]],
806
+ ["bg-canvas", ZINC[50]]
807
+ ]
808
+ ],
809
+ [
810
+ "dark",
811
+ D,
812
+ [
813
+ ["bg-surface", ZINC[900]],
814
+ ["bg-canvas", ZINC[950]]
815
+ ]
816
+ ]
817
+ ].forEach(function(g) {
818
+ const t = g[0], m = g[1];
819
+ g[2].forEach(function(gr) {
820
+ check(t, "control-checked-bg", gr[0], 3, m["control-checked-bg"], gr[1]);
821
+ });
822
+ check(t, "control-checked-fg", "control-checked-bg", 4.5, m["control-checked-fg"], m["control-checked-bg"]);
823
+ });
793
824
  function css(selector) {
794
825
  if (selector !== void 0 && /[<{};@\r\n]|\/\*|\*\//.test(selector))
795
826
  throw new Error('createTheme: "' + selector + '" is not a plain CSS selector');