@hjmds/react 1.30.0 → 1.32.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.
Files changed (102) hide show
  1. package/dist/auth-screen.d.ts +18 -3
  2. package/dist/auth-screen.d.ts.map +1 -1
  3. package/dist/auth-screen.js +22 -6
  4. package/dist/auth-screen.js.map +1 -1
  5. package/dist/bottom-cta.d.ts +6 -1
  6. package/dist/bottom-cta.d.ts.map +1 -1
  7. package/dist/bottom-cta.js +1 -1
  8. package/dist/bottom-cta.js.map +1 -1
  9. package/dist/bottom-navigation.d.ts +10 -0
  10. package/dist/bottom-navigation.d.ts.map +1 -1
  11. package/dist/bottom-navigation.js +80 -8
  12. package/dist/bottom-navigation.js.map +1 -1
  13. package/dist/carousel.d.ts +29 -1
  14. package/dist/carousel.d.ts.map +1 -1
  15. package/dist/carousel.js +27 -3
  16. package/dist/carousel.js.map +1 -1
  17. package/dist/display.d.ts +8 -2
  18. package/dist/display.d.ts.map +1 -1
  19. package/dist/display.js +12 -7
  20. package/dist/display.js.map +1 -1
  21. package/dist/evidence.d.ts +1 -1
  22. package/dist/evidence.js +1 -1
  23. package/dist/evidence.js.map +1 -1
  24. package/dist/forms.d.ts.map +1 -1
  25. package/dist/forms.js +2 -1
  26. package/dist/forms.js.map +1 -1
  27. package/dist/image-choice.d.ts +20 -2
  28. package/dist/image-choice.d.ts.map +1 -1
  29. package/dist/image-choice.js +60 -7
  30. package/dist/image-choice.js.map +1 -1
  31. package/dist/index.d.ts +3 -3
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/internal/field-error.d.ts +19 -0
  36. package/dist/internal/field-error.d.ts.map +1 -0
  37. package/dist/internal/field-error.js +19 -0
  38. package/dist/internal/field-error.js.map +1 -0
  39. package/dist/internal/initial-focus.d.ts.map +1 -1
  40. package/dist/internal/initial-focus.js +49 -7
  41. package/dist/internal/initial-focus.js.map +1 -1
  42. package/dist/internal/media-album.d.ts +70 -0
  43. package/dist/internal/media-album.d.ts.map +1 -0
  44. package/dist/internal/media-album.js +52 -0
  45. package/dist/internal/media-album.js.map +1 -0
  46. package/dist/internal/message-reactions.d.ts +39 -2
  47. package/dist/internal/message-reactions.d.ts.map +1 -1
  48. package/dist/internal/message-reactions.js +31 -6
  49. package/dist/internal/message-reactions.js.map +1 -1
  50. package/dist/layout.d.ts +19 -3
  51. package/dist/layout.d.ts.map +1 -1
  52. package/dist/layout.js +10 -3
  53. package/dist/layout.js.map +1 -1
  54. package/dist/navigation.d.ts +3 -1
  55. package/dist/navigation.d.ts.map +1 -1
  56. package/dist/navigation.js +4 -2
  57. package/dist/navigation.js.map +1 -1
  58. package/dist/number-field.d.ts.map +1 -1
  59. package/dist/number-field.js +2 -1
  60. package/dist/number-field.js.map +1 -1
  61. package/dist/overlays.d.ts +65 -0
  62. package/dist/overlays.d.ts.map +1 -1
  63. package/dist/overlays.js +36 -3
  64. package/dist/overlays.js.map +1 -1
  65. package/dist/pagination.d.ts +13 -11
  66. package/dist/pagination.d.ts.map +1 -1
  67. package/dist/pagination.js +12 -5
  68. package/dist/pagination.js.map +1 -1
  69. package/dist/public-exports.generated.d.ts +8 -0
  70. package/dist/public-exports.generated.d.ts.map +1 -1
  71. package/dist/public-exports.generated.js +24 -0
  72. package/dist/public-exports.generated.js.map +1 -1
  73. package/dist/screen-flows.d.ts +118 -14
  74. package/dist/screen-flows.d.ts.map +1 -1
  75. package/dist/screen-flows.js +95 -23
  76. package/dist/screen-flows.js.map +1 -1
  77. package/dist/screens.d.ts +69 -10
  78. package/dist/screens.d.ts.map +1 -1
  79. package/dist/screens.js +77 -15
  80. package/dist/screens.js.map +1 -1
  81. package/dist/select.d.ts.map +1 -1
  82. package/dist/select.js +2 -1
  83. package/dist/select.js.map +1 -1
  84. package/dist/selection.d.ts.map +1 -1
  85. package/dist/selection.js +3 -2
  86. package/dist/selection.js.map +1 -1
  87. package/dist/steps.d.ts +11 -1
  88. package/dist/steps.d.ts.map +1 -1
  89. package/dist/steps.js +11 -6
  90. package/dist/steps.js.map +1 -1
  91. package/dist/styles.css +181 -22
  92. package/dist/styles.layered.css +181 -22
  93. package/dist/supplemental-display.d.ts.map +1 -1
  94. package/dist/supplemental-display.js +2 -48
  95. package/dist/supplemental-display.js.map +1 -1
  96. package/dist/theme.d.ts.map +1 -1
  97. package/dist/theme.js +1 -0
  98. package/dist/theme.js.map +1 -1
  99. package/dist/toast.d.ts.map +1 -1
  100. package/dist/toast.js +1 -1
  101. package/dist/toast.js.map +1 -1
  102. package/package.json +3 -3
package/dist/steps.d.ts CHANGED
@@ -6,10 +6,15 @@ export type StepsProps<Id extends string = string> = Omit<HTMLAttributes<HTMLOLi
6
6
  statusLabels: StepsStatusLabels;
7
7
  composeAccessibleName: ComposeStepsAccessibleName;
8
8
  renderMark?: (status: StepStatus, position: number) => ReactNode;
9
+ /**
10
+ * Return to a step before the cursor (2026-10-18, spint compose). Only those steps become buttons; the cursor and
11
+ * later steps stay static so the flow never skips forward past validation. Omit it while the flow is locked.
12
+ */
13
+ onStepPress?: (id: Id) => void;
9
14
  /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */
10
15
  layoutStyle?: HjmCompositionStyleProp;
11
16
  }>;
