@hjmds/react 1.30.0 → 1.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.
Files changed (91) 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-navigation.d.ts +7 -0
  6. package/dist/bottom-navigation.d.ts.map +1 -1
  7. package/dist/bottom-navigation.js +64 -8
  8. package/dist/bottom-navigation.js.map +1 -1
  9. package/dist/carousel.d.ts +29 -1
  10. package/dist/carousel.d.ts.map +1 -1
  11. package/dist/carousel.js +27 -3
  12. package/dist/carousel.js.map +1 -1
  13. package/dist/display.d.ts +8 -2
  14. package/dist/display.d.ts.map +1 -1
  15. package/dist/display.js +12 -7
  16. package/dist/display.js.map +1 -1
  17. package/dist/evidence.d.ts +1 -1
  18. package/dist/evidence.js +1 -1
  19. package/dist/evidence.js.map +1 -1
  20. package/dist/forms.d.ts.map +1 -1
  21. package/dist/forms.js +2 -1
  22. package/dist/forms.js.map +1 -1
  23. package/dist/image-choice.d.ts +5 -0
  24. package/dist/image-choice.d.ts.map +1 -1
  25. package/dist/image-choice.js +4 -3
  26. package/dist/image-choice.js.map +1 -1
  27. package/dist/index.d.ts +3 -3
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +3 -3
  30. package/dist/index.js.map +1 -1
  31. package/dist/internal/field-error.d.ts +19 -0
  32. package/dist/internal/field-error.d.ts.map +1 -0
  33. package/dist/internal/field-error.js +19 -0
  34. package/dist/internal/field-error.js.map +1 -0
  35. package/dist/internal/initial-focus.d.ts.map +1 -1
  36. package/dist/internal/initial-focus.js +49 -7
  37. package/dist/internal/initial-focus.js.map +1 -1
  38. package/dist/internal/media-album.d.ts +49 -0
  39. package/dist/internal/media-album.d.ts.map +1 -0
  40. package/dist/internal/media-album.js +31 -0
  41. package/dist/internal/media-album.js.map +1 -0
  42. package/dist/internal/message-reactions.d.ts +28 -2
  43. package/dist/internal/message-reactions.d.ts.map +1 -1
  44. package/dist/internal/message-reactions.js +21 -5
  45. package/dist/internal/message-reactions.js.map +1 -1
  46. package/dist/layout.d.ts +19 -3
  47. package/dist/layout.d.ts.map +1 -1
  48. package/dist/layout.js +10 -3
  49. package/dist/layout.js.map +1 -1
  50. package/dist/navigation.d.ts +3 -1
  51. package/dist/navigation.d.ts.map +1 -1
  52. package/dist/navigation.js +4 -2
  53. package/dist/navigation.js.map +1 -1
  54. package/dist/number-field.d.ts.map +1 -1
  55. package/dist/number-field.js +2 -1
  56. package/dist/number-field.js.map +1 -1
  57. package/dist/overlays.d.ts +65 -0
  58. package/dist/overlays.d.ts.map +1 -1
  59. package/dist/overlays.js +36 -3
  60. package/dist/overlays.js.map +1 -1
  61. package/dist/public-exports.generated.d.ts +7 -0
  62. package/dist/public-exports.generated.d.ts.map +1 -1
  63. package/dist/public-exports.generated.js +21 -0
  64. package/dist/public-exports.generated.js.map +1 -1
  65. package/dist/screen-flows.d.ts +104 -12
  66. package/dist/screen-flows.d.ts.map +1 -1
  67. package/dist/screen-flows.js +87 -18
  68. package/dist/screen-flows.js.map +1 -1
  69. package/dist/screens.d.ts +59 -8
  70. package/dist/screens.d.ts.map +1 -1
  71. package/dist/screens.js +57 -15
  72. package/dist/screens.js.map +1 -1
  73. package/dist/select.d.ts.map +1 -1
  74. package/dist/select.js +2 -1
  75. package/dist/select.js.map +1 -1
  76. package/dist/selection.d.ts.map +1 -1
  77. package/dist/selection.js +3 -2
  78. package/dist/selection.js.map +1 -1
  79. package/dist/steps.d.ts +11 -1
  80. package/dist/steps.d.ts.map +1 -1
  81. package/dist/steps.js +11 -6
  82. package/dist/steps.js.map +1 -1
  83. package/dist/styles.css +139 -20
  84. package/dist/styles.layered.css +139 -20
  85. package/dist/supplemental-display.d.ts.map +1 -1
  86. package/dist/supplemental-display.js +2 -48
  87. package/dist/supplemental-display.js.map +1 -1
  88. package/dist/theme.d.ts.map +1 -1
  89. package/dist/theme.js +1 -0
  90. package/dist/theme.js.map +1 -1
  91. 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":"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","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;