12
- /** Linear, read-only progress steps with one canonical cursor. */
17
+ /** Linear progress steps with one canonical cursor; steps before it can be pressed to go back. */
13
18
  export declare const Steps: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLOListElement>, "children"> & Readonly<{
14
19
  descriptor: Readonly<{
15
20
  steps: readonly Readonly<{
@@ -28,6 +33,11 @@ export declare const Steps: import("react").ForwardRefExoticComponent<Omit<HTMLA
28
33
  }>;
29
34
  composeAccessibleName: ComposeStepsAccessibleName;
30
35
  renderMark?: (status: StepStatus, position: number) => ReactNode;
36
+ /**
37
+ * Return to a step before the cursor (2026-10-18, spint compose). Only those steps become buttons; the cursor and
38
+ * later steps stay static so the flow never skips forward past validation. Omit it while the flow is locked.
39
+ */
40
+ onStepPress?: (id: string) => void;
31
41
  /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */
32
42
  layoutStyle?: HjmCompositionStyleProp;
33
43
  }> & import("react").RefAttributes<HTMLOListElement>>;
@@ -1 +1 @@
1
- {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAc,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,UAAU,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,CACvD,cAAc,CAAC,gBAAgB,CAAC,EAChC,UAAU,CACX,GAAG,QAAQ,CAAC;IACX,UAAU,EAAE,eAAe,CAAC,EAAE,CAAC,CAAC;IAChC,YAAY,EAAE,iBAAiB,CAAC;IAChC,qBAAqB,EAAE,0BAA0B,CAAC;IAClD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,KAAK,SAAS,CAAC;IACjE,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAEH,kEAAkE;AAClE,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;2BAPO,0BAA0B;iBACpC,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,KAAK,SAAS;IAChE,gGAAgG;kBAClF,uBAAuB;qDAsCrC,CAAC"}
1
+ {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG/E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,UAAU,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,CACvD,cAAc,CAAC,gBAAgB,CAAC,EAChC,UAAU,CACX,GAAG,QAAQ,CAAC;IACX,UAAU,EAAE,eAAe,CAAC,EAAE,CAAC,CAAC;IAChC,YAAY,EAAE,iBAAiB,CAAC;IAChC,qBAAqB,EAAE,0BAA0B,CAAC;IAClD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,KAAK,SAAS,CAAC;IACjE;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC;IAC/B,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAEH,kGAAkG;AAClG,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;2BAZO,0BAA0B;iBACpC,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,KAAK,SAAS;IAChE;;;OAGG;kCACuB,IAAI;IAC9B,gGAAgG;kBAClF,uBAAuB;qDAgDrC,CAAC"}
package/dist/steps.js CHANGED
@@ -1,10 +1,15 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { resolveStepsDescriptor, } from "@hjmds/design-contracts/components/steps";
3
- import { forwardRef } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { resolveStepsDescriptor, stepsRecipe, } from "@hjmds/design-contracts/components/steps";
3
+ import { forwardRef, useId } from "react";
4
4
  import { classNames } from "./internal.js";
5
- /** Linear, read-only progress steps with one canonical cursor. */
6
- export const Steps = forwardRef(function Steps({ descriptor, statusLabels, composeAccessibleName, renderMark, className, layoutStyle, ...props }, ref) {
5
+ /** Linear progress steps with one canonical cursor; steps before it can be pressed to go back. */
6
+ export const Steps = forwardRef(function Steps({ descriptor, statusLabels, composeAccessibleName, renderMark, onStepPress, className, layoutStyle, ...props }, ref) {
7
7
  const steps = resolveStepsDescriptor(descriptor, { statusLabels, composeAccessibleName });
8
- return (_jsx("ol", { ...props, style: { ...props.style, ...layoutStyle }, ref: ref, className: classNames("hjm-steps", className), children: steps.map((step, index) => (_jsxs("li", { "aria-label": step.accessibleName, "aria-current": step.status === "current" || step.status === "error" ? "step" : undefined, className: "hjm-steps__step", "data-status": step.status, children: [_jsxs("div", { className: "hjm-steps__rail", "aria-hidden": "true", children: [_jsx("span", { className: "hjm-steps__indicator", children: renderMark?.(step.status, step.position) ?? (step.status === "complete" ? "✓" : step.status === "error" ? "!" : step.position) }), index === steps.length - 1 ? null : _jsx("span", { className: "hjm-steps__connector" })] }), _jsxs("div", { className: "hjm-steps__copy", children: [_jsx("span", { className: "hjm-steps__label", children: step.label }), step.description === undefined ? null : (_jsx("span", { className: "hjm-steps__description", children: step.description })), _jsx("span", { className: "hjm-visually-hidden", children: step.statusLabel })] })] }, step.id))) }));
8
+ const statusId = useId();
9
+ return (_jsx("ol", { ...props, style: { ["--hjm-steps-press-target"]: `${stepsRecipe.press.minTouchTarget}px`, ...props.style, ...layoutStyle }, ref: ref, className: classNames("hjm-steps", className), children: steps.map((step, index) => {
10
+ const content = _jsxs(_Fragment, { children: [_jsxs("div", { className: "hjm-steps__rail", "aria-hidden": "true", children: [_jsx("span", { className: "hjm-steps__indicator", children: renderMark?.(step.status, step.position) ?? (step.status === "complete" ? "✓" : step.status === "error" ? "!" : step.position) }), index === steps.length - 1 ? null : _jsx("span", { className: "hjm-steps__connector" })] }), _jsxs("div", { className: "hjm-steps__copy", children: [_jsx("span", { className: "hjm-steps__label", children: step.label }), step.description === undefined ? null : (_jsx("span", { className: "hjm-steps__description", children: step.description })), _jsx("span", { className: "hjm-visually-hidden", id: `${statusId}-${index}`, children: step.statusLabel })] })] });
11
+ // A pressable step is one named button; its status stays a description so the name keeps order + label.
12
+ return step.revisitable && onStepPress ? (_jsx("li", { className: "hjm-steps__step", "data-status": step.status, children: _jsx("button", { type: "button", className: "hjm-steps__press", "aria-label": step.accessibleName, "aria-describedby": `${statusId}-${index}`, onClick: () => onStepPress(step.id), children: content }) }, step.id)) : (_jsx("li", { "aria-label": step.accessibleName, "aria-current": step.status === "current" || step.status === "error" ? "step" : undefined, className: "hjm-steps__step", "data-status": step.status, children: content }, step.id));
13
+ }) }));
9
14
  });
10
15
  //# sourceMappingURL=steps.js.map
package/dist/steps.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,sBAAsB,GAKvB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,UAAU,EAAuC,MAAM,OAAO,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAe3C,kEAAkE;AAClE,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA+B,SAAS,KAAK,CAC1E,EAAE,UAAU,EAAE,YAAY,EAAE,qBAAqB,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,KAAK,EAAE,EACjG,GAAG;IAEH,MAAM,KAAK,GAAG,sBAAsB,CAAC,UAAU,EAAE,EAAE,YAAY,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC1F,OAAO,CACL,gBAAQ,KAAK,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,WAAW,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,YAC9G,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAC1B,4BACc,IAAI,CAAC,cAAc,kBACjB,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACvF,SAAS,EAAC,iBAAiB,iBACd,IAAI,CAAC,MAAM,aAGxB,eAAK,SAAS,EAAC,iBAAiB,iBAAa,MAAM,aACjD,eAAM,SAAS,EAAC,sBAAsB,YACnC,UAAU,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAC3C,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CACjF,GACI,EACN,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,sBAAsB,GAAG,IAC1E,EACN,eAAK,SAAS,EAAC,iBAAiB,aAC9B,eAAM,SAAS,EAAC,kBAAkB,YAAE,IAAI,CAAC,KAAK,GAAQ,EACrD,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACvC,eAAM,SAAS,EAAC,wBAAwB,YAAE,IAAI,CAAC,WAAW,GAAQ,CACnE,EACD,eAAM,SAAS,EAAC,qBAAqB,YAAE,IAAI,CAAC,WAAW,GAAQ,IAC3D,KAhBD,IAAI,CAAC,EAAE,CAiBT,CACN,CAAC,GACC,CACN,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import {\n resolveStepsDescriptor,\n type ComposeStepsAccessibleName,\n type StepStatus,\n type StepsDescriptor,\n type StepsStatusLabels,\n} from \"@hjmds/design-contracts/components/steps\";\nimport { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { classNames } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type StepsProps<Id extends string = string> = Omit<\n HTMLAttributes<HTMLOListElement>,\n \"children\"\n> & Readonly<{\n descriptor: StepsDescriptor<Id>;\n statusLabels: StepsStatusLabels;\n composeAccessibleName: ComposeStepsAccessibleName;\n renderMark?: (status: StepStatus, position: number) => ReactNode;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Linear, read-only progress steps with one canonical cursor. */\nexport const Steps = forwardRef<HTMLOListElement, StepsProps>(function Steps(\n { descriptor, statusLabels, composeAccessibleName, renderMark, className, layoutStyle, ...props },\n ref,\n) {\n const steps = resolveStepsDescriptor(descriptor, { statusLabels, composeAccessibleName });\n return (\n <ol {...props} style={{ ...props.style, ...layoutStyle }} ref={ref} className={classNames(\"hjm-steps\", className)}>\n {steps.map((step, index) => (\n <li\n aria-label={step.accessibleName}\n aria-current={step.status === \"current\" || step.status === \"error\" ? \"step\" : undefined}\n className=\"hjm-steps__step\"\n data-status={step.status}\n key={step.id}\n >\n <div className=\"hjm-steps__rail\" aria-hidden=\"true\">\n <span className=\"hjm-steps__indicator\">\n {renderMark?.(step.status, step.position) ?? (\n step.status === \"complete\" ? \"✓\" : step.status === \"error\" ? \"!\" : step.position\n )}\n </span>\n {index === steps.length - 1 ? null : <span className=\"hjm-steps__connector\" />}\n </div>\n <div className=\"hjm-steps__copy\">\n <span className=\"hjm-steps__label\">{step.label}</span>\n {step.description === undefined ? null : (\n <span className=\"hjm-steps__description\">{step.description}</span>\n )}\n <span className=\"hjm-visually-hidden\">{step.statusLabel}</span>\n </div>\n </li>\n ))}\n </ol>\n );\n});\n"]}
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,sBAAsB,EACtB,WAAW,GAKZ,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAuC,MAAM,OAAO,CAAC;AAE/E,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAoB3C,kGAAkG;AAClG,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA+B,SAAS,KAAK,CAC1E,EAAE,UAAU,EAAE,YAAY,EAAE,qBAAqB,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,KAAK,EAAE,EAC9G,GAAG;IAEH,MAAM,KAAK,GAAG,sBAAsB,CAAC,UAAU,EAAE,EAAE,YAAY,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC1F,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAC;IACzB,OAAO,CACL,gBAAQ,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,0BAAoC,CAAC,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,WAAW,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,YAC/L,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACzB,MAAM,OAAO,GAAG,8BACd,eAAK,SAAS,EAAC,iBAAiB,iBAAa,MAAM,aACjD,eAAM,SAAS,EAAC,sBAAsB,YACnC,UAAU,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAC3C,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CACjF,GACI,EACN,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,sBAAsB,GAAG,IAC1E,EACN,eAAK,SAAS,EAAC,iBAAiB,aAC9B,eAAM,SAAS,EAAC,kBAAkB,YAAE,IAAI,CAAC,KAAK,GAAQ,EACrD,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACvC,eAAM,SAAS,EAAC,wBAAwB,YAAE,IAAI,CAAC,WAAW,GAAQ,CACnE,EACD,eAAM,SAAS,EAAC,qBAAqB,EAAC,EAAE,EAAE,GAAG,QAAQ,IAAI,KAAK,EAAE,YAAG,IAAI,CAAC,WAAW,GAAQ,IACvF,IACL,CAAC;YACJ,wGAAwG;YACxG,OAAO,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,CAAC,CAAC,CACvC,aAAI,SAAS,EAAC,iBAAiB,iBAAc,IAAI,CAAC,MAAM,YACtD,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,kBAAkB,gBAAa,IAAI,CAAC,cAAc,sBAAoB,GAAG,QAAQ,IAAI,KAAK,EAAE,EAC1H,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,YAAG,OAAO,GAAU,IAFI,IAAI,CAAC,EAAE,CAGjE,CACN,CAAC,CAAC,CAAC,CACF,2BACc,IAAI,CAAC,cAAc,kBACjB,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACvF,SAAS,EAAC,iBAAiB,iBACd,IAAI,CAAC,MAAM,YAExB,OAAO,IADF,IAAI,CAAC,EAAE,CACC,CAChB,CAAC;QACJ,CAAC,CAAC,GACC,CACN,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import {\n resolveStepsDescriptor,\n stepsRecipe,\n type ComposeStepsAccessibleName,\n type StepStatus,\n type StepsDescriptor,\n type StepsStatusLabels,\n} from \"@hjmds/design-contracts/components/steps\";\nimport { forwardRef, useId, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { classNames } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type StepsProps<Id extends string = string> = Omit<\n HTMLAttributes<HTMLOListElement>,\n \"children\"\n> & Readonly<{\n descriptor: StepsDescriptor<Id>;\n statusLabels: StepsStatusLabels;\n composeAccessibleName: ComposeStepsAccessibleName;\n renderMark?: (status: StepStatus, position: number) => ReactNode;\n /**\n * Return to a step before the cursor (2026-10-18, spint compose). Only those steps become buttons; the cursor and\n * later steps stay static so the flow never skips forward past validation. Omit it while the flow is locked.\n */\n onStepPress?: (id: Id) => void;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Linear progress steps with one canonical cursor; steps before it can be pressed to go back. */\nexport const Steps = forwardRef<HTMLOListElement, StepsProps>(function Steps(\n { descriptor, statusLabels, composeAccessibleName, renderMark, onStepPress, className, layoutStyle, ...props },\n ref,\n) {\n const steps = resolveStepsDescriptor(descriptor, { statusLabels, composeAccessibleName });\n const statusId = useId();\n return (\n <ol {...props} style={{ [\"--hjm-steps-press-target\" as string]: `${stepsRecipe.press.minTouchTarget}px`, ...props.style, ...layoutStyle }} ref={ref} className={classNames(\"hjm-steps\", className)}>\n {steps.map((step, index) => {\n const content = <>\n <div className=\"hjm-steps__rail\" aria-hidden=\"true\">\n <span className=\"hjm-steps__indicator\">\n {renderMark?.(step.status, step.position) ?? (\n step.status === \"complete\" ? \"✓\" : step.status === \"error\" ? \"!\" : step.position\n )}\n </span>\n {index === steps.length - 1 ? null : <span className=\"hjm-steps__connector\" />}\n </div>\n <div className=\"hjm-steps__copy\">\n <span className=\"hjm-steps__label\">{step.label}</span>\n {step.description === undefined ? null : (\n <span className=\"hjm-steps__description\">{step.description}</span>\n )}\n <span className=\"hjm-visually-hidden\" id={`${statusId}-${index}`}>{step.statusLabel}</span>\n </div>\n </>;\n // A pressable step is one named button; its status stays a description so the name keeps order + label.\n return step.revisitable && onStepPress ? (\n <li className=\"hjm-steps__step\" data-status={step.status} key={step.id}>\n <button type=\"button\" className=\"hjm-steps__press\" aria-label={step.accessibleName} aria-describedby={`${statusId}-${index}`}\n onClick={() => onStepPress(step.id)}>{content}</button>\n </li>\n ) : (\n <li\n aria-label={step.accessibleName}\n aria-current={step.status === \"current\" || step.status === \"error\" ? \"step\" : undefined}\n className=\"hjm-steps__step\"\n data-status={step.status}\n key={step.id}\n >{content}</li>\n );\n })}\n </ol>\n );\n});\n"]}
package/dist/styles.css CHANGED
@@ -372,7 +372,7 @@
372
372
  the support text fades, so a custom Field control dims without needing a class. A component
373
373
  recipe value (Select, NumberField, OtpField, SearchField, PasswordField) arrives as an
374
374
  inline --hjm-field-disabled-opacity on the root. */
375
- .hjm-field[data-state="disabled"] > :not(.hjm-field__description):not(.hjm-field__error) { opacity: var(--hjm-field-disabled-opacity); }
375
+ .hjm-field[data-state="disabled"] > :not(.hjm-field__description):not(.hjm-field__error):not([data-hjm-field-error]) { opacity: var(--hjm-field-disabled-opacity); }
376
376
  .hjm-field__input {
377
377
  inline-size: 100%;
378
378
  min-inline-size: 0;
@@ -524,6 +524,13 @@
524
524
  border-radius: var(--hjm-radius-xl);
525
525
  box-shadow: var(--hjm-shadow-floating);
526
526
  }
527
+ /* configuration.shape (bottomNavigationRecipe.shapes): the renderer sets the frame radius token and inner padding. */
528
+ .hjm-bottom-navigation[data-presentation="floating"]:not([data-shape="standard"]) .hjm-bottom-navigation__surface {
529
+ border-radius: var(--hjm-bottom-navigation-shape-radius);
530
+ }
531
+ .hjm-bottom-navigation[data-presentation="floating"]:not([data-shape="standard"]) .hjm-bottom-navigation__list {
532
+ padding: var(--hjm-bottom-navigation-shape-padding);
533
+ }
527
534
  .hjm-bottom-navigation__list {
528
535
  display: grid;
529
536
  grid-template-columns: repeat(var(--hjm-bottom-navigation-columns), minmax(var(--hjm-bottom-navigation-item-min-width), 1fr));
@@ -573,6 +580,14 @@
573
580
  }
574
581
  .hjm-bottom-navigation__indicator > :first-child { transition: transform 160ms ease; }
575
582
  .hjm-bottom-navigation__indicator[data-state="selected"] > :first-child { transform: scale(1.06); }
583
+ /* iconEmphasis "lift" (bottomNavigationRecipe.icon.liftEmphasis): the selected icon rises one step above the shared
584
+ scale and idle icons dim, the non-color cue for full-color artwork. The badge is a later sibling and stays opaque. */
585
+ .hjm-bottom-navigation[data-icon-emphasis="lift"] .hjm-bottom-navigation__indicator[data-state="selected"] > :first-child {
586
+ transform: translateY(var(--hjm-bottom-navigation-lift-offset)) scale(1.06);
587
+ }
588
+ .hjm-bottom-navigation[data-icon-emphasis="lift"] li:not([data-prominent="true"]) .hjm-bottom-navigation__indicator[data-state="idle"] > :first-child {
589
+ opacity: var(--hjm-bottom-navigation-lift-idle-opacity);
590
+ }
576
591
  /* Icon-only items center the indicator in the density's minimum height instead of leaving an empty label row. */
577
592
  .hjm-bottom-navigation[data-labels="hidden"] .hjm-bottom-navigation__item { justify-content: center; }
578
593
  .hjm-bottom-navigation__label { font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); font-weight: var(--hjm-text-emphasis-medium); }
@@ -600,6 +615,18 @@
600
615
  inset-block-start: 50%;
601
616
  translate: -50% -50%;
602
617
  }
618
+ /* Prominent center destination: bottomNavigationRecipe.prominentDestination, lifted so its label stays on the
619
+ other labels' line. The circle keeps brand fill in both states; the label carries selection. */
620
+ .hjm-bottom-navigation li[data-prominent="true"] .hjm-bottom-navigation__indicator {
621
+ inline-size: var(--hjm-bottom-navigation-prominent-diameter);
622
+ block-size: var(--hjm-bottom-navigation-prominent-diameter);
623
+ margin-block-start: calc(-1 * var(--hjm-bottom-navigation-prominent-lift));
624
+ border-radius: var(--hjm-radius-full);
625
+ color: var(--hjm-color-on-primary);
626
+ background: var(--hjm-color-primary);
627
+ box-shadow: var(--hjm-shadow-floating);
628
+ }
629
+ .hjm-bottom-navigation li[data-prominent="true"] .hjm-bottom-navigation__indicator > :first-child { transform: none; }
603
630
 
604
631
  .hjm-number-field__control {
605
632
  padding-inline: 0;
@@ -1719,6 +1746,22 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1719
1746
  AuthScreenLayout safeAreaBottom (2026-10-17 1.28 adoption: products wrapped the layout in their own inset). */
1720
1747
  padding-block-end: max(var(--hjm-auth-screen-padding-block), env(safe-area-inset-bottom, 0px));
1721
1748
  }