@@ -600,6 +600,18 @@
600
600
  inset-block-start: 50%;
601
601
  translate: -50% -50%;
602
602
  }
603
+ /* Prominent center destination: bottomNavigationRecipe.prominentDestination, lifted so its label stays on the
604
+ other labels' line. The circle keeps brand fill in both states; the label carries selection. */
605
+ .hjm-bottom-navigation li[data-prominent="true"] .hjm-bottom-navigation__indicator {
606
+ inline-size: var(--hjm-bottom-navigation-prominent-diameter);
607
+ block-size: var(--hjm-bottom-navigation-prominent-diameter);
608
+ margin-block-start: calc(-1 * var(--hjm-bottom-navigation-prominent-lift));
609
+ border-radius: var(--hjm-radius-full);
610
+ color: var(--hjm-color-on-primary);
611
+ background: var(--hjm-color-primary);
612
+ box-shadow: var(--hjm-shadow-floating);
613
+ }
614
+ .hjm-bottom-navigation li[data-prominent="true"] .hjm-bottom-navigation__indicator > :first-child { transform: none; }
603
615
 
604
616
  .hjm-number-field__control {
605
617
  padding-inline: 0;
@@ -1719,6 +1731,22 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1719
1731
  AuthScreenLayout safeAreaBottom (2026-10-17 1.28 adoption: products wrapped the layout in their own inset). */
1720
1732
  padding-block-end: max(var(--hjm-auth-screen-padding-block), env(safe-area-inset-bottom, 0px));
1721
1733
  }
1734
+ /* With title/exit the layout owns the ScreenLayout centered header band: full-bleed over the inline padding, at the
1735
+ shared screen gutter, with the former top padding moved below it (2026-10-18: products kept a TopBar above). */
1736
+ .hjm-auth-screen[data-header] { padding-block-start: 0; }
1737
+ .hjm-auth-screen__header {
1738
+ display: grid;
1739
+ grid-template-columns: 1fr minmax(0, auto) 1fr;
1740
+ align-items: center;
1741
+ box-sizing: border-box;
1742
+ min-block-size: var(--hjm-auth-screen-header-min-height);
1743
+ margin-inline: calc(var(--hjm-auth-screen-padding-inline) * -1);
1744
+ margin-block-end: var(--hjm-auth-screen-padding-block);
1745
+ padding-inline: var(--hjm-auth-screen-header-padding-inline);
1746
+ padding-block: var(--hjm-auth-screen-header-padding-block);
1747
+ }
1748
+ .hjm-auth-screen__header-title { min-inline-size: 0; text-align: center; overflow-wrap: anywhere; }
1749
+ .hjm-auth-screen__header-title > * { margin: 0; }
1722
1750
  .hjm-auth-screen__block {
1723
1751
  display: flex;
1724
1752
  flex: 1;
@@ -1770,17 +1798,18 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1770
1798
  flex-wrap: wrap;
1771
1799
  justify-content: center;
1772
1800
  align-items: center;
1773
- column-gap: var(--hjm-auth-screen-link-gap);
1774
1801
  }