1749
+ /* With title/exit the layout owns the ScreenLayout centered header band: full-bleed over the inline padding, at the
1750
+ shared screen gutter, with the former top padding moved below it (2026-10-18: products kept a TopBar above). */
1751
+ .hjm-auth-screen[data-header] { padding-block-start: 0; }
1752
+ .hjm-auth-screen__header {
1753
+ display: grid;
1754
+ grid-template-columns: 1fr minmax(0, auto) 1fr;
1755
+ align-items: center;
1756
+ box-sizing: border-box;
1757
+ min-block-size: var(--hjm-auth-screen-header-min-height);
1758
+ margin-inline: calc(var(--hjm-auth-screen-padding-inline) * -1);
1759
+ margin-block-end: var(--hjm-auth-screen-padding-block);
1760
+ padding-inline: var(--hjm-auth-screen-header-padding-inline);
1761
+ padding-block: var(--hjm-auth-screen-header-padding-block);
1762
+ }
1763
+ .hjm-auth-screen__header-title { min-inline-size: 0; text-align: center; overflow-wrap: anywhere; }
1764
+ .hjm-auth-screen__header-title > * { margin: 0; }
1722
1765
  .hjm-auth-screen__block {
1723
1766
  display: flex;
1724
1767
  flex: 1;
@@ -1770,17 +1813,18 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1770
1813
  flex-wrap: wrap;
1771
1814
  justify-content: center;
1772
1815
  align-items: center;
1773
- column-gap: var(--hjm-auth-screen-link-gap);
1774
1816
  }
1775
- /* 2026-10-17 "간격 줄여": 보이는 간격은 gap + 구분점뿐이다. 44 터치 높이는 배경 없는 min-block-size,
1776
- 가로 터치 폭은 보이지 않는 가상 요소가 구분점 앞까지 넓힌다 — 버튼 안쪽 여백으로 만들면 글자 사이가 다시 벌어진다. */
1817
+ /* 2026-10-17 "간격 줄여": 보이는 간격은 linkGap + 구분점뿐이다. 44 터치 높이는 배경 없는 min-block-size다.
1818
+ 2026-10-18 링크 사이 간격을 flex column-gap에서 링크의 inline 여백으로 옮겼다. 보이는 간격은 같고(column-gap 0),
1819
+ 가로 터치 폭이 그 여백만큼 링크 상자 안에 든다. 예전 가상 요소는 상자 밖으로 linkGap만큼 뻗어, 긴 링크가 푸터 폭을
1820
+ 다 쓰면 푸터의 스크롤 폭을 넓혔다(웹 scenario-matrix auth-screen long-copy 288 > 282). 여백을 gap과 함께 두면
1821
+ 글자 사이가 두 배로 벌어지므로 gap은 두지 않는다. */
1777
1822
  .hjm-auth-screen__link {
1778
- position: relative;
1779
1823
  display: inline-flex;
1780
1824
  align-items: center;
1781
1825
  min-block-size: var(--hjm-auth-screen-link-target);
1782
1826
  margin: 0;
1783
- padding: 0;
1827
+ padding: 0 var(--hjm-auth-screen-link-gap);
1784
1828
  border: 0;
1785
1829
  background: none;
1786
1830
  font-family: var(--hjm-font-family-ui, inherit);
@@ -1788,12 +1832,6 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1788
1832
  text-underline-offset: 0.18em;
1789
1833
  cursor: pointer;
1790
1834
  }
1791
- .hjm-auth-screen__link::after {
1792
- content: "";
1793
- position: absolute;
1794
- inset-block: 0;
1795
- inset-inline: calc(var(--hjm-auth-screen-link-gap) * -1);
1796
- }
1797
1835
  .hjm-auth-screen__link:focus-visible {
1798
1836
  outline: var(--hjm-focus-width) solid var(--hjm-color-focus);
1799
1837
  outline-offset: var(--hjm-focus-offset);
@@ -2191,6 +2229,46 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2191
2229
  background: var(--hjm-backdrop-modal);
2192
2230
  overscroll-behavior: contain;
2193
2231
  }
2232
+ /* Dialog overlayLayer: viewport-sized, decorative, above the card (later sibling) and never a pointer target. */
2233
+ .hjm-dialog__overlay-layer {
2234
+ position: absolute;
2235
+ inset: 0;
2236
+ pointer-events: none;
2237
+ }
2238
+ /* ImmersiveOverlay: an edge-to-edge stage and control bar with no scrim, card or gutter. */
2239
+ .hjm-overlay.hjm-immersive-positioner {
2240
+ align-items: stretch;
2241
+ padding: 0;
2242
+ background: var(--hjm-immersive-background);
2243
+ }
2244
+ .hjm-immersive {
2245
+ display: flex;
2246
+ flex-direction: column;
2247
+ inline-size: 100%;
2248
+ block-size: 100%;
2249
+ color: var(--hjm-color-text);
2250
+ background: var(--hjm-immersive-background);
2251
+ outline: none;
2252
+ }
2253
+ .hjm-immersive__stage {
2254
+ position: relative;
2255
+ flex: 1 1 auto;
2256
+ min-block-size: 0;
2257
+ overflow: hidden;
2258
+ }
2259
+ .hjm-immersive__controls {
2260
+ display: flex;
2261
+ flex-direction: column;
2262
+ gap: var(--hjm-immersive-gap);
2263
+ padding: var(--hjm-immersive-padding);
2264
+ padding-inline: max(var(--hjm-immersive-padding), env(safe-area-inset-left)) max(var(--hjm-immersive-padding), env(safe-area-inset-right));
2265
+ padding-block-end: max(var(--hjm-immersive-padding), env(safe-area-inset-bottom));
2266
+ }
2267
+ .hjm-immersive__actions {
2268
+ display: flex;
2269
+ align-items: center;
2270
+ gap: var(--hjm-immersive-action-gap);
2271
+ }
2194
2272
  .hjm-dialog,
2195
2273
  .hjm-alert-dialog,