1775
- /* 2026-10-17 "간격 줄여": 보이는 간격은 gap + 구분점뿐이다. 44 터치 높이는 배경 없는 min-block-size,
1776
- 가로 터치 폭은 보이지 않는 가상 요소가 구분점 앞까지 넓힌다 — 버튼 안쪽 여백으로 만들면 글자 사이가 다시 벌어진다. */
1802
+ /* 2026-10-17 "간격 줄여": 보이는 간격은 linkGap + 구분점뿐이다. 44 터치 높이는 배경 없는 min-block-size다.
1803
+ 2026-10-18 링크 사이 간격을 flex column-gap에서 링크의 inline 여백으로 옮겼다. 보이는 간격은 같고(column-gap 0),
1804
+ 가로 터치 폭이 그 여백만큼 링크 상자 안에 든다. 예전 가상 요소는 상자 밖으로 linkGap만큼 뻗어, 긴 링크가 푸터 폭을
1805
+ 다 쓰면 푸터의 스크롤 폭을 넓혔다(웹 scenario-matrix auth-screen long-copy 288 > 282). 여백을 gap과 함께 두면
1806
+ 글자 사이가 두 배로 벌어지므로 gap은 두지 않는다. */
1777
1807
  .hjm-auth-screen__link {
1778
- position: relative;
1779
1808
  display: inline-flex;
1780
1809
  align-items: center;
1781
1810
  min-block-size: var(--hjm-auth-screen-link-target);
1782
1811
  margin: 0;
1783
- padding: 0;
1812
+ padding: 0 var(--hjm-auth-screen-link-gap);
1784
1813
  border: 0;
1785
1814
  background: none;
1786
1815
  font-family: var(--hjm-font-family-ui, inherit);
@@ -1788,12 +1817,6 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1788
1817
  text-underline-offset: 0.18em;
1789
1818
  cursor: pointer;
1790
1819
  }
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
1820
  .hjm-auth-screen__link:focus-visible {
1798
1821
  outline: var(--hjm-focus-width) solid var(--hjm-color-focus);
1799
1822
  outline-offset: var(--hjm-focus-offset);
@@ -2191,6 +2214,46 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2191
2214
  background: var(--hjm-backdrop-modal);
2192
2215
  overscroll-behavior: contain;
2193
2216
  }
2217
+ /* Dialog overlayLayer: viewport-sized, decorative, above the card (later sibling) and never a pointer target. */
2218
+ .hjm-dialog__overlay-layer {
2219
+ position: absolute;
2220
+ inset: 0;
2221
+ pointer-events: none;
2222
+ }
2223
+ /* ImmersiveOverlay: an edge-to-edge stage and control bar with no scrim, card or gutter. */
2224
+ .hjm-overlay.hjm-immersive-positioner {
2225
+ align-items: stretch;
2226
+ padding: 0;
2227
+ background: var(--hjm-immersive-background);
2228
+ }
2229
+ .hjm-immersive {
2230
+ display: flex;
2231
+ flex-direction: column;
2232
+ inline-size: 100%;
2233
+ block-size: 100%;
2234
+ color: var(--hjm-color-text);
2235
+ background: var(--hjm-immersive-background);
2236
+ outline: none;
2237
+ }
2238
+ .hjm-immersive__stage {
2239
+ position: relative;
2240
+ flex: 1 1 auto;
2241
+ min-block-size: 0;
2242
+ overflow: hidden;
2243
+ }
2244
+ .hjm-immersive__controls {
2245
+ display: flex;
2246
+ flex-direction: column;
2247
+ gap: var(--hjm-immersive-gap);
2248
+ padding: var(--hjm-immersive-padding);
2249
+ padding-inline: max(var(--hjm-immersive-padding), env(safe-area-inset-left)) max(var(--hjm-immersive-padding), env(safe-area-inset-right));
2250
+ padding-block-end: max(var(--hjm-immersive-padding), env(safe-area-inset-bottom));
2251
+ }
2252
+ .hjm-immersive__actions {
2253
+ display: flex;
2254
+ align-items: center;
2255
+ gap: var(--hjm-immersive-action-gap);
2256
+ }
2194
2257
  .hjm-dialog,
2195
2258
  .hjm-alert-dialog,