2196
2274
  .hjm-sheet {
@@ -2207,6 +2285,8 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2207
2285
  .hjm-dialog { inline-size: min(36rem, 100%); }
2208
2286
  .hjm-dialog[data-size="small"] { inline-size: min(28rem, 100%); }
2209
2287
  .hjm-dialog[data-size="large"] { inline-size: min(48rem, 100%); }
2288
+ /* Dialog scrollEnabled={false}: the card keeps its scroll offset while a gesture in the body owns the pointer. */
2289
+ .hjm-dialog[data-scroll="locked"] { overflow: hidden; overscroll-behavior: contain; touch-action: none; }
2210
2290
  .hjm-alert-dialog[data-size="small"] { inline-size: min(28rem, 100%); }
2211
2291
  .hjm-alert-dialog[data-size="large"] { inline-size: min(48rem, 100%); }
2212
2292
  .hjm-dialog__header,
@@ -2724,6 +2804,8 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2724
2804
  .hjm-pagination__item[data-state="current"] { border-color: var(--hjm-color-content-brand); color: var(--hjm-color-content-brand); font-weight: var(--hjm-text-emphasis-strong); }
2725
2805
  .hjm-pagination__item[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
2726
2806
  .hjm-pagination__ellipsis { color: var(--hjm-color-text-muted); }
2807
+ /* Open-ended current page label (paginationRecipe.pageLabel): text, not a button, on the steps' 44px row. */
2808
+ .hjm-pagination__page-label { min-block-size: var(--hjm-control-min-touch-target); display: grid; place-items: center; padding-inline: var(--hjm-space-xs); color: var(--hjm-color-text-body); overflow-wrap: anywhere; }
2727
2809
  .hjm-load-more { display: grid; justify-items: center; gap: var(--hjm-space-sm); padding-block: var(--hjm-space-lg); text-align: center; }
2728
2810
  .hjm-load-more[data-density="compact"] { gap: var(--hjm-space-xs); padding-block: var(--hjm-space-sm); }
2729
2811
  .hjm-load-more__sentinel { inline-size: 1px; block-size: 1px; }
@@ -2748,6 +2830,9 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2748
2830
  .hjm-steps__step[data-status="pending"] .hjm-steps__label { color: var(--hjm-color-text-muted); }
2749
2831
  .hjm-steps__step[data-status="error"] .hjm-steps__label { color: var(--hjm-color-danger); }
2750
2832
  .hjm-steps__description { color: var(--hjm-color-text-muted); font-size: var(--hjm-type-caption-size); overflow-wrap: anywhere; }
2833
+ /* A step before the cursor is one button over marker and label (Steps onStepPress); the cell keeps its static layout. */
2834
+ .hjm-steps__press { display: grid; align-content: start; gap: var(--hjm-space-xs); min-inline-size: 0; min-block-size: var(--hjm-steps-press-target); margin: 0; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer; }
2835
+ .hjm-steps__press:focus-visible { outline: var(--hjm-focus-width) solid var(--hjm-color-focus); outline-offset: var(--hjm-focus-offset); }
2751
2836
 
2752
2837
  .hjm-file-picker { display: grid; gap: var(--hjm-space-xs); }
2753
2838
  .hjm-file-picker__label { color: var(--hjm-color-text); font-weight: var(--hjm-text-emphasis-medium); }
@@ -2796,14 +2881,14 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2796
2881
  /* Cards stay narrow on desktop too; keep close/action rows explicit (docs/toast.md). The card owns its width cap so a
2797
2882
  controlled Toast outside the viewport keeps it too (it stretched to 1200px in the layout story, 2026-10-17 review).
2798
2883
  1.10.0 layout: [tone badge][title + description][close] / action pill on its own row, end-aligned.
2799
- The 2px tone strip is retired — the tinted badge carries tone (toastRecipe.toneMark width 0). */
2884
+ There is no edge stripe: the tinted badge (or product media) carries tone. 1.10.0 retired the 2px strip and 1.32.0
2885
+ removed its always-hidden element, so no product rule is needed to hide it (docs/toast.md). */
2800
2886
  .hjm-toast { position: relative; min-inline-size: 0; max-inline-size: min(26.25rem, 100%); min-block-size: 3.5rem; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; column-gap: var(--hjm-space-sm); row-gap: var(--hjm-space-xs); padding: var(--hjm-toast-padding-block) var(--hjm-toast-padding-inline); overflow: hidden; border: var(--hjm-toast-border-width) solid var(--hjm-toast-border); border-radius: var(--hjm-toast-radius); color: var(--hjm-color-text); background: var(--hjm-toast-background); box-shadow: var(--hjm-toast-shadow); pointer-events: auto; }
2801
2887
  /* Whole-card activation is a sibling surface; explicit controls stay above it. */
2802
2888
  .hjm-toast__press { position: absolute; inset: 0; z-index: 1; border: 0; border-radius: inherit; padding: 0; background: transparent; cursor: pointer; }
2803
2889
  .hjm-toast__press:focus-visible { outline: 2px solid var(--hjm-color-content-brand); outline-offset: -2px; }
2804
2890
  .hjm-toast:has(> .hjm-toast__press:active) { opacity: var(--hjm-toast-pressed-opacity); }
2805
2891
  .hjm-toast:has(> .hjm-toast__press) > :is(.hjm-toast__action, .hjm-toast__close) { position: relative; z-index: 2; }
2806
- .hjm-toast__tone-mark { display: none; }
2807
2892
  .hjm-toast__icon { grid-column: 1; grid-row: 1; align-self: start; display: grid; place-items: center; inline-size: var(--hjm-toast-badge-size); block-size: var(--hjm-toast-badge-size); border-radius: 50%; color: var(--hjm-toast-accent-neutral); background: var(--hjm-toast-badge-neutral); }
2808
2893
  .hjm-toast[data-tone="info"] .hjm-toast__icon { color: var(--hjm-toast-accent-info); background: var(--hjm-toast-badge-info); }
2809
2894
  .hjm-toast[data-tone="success"] .hjm-toast__icon { color: var(--hjm-toast-accent-success); background: var(--hjm-toast-badge-success); }
@@ -2934,10 +3019,12 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2934
3019
  .hjm-carousel__rotation { justify-self: start; }
2935
3020
  .hjm-carousel__controls, .hjm-carousel__dots { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: var(--hjm-space-xs); }
2936
3021
  .hjm-carousel__controls { flex-wrap: nowrap; } .hjm-carousel__dots { flex-shrink: 1; min-inline-size: 0; }
2937
- .hjm-carousel__dot { display: grid; place-items: center; min-inline-size: var(--hjm-control-min-touch-target); min-block-size: var(--hjm-control-min-touch-target); border: 0; border-radius: var(--hjm-radius-full); background: transparent; cursor: pointer; }
2938
- .hjm-carousel__dot > span { inline-size: var(--hjm-space-xs); block-size: var(--hjm-space-xs); border-radius: var(--hjm-radius-full); background: var(--hjm-color-text-weak); } /* carouselRecipe.dot.color.inactive (border.strong), as Native */
2939
- .hjm-carousel__dot[data-current="true"] > span { background: var(--hjm-color-content-brand); }
2940
- .hjm-carousel__dot:focus-visible { outline: 2px solid var(--hjm-color-content-brand); outline-offset: 2px; }
3022
+ /* PageIndicator is the same dot row for a product-owned pager (carouselRecipe.dot). */
3023
+ .hjm-page-indicator { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: var(--hjm-space-xs); min-inline-size: 0; }
3024
+ .hjm-carousel__dot, .hjm-page-indicator__dot { display: grid; place-items: center; min-inline-size: var(--hjm-control-min-touch-target); min-block-size: var(--hjm-control-min-touch-target); border: 0; border-radius: var(--hjm-radius-full); background: transparent; cursor: pointer; }
3025
+ .hjm-carousel__dot > span, .hjm-page-indicator__dot > span { inline-size: var(--hjm-space-xs); block-size: var(--hjm-space-xs); border-radius: var(--hjm-radius-full); background: var(--hjm-color-text-weak); } /* carouselRecipe.dot.color.inactive (border.strong), as Native */
3026
+ .hjm-carousel__dot[data-current="true"] > span, .hjm-page-indicator__dot[data-current="true"] > span { background: var(--hjm-color-content-brand); }
3027
+ .hjm-carousel__dot:focus-visible, .hjm-page-indicator__dot:focus-visible { outline: 2px solid var(--hjm-color-content-brand); outline-offset: 2px; }
2941
3028
 
2942
3029
  /* The viewport action follows the content in DOM order. Its measured clearance
2943
3030
  belongs on the scroll content, not on the fixed button (docs/floating-action-button.md). */
@@ -3113,6 +3200,16 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3113
3200
  .hjm-tabs:is([data-appearance="gooey"], [data-appearance="slide"]) .hjm-tabs__list { position: relative; }
3114
3201
  .hjm-tabs:is([data-appearance="gooey"], [data-appearance="slide"]) .hjm-tabs__tab[data-state="selected"]::after { display: none; }
3115
3202
  .hjm-tabs__indicator { position: absolute; bottom: 0; height: 2px; border-radius: var(--hjm-radius-full); background: var(--hjm-color-content-brand); pointer-events: none; }
3203
+ /* indicator="filled" (tabsRecipe.indicators.filled): a surface-accent pill behind the selected tab, no list divider.
3204
+ The moving pill spans the tab height and sits under the labels. */
3205
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__list { border: 0; }
3206
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab { border-radius: var(--hjm-radius-full); }
3207
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab[data-state="selected"] { color: var(--hjm-color-content-brand); }
3208
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab[data-state="selected"]::after { display: none; }
3209
+ .hjm-tabs[data-indicator="filled"]:not([data-indicator-motion="moving"]) .hjm-tabs__tab[data-state="selected"] { background: var(--hjm-color-surface-accent); }
3210
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__indicator { top: 0; bottom: 0; height: auto; border-radius: var(--hjm-radius-full); background: var(--hjm-color-surface-accent); z-index: 0; }
3211
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__list { position: relative; }
3212
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab { z-index: 1; }
3116
3213
  .hjm-tabs__gooey { height: 6px; }
3117
3214
 
3118
3215
  /* Capsule destinations retain route/link semantics. At larger text sizes we show
@@ -3167,6 +3264,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3167
3264
  .hjm-screen__header { display: flex; flex-wrap: wrap; align-items: center; gap: var(--hjm-screen-item-gap, var(--hjm-space-sm)); padding: var(--hjm-screen-padding); flex: 0 0 auto; }
3168
3265
  .hjm-screen__heading { flex: 1 1 calc(var(--hjm-screen-header-min) * var(--hjm-text-scale, 1)); min-inline-size: 0; overflow-wrap: anywhere; }
3169
3266
  .hjm-screen__heading > * { margin: 0; }
3267
+ .hjm-screen__title-leading { display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center; }
3170
3268
  .hjm-screen__notice { padding-inline: var(--hjm-screen-padding); flex: 0 0 auto; }
3171
3269
  /* --hjm-screen-body-inline is the body's inline padding and the distance a bleeding CollectionRail crosses (Native
3172
3270
  ScreenBodyInsetContext); inset variants change this one value instead of the padding itself. */
@@ -3174,8 +3272,15 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3174
3272
  /* contentInset="body-edge": header, notice and footer keep the gutter; full-width list rows own their inline inset
3175
3273
  (review S5#9: body 16 + row 16 put DM avatars ~32 from the edge). "none" keeps its host-owns-all meaning. */
3176
3274
  .hjm-screen[data-content-inset="body-edge"] > .hjm-screen__body { --hjm-screen-body-inline: 0px; }
3275
+ /* contentInset="body-bleed": the same gutters stay on header, notice and footer; a map or canvas body has none on any
3276
+ side. As the last region it still clears the fixed BottomNavigation, without the gutter (Native twin). */
3277
+ .hjm-screen[data-content-inset="body-bleed"] > .hjm-screen__body { --hjm-screen-body-inline: 0px; padding-block: 0; }
3278
+ .hjm-screen[data-content-inset="body-bleed"] > .hjm-screen__body:last-child { padding-block-end: var(--hjm-bottom-navigation-inset, 0px); }
3279
+ /* Both hand the body gutter to product content; the shared state group has no inset of its own, so it keeps the gutter. */
3280
+ .hjm-screen[data-content-inset="body-edge"] > .hjm-screen__body > .hjm-screen__state,
3281
+ .hjm-screen[data-content-inset="body-bleed"] > .hjm-screen__body > .hjm-screen__state { padding-inline: var(--hjm-screen-padding); }
3177
3282
  /* Overlay content is not in the screen body, even when rendered inside it: a bleeding rail there stays in its box. */
3178
- .hjm-sheet, .hjm-dialog, .hjm-alert-dialog, .hjm-popover { --hjm-screen-body-inline: 0px; }
3283
+ .hjm-sheet, .hjm-dialog, .hjm-alert-dialog, .hjm-popover { --hjm-screen-body-inline: 0px; --hjm-bottom-navigation-inset: 0px; }
3179
3284
  /* contentInset="none" hands every inset to the host: no footer rule, as on Native (BottomCTA then draws its own bar). */
3180
3285
  .hjm-screen[data-content-inset="none"] > .hjm-screen__footer { border-block-start: 0; }
3181
3286
  .hjm-screen__body[data-scroll="screen"] { overflow-y: auto; }
@@ -3184,7 +3289,11 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3184
3289
  .hjm-screen__centered-content { margin-block: auto; flex-shrink: 0; inline-size: 100%; }
3185
3290
  .hjm-screen__body[data-replacement="true"] { display: flex; flex-direction: column; }
3186
3291
  .hjm-screen__state { margin-block: auto; flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: var(--hjm-screen-state-gap, var(--hjm-space-md)); padding-block: var(--hjm-screen-gap); text-align: center; }
3187
- .hjm-screen__footer { flex: 0 0 auto; padding: var(--hjm-screen-padding); padding-block-end: max(var(--hjm-screen-padding), env(safe-area-inset-bottom)); border-block-start: 1px solid var(--hjm-color-border); }
3292
+ .hjm-screen__footer { flex: 0 0 auto; padding: var(--hjm-screen-padding); padding-block-end: max(var(--hjm-screen-padding), env(safe-area-inset-bottom), calc(var(--hjm-screen-padding) + var(--hjm-bottom-navigation-inset, 0px))); border-block-start: 1px solid var(--hjm-color-border); }
3293
+ /* --hjm-bottom-navigation-inset (BottomNavigationInsetProvider) is the viewport bottom the fixed bar covers. The last
3294
+ region keeps its gutter above the bar; without a provider the value is 0 and the padding is unchanged. This is the
3295
+ only place the inset is applied: a scroll="content" list inside must not add it again. */
3296
+ .hjm-screen__body:last-child { padding-block-end: calc(var(--hjm-screen-padding) + var(--hjm-bottom-navigation-inset, 0px)); }
3188
3297
  /* SearchScreen filtersOverflow=scroll; why: screen-flows.tsx. Block padding keeps focus rings unclipped.
3189
3298
  --hjm-search-filters-bleed = screen padding + hostGutter (1.13.1), set inline by SearchScreen. */
3190
3299
  .hjm-search-screen__filters { --f: calc(var(--hjm-focus-width) + var(--hjm-focus-offset)); --b: var(--hjm-search-filters-bleed, var(--hjm-screen-padding)); overflow-x: auto; margin: calc(-1 * var(--f)) calc(-1 * var(--b)); padding: var(--f) var(--b); scroll-padding-inline: var(--b); scrollbar-width: none; }
@@ -3232,6 +3341,19 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3232
3341
  [dir="rtl"] .hjm-chat-message[data-tail] .hjm-chat-message__bubble::before { transform: scaleX(-1); }
3233
3342
 
3234
3343
  .hjm-chat-message__meta { display: flex; flex-shrink: 0; align-items: center; gap: var(--hjm-space-xs); }
3344
+ /* Reaction summary pill (sizes inline from chatMessageRecipe.reactionSummary); own reaction shows the brand border. */
3345
+ .hjm-chat-message__reaction-summary { display: inline-flex; align-items: center; box-sizing: border-box; border: 1px solid var(--hjm-color-border); border-radius: var(--hjm-radius-full); background: var(--hjm-color-surface); color: var(--hjm-color-text); font: inherit; line-height: 1; cursor: pointer; }
3346
+ .hjm-chat-message__reaction-summary[data-selected] { border-color: var(--hjm-color-content-brand); }
3347
+ .hjm-chat-message__reaction-summary:hover:not(:disabled) { background: var(--hjm-color-surface-alt); }
3348
+ .hjm-chat-message__reaction-summary:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: 2px; }
3349
+ .hjm-chat-message__reaction-summary:disabled { opacity: 0.5; cursor: default; }
3350
+ /* Read-only counts (summary.readOnly): same slot, plain text, no pill surface or pointer affordance. */
3351
+ .hjm-chat-message__reaction-counts { display: inline-flex; align-items: center; line-height: 1; }
3352
+ /* Typing dots (typingIndicatorRecipe via custom properties): staggered pulse, static under reduced motion. */
3353
+ .hjm-typing-indicator__dot { inline-size: var(--hjm-typing-dot); block-size: var(--hjm-typing-dot); border-radius: 50%; background: var(--hjm-color-text-muted); animation: hjm-typing-pulse var(--hjm-typing-cycle) ease-in-out infinite both; }
3354
+ .hjm-typing-indicator[data-reduced-motion] .hjm-typing-indicator__dot { animation: none; }
3355
+ @media (prefers-reduced-motion: reduce) { .hjm-typing-indicator__dot { animation: none; } }
3356
+ @keyframes hjm-typing-pulse { 0%, 100% { opacity: var(--hjm-typing-rest); } 50% { opacity: 1; } }
3235
3357
  /* A quoted reply heads the bubble and is divided from the answer. */
3236
3358
  .hjm-chat-message__reply { border-block-end: 1px solid var(--hjm-color-border); padding-block-end: var(--hjm-space-xs); margin-block-end: var(--hjm-space-xs); display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
3237
3359
  .hjm-chat-message__quote { inline-size: 100%; border-inline: 0; border-block-start: 0; background: none; padding-inline: 0; padding-block-start: 0; color: inherit; font: inherit; text-align: start; cursor: pointer; }
@@ -3242,6 +3364,17 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3242
3364
  .hjm-chat-message__media-item > img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
3243
3365
  button.hjm-chat-message__media-item { cursor: pointer; }
3244
3366
  button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: -2px; }
3367
+ /* Paged media album (2026-10-18, mediaAlbumRecipe): one mounted page in the host's media width, counter row below. */
3368
+ .hjm-media-album { display: flex; flex-direction: column; max-inline-size: 100%; }
3369
+ .hjm-media-album__page { position: relative; display: block; inline-size: 100%; overflow: hidden; padding: 0; border: 0; background: var(--hjm-color-surface-alt); }
3370
+ /* The page wrapper fills the frame in every state; the open action is a sibling overlay so a page status inside stays reachable. */
3371
+ .hjm-media-album__content { inline-size: 100%; block-size: 100%; }
3372
+ .hjm-media-album__content > img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
3373
+ .hjm-media-album__open { position: absolute; inset: 0; z-index: 1; padding: 0; border: 0; border-radius: inherit; background: transparent; cursor: pointer; }
3374
+ .hjm-media-album__open:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: -2px; }
3375
+ /* Loading / failed page (MediaAlbumPageStatus): one block centered in the frame (VQ-09); gap and padding inline from mediaAlbumRecipe.pageStatus. */
3376
+ .hjm-media-album__status { box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center; inline-size: 100%; block-size: 100%; text-align: center; }
3377
+ .hjm-media-album__controls { display: flex; align-items: center; justify-content: space-between; }
3245
3378
  .hjm-chat-message__bubble[data-photo-only] { background: none; padding: 0; }
3246
3379
  .hjm-chat-message__bubble[data-photo-only]::before { display: none; }
3247
3380
  /* Only the shared default gutter narrows to the timeline gutter; "none" and "body-edge" keep their own meaning. */
@@ -3254,8 +3387,21 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3254
3387
  .hjm-image-choice { display: flex; flex-direction: column; gap: var(--hjm-space-xs); }
3255
3388
  .hjm-image-choice__label { font-weight: var(--hjm-text-emphasis-medium); }
3256
3389
  .hjm-image-choice__tiles { display: flex; flex-wrap: wrap; gap: var(--hjm-image-choice-gap); }
3257
- .hjm-image-choice__tile { position: relative; inline-size: calc(var(--hjm-image-choice-size) + (var(--hjm-image-choice-ring) + var(--hjm-image-choice-offset)) * 2); aspect-ratio: 1; padding: var(--hjm-image-choice-offset); border: var(--hjm-image-choice-ring) solid transparent; border-radius: calc(var(--hjm-image-choice-radius) + var(--hjm-image-choice-ring) + var(--hjm-image-choice-offset)); background: none; cursor: pointer; }
3258
- .hjm-image-choice__tile[data-selected] { border-color: var(--hjm-color-content-brand); }
3390
+ /* The button is a column (ring frame, optional item label) so the label shares the tap target; the ring is drawn on
3391
+ the inner frame (2026-10-18 showItemLabels). */
3392
+ .hjm-image-choice__tile { display: flex; flex-direction: column; gap: var(--hjm-image-choice-item-label-gap); inline-size: calc(var(--hjm-image-choice-size) + (var(--hjm-image-choice-ring) + var(--hjm-image-choice-offset)) * 2); padding: 0; border: 0; border-radius: calc(var(--hjm-image-choice-radius) + var(--hjm-image-choice-ring) + var(--hjm-image-choice-offset)); background: none; color: inherit; cursor: pointer; }
3393
+ .hjm-image-choice__ring { position: relative; display: block; inline-size: 100%; aspect-ratio: 1; padding: var(--hjm-image-choice-offset); border: var(--hjm-image-choice-ring) solid transparent; border-radius: inherit; }
3394
+ .hjm-image-choice__tile[data-selected] .hjm-image-choice__ring { border-color: var(--hjm-color-content-brand); }
3395
+ .hjm-image-choice__item-label { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
3396
+ /* Caption block (label lines + optional description line) has the same height in every tile of a group
3397
+ (resolveImageChoiceCaptionLines), so wrapped rows stay even and virtualized rows share one stride. */
3398
+ .hjm-image-choice__caption { display: flex; flex-direction: column; min-inline-size: 0; block-size: calc(var(--hjm-type-caption-line-height) * var(--hjm-image-choice-caption-lines)); }
3399
+ /* Two-line labels keep words whole (keep-all) and clamp; a single over-long word still breaks rather than overflowing. */
3400
+ .hjm-image-choice__item-label[data-lines="2"] { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; white-space: normal; word-break: keep-all; overflow-wrap: anywhere; }
3401
+ .hjm-image-choice__item-description { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
3402
+ /* virtualized: the group is its own fixed-height scroller; the inset keeps the tile focus outline inside the clip. */
3403
+ .hjm-image-choice__viewport { position: relative; box-sizing: border-box; overflow-y: auto; padding: var(--hjm-image-choice-viewport-inset); }
3404
+ .hjm-image-choice__row { position: absolute; inset-inline: 0; display: flex; gap: var(--hjm-image-choice-gap); }
3259
3405
  .hjm-image-choice__tile:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: 2px; }
3260
3406
  .hjm-image-choice__tile:disabled { opacity: 0.5; cursor: default; }
3261
3407
  .hjm-image-choice__image { display: block; inline-size: 100%; block-size: 100%; overflow: hidden; border-radius: var(--hjm-image-choice-radius); background: var(--hjm-color-surface); }
@@ -3303,6 +3449,8 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3303
3449
  .hjm-comment-row__reactions:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: 2px; }