2196
2259
  .hjm-sheet {
@@ -2207,6 +2270,8 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2207
2270
  .hjm-dialog { inline-size: min(36rem, 100%); }
2208
2271
  .hjm-dialog[data-size="small"] { inline-size: min(28rem, 100%); }
2209
2272
  .hjm-dialog[data-size="large"] { inline-size: min(48rem, 100%); }
2273
+ /* Dialog scrollEnabled={false}: the card keeps its scroll offset while a gesture in the body owns the pointer. */
2274
+ .hjm-dialog[data-scroll="locked"] { overflow: hidden; overscroll-behavior: contain; touch-action: none; }
2210
2275
  .hjm-alert-dialog[data-size="small"] { inline-size: min(28rem, 100%); }
2211
2276
  .hjm-alert-dialog[data-size="large"] { inline-size: min(48rem, 100%); }
2212
2277
  .hjm-dialog__header,
@@ -2748,6 +2813,9 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2748
2813
  .hjm-steps__step[data-status="pending"] .hjm-steps__label { color: var(--hjm-color-text-muted); }
2749
2814
  .hjm-steps__step[data-status="error"] .hjm-steps__label { color: var(--hjm-color-danger); }
2750
2815
  .hjm-steps__description { color: var(--hjm-color-text-muted); font-size: var(--hjm-type-caption-size); overflow-wrap: anywhere; }
2816
+ /* A step before the cursor is one button over marker and label (Steps onStepPress); the cell keeps its static layout. */
2817
+ .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; }
2818
+ .hjm-steps__press:focus-visible { outline: var(--hjm-focus-width) solid var(--hjm-color-focus); outline-offset: var(--hjm-focus-offset); }
2751
2819
 
2752
2820
  .hjm-file-picker { display: grid; gap: var(--hjm-space-xs); }
2753
2821
  .hjm-file-picker__label { color: var(--hjm-color-text); font-weight: var(--hjm-text-emphasis-medium); }
@@ -2934,10 +3002,12 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2934
3002
  .hjm-carousel__rotation { justify-self: start; }
2935
3003
  .hjm-carousel__controls, .hjm-carousel__dots { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: var(--hjm-space-xs); }
2936
3004
  .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; }
3005
+ /* PageIndicator is the same dot row for a product-owned pager (carouselRecipe.dot). */
3006
+ .hjm-page-indicator { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: var(--hjm-space-xs); min-inline-size: 0; }
3007
+ .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; }
3008
+ .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 */
3009
+ .hjm-carousel__dot[data-current="true"] > span, .hjm-page-indicator__dot[data-current="true"] > span { background: var(--hjm-color-content-brand); }
3010
+ .hjm-carousel__dot:focus-visible, .hjm-page-indicator__dot:focus-visible { outline: 2px solid var(--hjm-color-content-brand); outline-offset: 2px; }
2941
3011
 
2942
3012
  /* The viewport action follows the content in DOM order. Its measured clearance
2943
3013
  belongs on the scroll content, not on the fixed button (docs/floating-action-button.md). */
@@ -3113,6 +3183,16 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3113
3183
  .hjm-tabs:is([data-appearance="gooey"], [data-appearance="slide"]) .hjm-tabs__list { position: relative; }
3114
3184
  .hjm-tabs:is([data-appearance="gooey"], [data-appearance="slide"]) .hjm-tabs__tab[data-state="selected"]::after { display: none; }
3115
3185
  .hjm-tabs__indicator { position: absolute; bottom: 0; height: 2px; border-radius: var(--hjm-radius-full); background: var(--hjm-color-content-brand); pointer-events: none; }
3186
+ /* indicator="filled" (tabsRecipe.indicators.filled): a surface-accent pill behind the selected tab, no list divider.
3187
+ The moving pill spans the tab height and sits under the labels. */
3188
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__list { border: 0; }
3189
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab { border-radius: var(--hjm-radius-full); }
3190
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab[data-state="selected"] { color: var(--hjm-color-content-brand); }
3191
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab[data-state="selected"]::after { display: none; }
3192
+ .hjm-tabs[data-indicator="filled"]:not([data-indicator-motion="moving"]) .hjm-tabs__tab[data-state="selected"] { background: var(--hjm-color-surface-accent); }
3193
+ .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; }
3194
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__list { position: relative; }
3195
+ .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab { z-index: 1; }
3116
3196
  .hjm-tabs__gooey { height: 6px; }
3117
3197
 
3118
3198
  /* Capsule destinations retain route/link semantics. At larger text sizes we show
@@ -3167,6 +3247,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3167
3247
  .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
3248
  .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
3249
  .hjm-screen__heading > * { margin: 0; }
3250
+ .hjm-screen__title-leading { display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center; }
3170
3251
  .hjm-screen__notice { padding-inline: var(--hjm-screen-padding); flex: 0 0 auto; }
3171
3252
  /* --hjm-screen-body-inline is the body's inline padding and the distance a bleeding CollectionRail crosses (Native
3172
3253
  ScreenBodyInsetContext); inset variants change this one value instead of the padding itself. */