3304
3450
  .hjm-comment-row__media { align-self: flex-start; overflow: hidden; margin-block-start: 4px; }
3305
3451
  .hjm-comment-row__media > img { display: block; inline-size: 100%; block-size: auto; }
3452
+ .hjm-comment-row__album { align-self: flex-start; margin-block-start: 4px; }
3453
+ .hjm-comment-row__panel { min-inline-size: 0; }
3306
3454
  /* commentThreadRecipe.actionAnchor (2026-10-17 measured audit): the 44 targets centre on the identity's first line
3307
3455
  (--hjm-comment-anchor-line, set per row), so like/more glyphs share the author line's centre instead of sitting
3308
3456
  12px below it. When HJM's own like/more is the last control (data-edge), the whole column takes the IconButton edge
@@ -3416,6 +3564,17 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3416
3564
  .hjm-card[data-interactive="true"] { font: inherit; color: inherit; text-align: inherit; cursor: pointer; min-inline-size: var(--hjm-control-min-touch-target); min-block-size: var(--hjm-control-min-touch-target); }
3417
3565
  .hjm-card[data-interactive="true"]:active:not(:disabled) { opacity: var(--hjm-card-pressed-opacity); }
3418
3566
  .hjm-card[data-interactive="true"]:disabled { opacity: var(--hjm-card-disabled-opacity); cursor: default; }
3567
+ /* Whole-card activation beside actions: the article keeps its semantics, a transparent button covers it, and only the
3568
+ action controls rise above that button, so the gaps around them still open the card. */
3569
+ .hjm-card[data-activation="overlay"] { position: relative; }
3570
+ .hjm-card__activator { position: absolute; inset: 0; z-index: 0; margin: 0; padding: 0; border: 0; border-radius: inherit; background: transparent; cursor: pointer; }
3571
+ .hjm-card__activator:disabled { cursor: default; }
3572
+ .hjm-card[data-activation="overlay"] > .hjm-card__actions { position: relative; z-index: 1; pointer-events: none; }
3573
+ .hjm-card[data-activation="overlay"] > .hjm-card__actions > * { pointer-events: auto; }
3574
+ /* Press/disabled feedback belongs to the card target (media and body); action controls keep their own states. */
3575
+ .hjm-card[data-activation="overlay"]:has(> .hjm-card__activator:active:not(:disabled)) > :is(.hjm-card__media, .hjm-card__body) { opacity: var(--hjm-card-pressed-opacity); }
3576
+ .hjm-card[data-activation="overlay"]:has(> .hjm-card__activator:disabled) > :is(.hjm-card__media, .hjm-card__body) { opacity: var(--hjm-card-disabled-opacity); }
3577
+ .hjm-card[data-activation="overlay"]:has(> .hjm-card__activator:focus-visible) { outline: var(--hjm-focus-width) solid var(--hjm-color-focus); outline-offset: var(--hjm-focus-offset); }
3419
3578
 
3420
3579
  /* Full destination cards keep all names and widths stable; colors remain product semantic roles. */
3421
3580
  .hjm-bottom-navigation[data-selection="tile"] .hjm-bottom-navigation__surface { background: var(--hjm-color-surface-accent); }