@@ -3175,7 +3256,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3175
3256
  (review S5#9: body 16 + row 16 put DM avatars ~32 from the edge). "none" keeps its host-owns-all meaning. */
3176
3257
  .hjm-screen[data-content-inset="body-edge"] > .hjm-screen__body { --hjm-screen-body-inline: 0px; }
3177
3258
  /* 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; }
3259
+ .hjm-sheet, .hjm-dialog, .hjm-alert-dialog, .hjm-popover { --hjm-screen-body-inline: 0px; --hjm-bottom-navigation-inset: 0px; }
3179
3260
  /* contentInset="none" hands every inset to the host: no footer rule, as on Native (BottomCTA then draws its own bar). */
3180
3261
  .hjm-screen[data-content-inset="none"] > .hjm-screen__footer { border-block-start: 0; }
3181
3262
  .hjm-screen__body[data-scroll="screen"] { overflow-y: auto; }
@@ -3184,7 +3265,10 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3184
3265
  .hjm-screen__centered-content { margin-block: auto; flex-shrink: 0; inline-size: 100%; }
3185
3266
  .hjm-screen__body[data-replacement="true"] { display: flex; flex-direction: column; }
3186
3267
  .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); }
3268
+ .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); }
3269
+ /* --hjm-bottom-navigation-inset (BottomNavigationInsetProvider) is the viewport bottom the fixed bar covers. The last
3270
+ region keeps its gutter above the bar; without a provider the value is 0 and the padding is unchanged. */
3271
+ .hjm-screen__body:last-child { padding-block-end: calc(var(--hjm-screen-padding) + var(--hjm-bottom-navigation-inset, 0px)); }
3188
3272
  /* SearchScreen filtersOverflow=scroll; why: screen-flows.tsx. Block padding keeps focus rings unclipped.
3189
3273
  --hjm-search-filters-bleed = screen padding + hostGutter (1.13.1), set inline by SearchScreen. */
3190
3274
  .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 +3316,17 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3232
3316
  [dir="rtl"] .hjm-chat-message[data-tail] .hjm-chat-message__bubble::before { transform: scaleX(-1); }
3233
3317
 
3234
3318
  .hjm-chat-message__meta { display: flex; flex-shrink: 0; align-items: center; gap: var(--hjm-space-xs); }
3319
+ /* Reaction summary pill (sizes inline from chatMessageRecipe.reactionSummary); own reaction shows the brand border. */
3320
+ .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; }
3321
+ .hjm-chat-message__reaction-summary[data-selected] { border-color: var(--hjm-color-content-brand); }
3322
+ .hjm-chat-message__reaction-summary:hover:not(:disabled) { background: var(--hjm-color-surface-alt); }
3323
+ .hjm-chat-message__reaction-summary:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: 2px; }
3324
+ .hjm-chat-message__reaction-summary:disabled { opacity: 0.5; cursor: default; }
3325
+ /* Typing dots (typingIndicatorRecipe via custom properties): staggered pulse, static under reduced motion. */
3326
+ .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; }
3327
+ .hjm-typing-indicator[data-reduced-motion] .hjm-typing-indicator__dot { animation: none; }
3328
+ @media (prefers-reduced-motion: reduce) { .hjm-typing-indicator__dot { animation: none; } }
3329
+ @keyframes hjm-typing-pulse { 0%, 100% { opacity: var(--hjm-typing-rest); } 50% { opacity: 1; } }
3235
3330
  /* A quoted reply heads the bubble and is divided from the answer. */
3236
3331
  .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
3332
  .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 +3337,13 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3242
3337
  .hjm-chat-message__media-item > img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
3243
3338
  button.hjm-chat-message__media-item { cursor: pointer; }
3244
3339
  button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: -2px; }
3340
+ /* Paged media album (2026-10-18, mediaAlbumRecipe): one mounted page in the host's media width, counter row below. */
3341
+ .hjm-media-album { display: flex; flex-direction: column; max-inline-size: 100%; }
3342
+ .hjm-media-album__page { display: block; inline-size: 100%; overflow: hidden; padding: 0; border: 0; background: var(--hjm-color-surface-alt); }
3343
+ .hjm-media-album__page > img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
3344
+ button.hjm-media-album__page { cursor: pointer; }
3345
+ button.hjm-media-album__page:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: -2px; }
3346
+ .hjm-media-album__controls { display: flex; align-items: center; justify-content: space-between; }
3245
3347
  .hjm-chat-message__bubble[data-photo-only] { background: none; padding: 0; }
3246
3348
  .hjm-chat-message__bubble[data-photo-only]::before { display: none; }
3247
3349
  /* Only the shared default gutter narrows to the timeline gutter; "none" and "body-edge" keep their own meaning. */
@@ -3254,8 +3356,12 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3254
3356
  .hjm-image-choice { display: flex; flex-direction: column; gap: var(--hjm-space-xs); }
3255
3357
  .hjm-image-choice__label { font-weight: var(--hjm-text-emphasis-medium); }
3256
3358
  .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); }
3359
+ /* The button is a column (ring frame, optional item label) so the label shares the tap target; the ring is drawn on
3360
+ the inner frame (2026-10-18 showItemLabels). */
3361
+ .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; }
3362
+ .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; }
3363
+ .hjm-image-choice__tile[data-selected] .hjm-image-choice__ring { border-color: var(--hjm-color-content-brand); }
3364
+ .hjm-image-choice__item-label { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
3259
3365
  .hjm-image-choice__tile:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: 2px; }
3260
3366
  .hjm-image-choice__tile:disabled { opacity: 0.5; cursor: default; }
3261
3367
  .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 +3409,8 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3303
3409
  .hjm-comment-row__reactions:focus-visible { outline: 2px solid var(--hjm-color-focus, currentColor); outline-offset: 2px; }
3304
3410
  .hjm-comment-row__media { align-self: flex-start; overflow: hidden; margin-block-start: 4px; }
3305
3411
  .hjm-comment-row__media > img { display: block; inline-size: 100%; block-size: auto; }
3412
+ .hjm-comment-row__album { align-self: flex-start; margin-block-start: 4px; }
3413
+ .hjm-comment-row__panel { min-inline-size: 0; }
3306
3414
  /* commentThreadRecipe.actionAnchor (2026-10-17 measured audit): the 44 targets centre on the identity's first line
3307
3415
  (--hjm-comment-anchor-line, set per row), so like/more glyphs share the author line's centre instead of sitting
3308
3416
  12px below it. When HJM's own like/more is the last control (data-edge), the whole column takes the IconButton edge
@@ -3416,6 +3524,17 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3416
3524
  .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
3525
  .hjm-card[data-interactive="true"]:active:not(:disabled) { opacity: var(--hjm-card-pressed-opacity); }
3418
3526
  .hjm-card[data-interactive="true"]:disabled { opacity: var(--hjm-card-disabled-opacity); cursor: default; }
3527
+ /* Whole-card activation beside actions: the article keeps its semantics, a transparent button covers it, and only the
3528
+ action controls rise above that button, so the gaps around them still open the card. */
3529
+ .hjm-card[data-activation="overlay"] { position: relative; }
3530
+ .hjm-card__activator { position: absolute; inset: 0; z-index: 0; margin: 0; padding: 0; border: 0; border-radius: inherit; background: transparent; cursor: pointer; }
3531
+ .hjm-card__activator:disabled { cursor: default; }
3532
+ .hjm-card[data-activation="overlay"] > .hjm-card__actions { position: relative; z-index: 1; pointer-events: none; }
3533
+ .hjm-card[data-activation="overlay"] > .hjm-card__actions > * { pointer-events: auto; }
3534
+ /* Press/disabled feedback belongs to the card target (media and body); action controls keep their own states. */
3535
+ .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); }
3536
+ .hjm-card[data-activation="overlay"]:has(> .hjm-card__activator:disabled) > :is(.hjm-card__media, .hjm-card__body) { opacity: var(--hjm-card-disabled-opacity); }
3537
+ .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
3538
 
3420
3539
  /* Full destination cards keep all names and widths stable; colors remain product semantic roles. */
3421
3540
  .hjm-bottom-navigation[data-selection="tile"] .hjm-bottom-navigation__surface { background: var(--hjm-color-surface-accent); }