@hjmds/react 1.37.2 → 1.38.1

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 (61) hide show
  1. package/dist/actions.d.ts +8 -0
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +4 -4
  4. package/dist/actions.js.map +1 -1
  5. package/dist/authoring-tools.js +1 -1
  6. package/dist/authoring-tools.js.map +1 -1
  7. package/dist/bottom-cta.d.ts.map +1 -1
  8. package/dist/bottom-cta.js +2 -1
  9. package/dist/bottom-cta.js.map +1 -1
  10. package/dist/carousel-motion.js +1 -1
  11. package/dist/carousel-motion.js.map +1 -1
  12. package/dist/collection-editing.js +4 -4
  13. package/dist/collection-editing.js.map +1 -1
  14. package/dist/collection-rail.d.ts +3 -1
  15. package/dist/collection-rail.d.ts.map +1 -1
  16. package/dist/collection-rail.js +2 -2
  17. package/dist/collection-rail.js.map +1 -1
  18. package/dist/display.d.ts +4 -0
  19. package/dist/display.d.ts.map +1 -1
  20. package/dist/display.js +2 -2
  21. package/dist/display.js.map +1 -1
  22. package/dist/evidence.d.ts +1 -1
  23. package/dist/evidence.js +1 -1
  24. package/dist/evidence.js.map +1 -1
  25. package/dist/inline-confirm.js +1 -1
  26. package/dist/inline-confirm.js.map +1 -1
  27. package/dist/internal/document-resource.js +1 -1
  28. package/dist/internal/document-resource.js.map +1 -1
  29. package/dist/internal/screen-exit.js +2 -2
  30. package/dist/internal/screen-exit.js.map +1 -1
  31. package/dist/notification-bell.js +1 -1
  32. package/dist/notification-bell.js.map +1 -1
  33. package/dist/screen-flows.d.ts +1 -0
  34. package/dist/screen-flows.d.ts.map +1 -1
  35. package/dist/screen-flows.js +3 -3
  36. package/dist/screen-flows.js.map +1 -1
  37. package/dist/screens.d.ts +5 -1
  38. package/dist/screens.d.ts.map +1 -1
  39. package/dist/screens.js +7 -3
  40. package/dist/screens.js.map +1 -1
  41. package/dist/selection.d.ts +4 -0
  42. package/dist/selection.d.ts.map +1 -1
  43. package/dist/selection.js +2 -2
  44. package/dist/selection.js.map +1 -1
  45. package/dist/sortable.js +1 -1
  46. package/dist/sortable.js.map +1 -1
  47. package/dist/step-player.js +1 -1
  48. package/dist/step-player.js.map +1 -1
  49. package/dist/steps.js +1 -1
  50. package/dist/steps.js.map +1 -1
  51. package/dist/streaming-answer.js +1 -1
  52. package/dist/streaming-answer.js.map +1 -1
  53. package/dist/styles.css +36 -22
  54. package/dist/styles.layered.css +36 -22
  55. package/dist/task-controls.js +2 -2
  56. package/dist/task-controls.js.map +1 -1
  57. package/dist/transfer-task.js +1 -1
  58. package/dist/transfer-task.js.map +1 -1
  59. package/dist/voice-note.js +1 -1
  60. package/dist/voice-note.js.map +1 -1
  61. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"streaming-answer.js","sourceRoot":"","sources":["../src/streaming-answer.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { consumeAnswerStream, type AnswerStreamEvent, type AnswerGenerationContext } from \"@hjmds/design-contracts/streaming-answer\";\nimport { Button } from \"./actions.js\";\nimport { Progress } from \"./feedback.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\nexport type StreamingAnswerProps = Readonly<{\n answerId: string; initialText?: string; label: string;\n onGenerate: (context: AnswerGenerationContext) => AsyncIterable<AnswerStreamEvent> | Promise<AsyncIterable<AnswerStreamEvent>>;\n labels: Readonly<{ generate: string; regenerate: string; stop: string; thinking: string; streaming: string; stopping: string; complete: string; stopped: string; error: string }>;\n thinkingIndicator?: ReactNode; renderText?: (text: string) => ReactNode; disabled?: boolean;\n}>;\ntype Phase = \"idle\" | \"thinking\" | \"streaming\" | \"stopping\" | \"complete\" | \"stopped\" | \"error\";\nexport function StreamingAnswer(props: StreamingAnswerProps) {\n if (!props.answerId.trim()) throw new TypeError(\"Streaming answers require a stable id\");\n return <AnswerBody key={props.answerId} {...props} />;\n}\nfunction AnswerBody(props: StreamingAnswerProps) {\n const [text, setText] = useState(props.initialText ?? \"\"), [phase, setPhase] = useState<Phase>(props.initialText ? \"complete\" : \"idle\");\n const action = useCompositionAction(props.disabled), request = useRef<AbortController | null>(null), mounted = useRef(true), disabled = useRef(props.disabled), requestWasRegenerate = useRef(false);\n disabled.current = props.disabled;\n useEffect(() => { mounted.current = true; return () => { mounted.current = false; request.current?.abort(); request.current = null; }; }, []);\n const generate = () => {\n if (!mounted.current || disabled.current || request.current) return;\n const controller = new AbortController(); request.current = controller;\n const previousText = text, regenerate = phase !== \"idle\"; requestWasRegenerate.current = regenerate;\n setPhase(\"thinking\");\n const current = () => mounted.current && request.current === controller;\n void action.run(\"generate\", async () => {\n try {\n const source = await props.onGenerate({ signal: controller.signal, previousText, regenerate });\n const result = await consumeAnswerStream(source, controller.signal, previousText, value => { if (current()) { setText(value); setPhase(\"streaming\"); } });\n if (current()) { setText(result.text); setPhase(result.phase); }\n } catch (error) {\n const aborted = controller.signal.aborted && error && typeof error === \"object\" && \"name\" in error && error.name === \"AbortError\";\n if (current()) setPhase(aborted ? \"stopped\" : \"error\");\n throw error;\n }\n }).then(() => { if (request.current === controller) request.current = null; });\n };\n const stop = () => { if (mounted.current && !disabled.current && request.current && !request.current.signal.aborted) { setPhase(\"stopping\"); request.current.abort(); } };\n const pending = action.pending;\n const status = phase === \"idle\" ? null : props.labels[phase];\n return <Stack gap=\"md\"><Text emphasis=\"strong\">{props.label}</Text>\n {text ? (props.renderText ? props.renderText(text) : <Text style={{ whiteSpace: \"pre-wrap\", overflowWrap: \"anywhere\" }}>{text}</Text>) : null}\n {phase === \"thinking\" ? <Stack align=\"center\" justify=\"center\">{props.thinkingIndicator ?? <Progress label={props.labels.thinking} />}</Stack> : null}\n <Stack axis=\"inline\" gap=\"sm\" wrap><Button loading={pending} disabled={props.disabled === true} onClick={generate}>{(pending ? requestWasRegenerate.current : phase !== \"idle\") ? props.labels.regenerate : props.labels.generate}</Button>\n {pending ? <Button tone=\"secondary\" disabled={props.disabled === true || phase === \"stopping\"} onClick={stop}>{props.labels.stop}</Button> : null}</Stack>\n {status && phase !== \"thinking\" ? <Text role={phase === \"error\" ? \"alert\" : \"status\"} style={{ textAlign: \"center\" }} tone={phase === \"error\" ? \"danger\" : \"primary\"}>{status}</Text> : null}\n </Stack>;\n}\n"]}
1
+ {"version":3,"file":"streaming-answer.js","sourceRoot":"","sources":["../src/streaming-answer.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { consumeAnswerStream, type AnswerStreamEvent, type AnswerGenerationContext } from \"@hjmds/design-contracts/streaming-answer\";\nimport { Button } from \"./actions.js\";\nimport { Progress } from \"./feedback.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\nexport type StreamingAnswerProps = Readonly<{\n answerId: string; initialText?: string; label: string;\n onGenerate: (context: AnswerGenerationContext) => AsyncIterable<AnswerStreamEvent> | Promise<AsyncIterable<AnswerStreamEvent>>;\n labels: Readonly<{ generate: string; regenerate: string; stop: string; thinking: string; streaming: string; stopping: string; complete: string; stopped: string; error: string }>;\n thinkingIndicator?: ReactNode; renderText?: (text: string) => ReactNode; disabled?: boolean;\n}>;\ntype Phase = \"idle\" | \"thinking\" | \"streaming\" | \"stopping\" | \"complete\" | \"stopped\" | \"error\";\nexport function StreamingAnswer(props: StreamingAnswerProps) {\n if (!props.answerId.trim()) throw new TypeError(\"Streaming answers require a stable id\");\n return <AnswerBody key={props.answerId} {...props} />;\n}\nfunction AnswerBody(props: StreamingAnswerProps) {\n const [text, setText] = useState(props.initialText ?? \"\"), [phase, setPhase] = useState<Phase>(props.initialText ? \"complete\" : \"idle\");\n const action = useCompositionAction(props.disabled), request = useRef<AbortController | null>(null), mounted = useRef(true), disabled = useRef(props.disabled), requestWasRegenerate = useRef(false);\n disabled.current = props.disabled;\n useEffect(() => { mounted.current = true; return () => { mounted.current = false; request.current?.abort(); request.current = null; }; }, []);\n const generate = () => {\n if (!mounted.current || disabled.current || request.current) return;\n const controller = new AbortController(); request.current = controller;\n const previousText = text, regenerate = phase !== \"idle\"; requestWasRegenerate.current = regenerate;\n setPhase(\"thinking\");\n const current = () => mounted.current && request.current === controller;\n void action.run(\"generate\", async () => {\n try {\n const source = await props.onGenerate({ signal: controller.signal, previousText, regenerate });\n const result = await consumeAnswerStream(source, controller.signal, previousText, value => { if (current()) { setText(value); setPhase(\"streaming\"); } });\n if (current()) { setText(result.text); setPhase(result.phase); }\n } catch (error) {\n const aborted = controller.signal.aborted && error && typeof error === \"object\" && \"name\" in error && error.name === \"AbortError\";\n if (current()) setPhase(aborted ? \"stopped\" : \"error\");\n throw error;\n }\n }).then(() => { if (request.current === controller) request.current = null; });\n };\n const stop = () => { if (mounted.current && !disabled.current && request.current && !request.current.signal.aborted) { setPhase(\"stopping\"); request.current.abort(); } };\n const pending = action.pending;\n const status = phase === \"idle\" ? null : props.labels[phase];\n return <Stack gap=\"md\"><Text emphasis=\"strong\">{props.label}</Text>\n {text ? (props.renderText ? props.renderText(text) : <Text style={{ whiteSpace: \"pre-wrap\", overflowWrap: \"anywhere\" }}>{text}</Text>) : null}\n {phase === \"thinking\" ? <Stack align=\"center\" justify=\"center\">{props.thinkingIndicator ?? <Progress label={props.labels.thinking} />}</Stack> : null}\n <Stack axis=\"inline\" gap=\"xs\" wrap><Button loading={pending} disabled={props.disabled === true} onClick={generate}>{(pending ? requestWasRegenerate.current : phase !== \"idle\") ? props.labels.regenerate : props.labels.generate}</Button>\n {pending ? <Button tone=\"secondary\" disabled={props.disabled === true || phase === \"stopping\"} onClick={stop}>{props.labels.stop}</Button> : null}</Stack>\n {status && phase !== \"thinking\" ? <Text role={phase === \"error\" ? \"alert\" : \"status\"} style={{ textAlign: \"center\" }} tone={phase === \"error\" ? \"danger\" : \"primary\"}>{status}</Text> : null}\n </Stack>;\n}\n"]}
package/dist/styles.css CHANGED
@@ -245,7 +245,7 @@
245
245
  .hjm-button[data-size="small"]::after {
246
246
  content: "";
247
247
  position: absolute;
248
- inset: calc(-1 * var(--hjm-space-xxs));
248
+ inset: calc(-1 * var(--hjm-button-hit-slop, var(--hjm-space-xxs)));
249
249
  }
250
250
  /* Shape and label alignment are recipe axes; a product must not override
251
251
  border-radius or justify-content on the control itself. */
@@ -315,7 +315,7 @@
315
315
  }
316
316
  .hjm-icon-button[data-shape="circle"] { border-radius: var(--hjm-radius-full); }
317
317
  .hjm-icon-button[data-size="small"] { inline-size: var(--hjm-control-button-small); block-size: var(--hjm-control-button-small); min-inline-size: 0; min-block-size: 0; }
318
- .hjm-icon-button[data-size="small"]::after { content: ""; position: absolute; inset: calc(-1 * var(--hjm-space-xxs)); }
318
+ .hjm-icon-button[data-size="small"]::after { content: ""; position: absolute; inset: calc(-1 * var(--hjm-button-hit-slop, var(--hjm-space-xxs))); }
319
319
  .hjm-icon-button[data-size="large"] { inline-size: var(--hjm-control-button-large); block-size: var(--hjm-control-button-large); }
320
320
  /* Edge alignment (iconButtonRecipe.edge / resolveButtonEdgeOverhang, 2026-10-17 measured audit): a ghost control on a
321
321
  container edge keeps its hit area but overhangs that edge, so its glyph slot or label sits on the inset like text.
@@ -332,8 +332,8 @@
332
332
  :is(.hjm-icon-button, .hjm-button):is([data-edge="end"], [data-edge="both"]) { margin-inline-end: calc(-1 * var(--hjm-edge-overhang, 0px)); }
333
333
  /* NotificationBell wraps a default (ghost, medium) IconButton with a badge anchored to its own end, so the bell root
334
334
  takes the same overhang and the badge keeps the icon's corner (2026-10-17 1.27.2 adoption: Spint's top-bar bell). */
335
- .hjm-notification-bell:is([data-edge="start"], [data-edge="both"]) { margin-inline-start: calc(-1 * var(--hjm-icon-button-edge-medium)); }
336
- .hjm-notification-bell:is([data-edge="end"], [data-edge="both"]) { margin-inline-end: calc(-1 * var(--hjm-icon-button-edge-medium)); }
335
+ .hjm-notification-bell:is([data-edge="start"], [data-edge="both"]) { margin-inline-start: calc(-1 * var(--hjm-icon-button-edge-small)); }
336
+ .hjm-notification-bell:is([data-edge="end"], [data-edge="both"]) { margin-inline-end: calc(-1 * var(--hjm-icon-button-edge-small)); }
337
337
  .hjm-link { display: inline-flex; align-items: center; gap: var(--hjm-space-xxs); max-inline-size: 100%; min-inline-size: 0; overflow-wrap: anywhere; color: var(--hjm-color-content-brand); text-underline-offset: 0.18em; }
338
338
  .hjm-link[data-tone="neutral"] { color: var(--hjm-color-text-body); }
339
339
  .hjm-link[data-variant="standalone"] { min-block-size: var(--hjm-control-min-touch-target); min-inline-size: var(--hjm-control-min-touch-target); justify-content: center; text-decoration: none; }
@@ -1020,7 +1020,7 @@
1020
1020
  /* cardRecipe.title.emphasis "strong": without a weight the h2–h6 element fell back to the UA bold (700). */
1021
1021
  .hjm-card__title { margin: 0; color: var(--hjm-color-text); font-size: var(--hjm-type-title-size); line-height: var(--hjm-type-title-line-height); font-weight: var(--hjm-text-emphasis-strong); overflow-wrap: anywhere; }
1022
1022
  .hjm-card__description { margin: 0; overflow-wrap: anywhere; }
1023
- .hjm-card__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-space-sm); padding: 0 var(--hjm-space-md) var(--hjm-space-md); }
1023
+ .hjm-card__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-space-xs); padding: 0 var(--hjm-space-md) var(--hjm-space-md); }
1024
1024
  /* Row rhythm reads the recipe density variables so the stylesheet does not
1025
1025
  carry a second copy of the same numbers. */
1026
1026
  /* Inline placement (listRowRecipe.surfaceBleed, 2026-10-17 user decision): the row text sits on the container's content
@@ -1135,14 +1135,14 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1135
1135
  .hjm-statistic[data-density="compact"] { gap: var(--hjm-space-xxs); }
1136
1136
  .hjm-statistic[data-density="compact"][data-presentation="surface"] { padding: var(--hjm-space-sm); }
1137
1137
  /* Text variants follow statisticRecipe as Native does: label `label` (compact `caption`) semibold, value `heading`
1138
- (compact `title`) heavy, affix `body` semibold in content.body, hint and trend `caption` (trend bold). Until 2026-10-06
1138
+ (compact `bodyLarge`) heavy, affix `body` semibold in content.body, hint and trend `caption` (trend bold). Until 2026-10-06
1139
1139
  Web drew a body-size medium label, a title-size value at both densities (scaled twice by --hjm-text-scale, which the
1140
1140
  --hjm-type-* sizes already include) and muted affixes. */
1141
1141
  .hjm-statistic__label { color: var(--hjm-color-text-muted); font-size: var(--hjm-type-label-size); line-height: var(--hjm-type-label-line-height); font-weight: var(--hjm-text-emphasis-medium); overflow-wrap: anywhere; }
1142
1142
  .hjm-statistic[data-density="compact"] .hjm-statistic__label { font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); }
1143
1143
  .hjm-statistic__value-row { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--hjm-space-xxs); min-inline-size: 0; }
1144
1144
  .hjm-statistic__value { color: var(--hjm-statistic-value-color, var(--hjm-color-text)); font-size: var(--hjm-type-heading-size); line-height: var(--hjm-type-heading-line-height); font-weight: var(--hjm-font-weight-semibold); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
1145
- .hjm-statistic[data-density="compact"] .hjm-statistic__value { font-size: var(--hjm-type-title-size); line-height: var(--hjm-type-title-line-height); }
1145
+ .hjm-statistic[data-density="compact"] .hjm-statistic__value { font-size: var(--hjm-type-body-large-size); line-height: var(--hjm-type-body-large-line-height); }
1146
1146
  .hjm-statistic__affix { color: var(--hjm-color-text-body); font-size: var(--hjm-type-body-size); line-height: var(--hjm-type-body-line-height); font-weight: var(--hjm-text-emphasis-medium); }
1147
1147
  .hjm-statistic__hint { color: var(--hjm-color-text-muted); font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); }
1148
1148
  .hjm-statistic__trend { display: inline-flex; align-items: center; gap: var(--hjm-space-xxs); font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); font-weight: var(--hjm-text-emphasis-medium); overflow-wrap: anywhere; }
@@ -1186,7 +1186,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1186
1186
  .hjm-result[data-status="info"] .hjm-result__icon { color: var(--hjm-accent-info); background: var(--hjm-color-bg); }
1187
1187
  .hjm-result__title { margin: 0; color: var(--hjm-color-text); font-size: var(--hjm-type-title-large-size); line-height: var(--hjm-type-title-large-line-height); font-weight: var(--hjm-type-title-large-weight); overflow-wrap: anywhere; }
1188
1188
  .hjm-result__description { max-inline-size: 40rem; margin: 0; color: var(--hjm-color-text-muted); overflow-wrap: anywhere; }
1189
- .hjm-result__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--hjm-space-sm); margin-block-start: var(--hjm-space-xs); }
1189
+ .hjm-result__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--hjm-space-xs); margin-block-start: var(--hjm-space-xs); }
1190
1190
  .hjm-progress { display: grid; gap: var(--hjm-space-xs); }
1191
1191
  .hjm-progress__copy { display: flex; justify-content: space-between; gap: var(--hjm-space-md); color: var(--hjm-color-text-body); }
1192
1192
  /*
@@ -2583,7 +2583,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2583
2583
  flex: 0 0 auto;
2584
2584
  flex-wrap: wrap;
2585
2585
  justify-content: flex-end;
2586
- gap: var(--hjm-space-sm);
2586
+ gap: var(--hjm-space-xs);
2587
2587
  padding: var(--hjm-space-sm) var(--hjm-space-lg)
2588
2588
  max(var(--hjm-space-sm), env(safe-area-inset-bottom, 0px));
2589
2589
  }
@@ -2774,7 +2774,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2774
2774
  .hjm-form__fields { display: grid; gap: var(--hjm-space-lg); }
2775
2775
  .hjm-form[data-density="compact"] .hjm-form__fields { gap: var(--hjm-space-sm); }
2776
2776
  .hjm-form__error { margin-block-start: var(--hjm-space-sm); color: var(--hjm-color-danger); }
2777
- .hjm-form__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-space-sm); margin-block-start: var(--hjm-space-sm); }
2777
+ .hjm-form__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-space-xs); margin-block-start: var(--hjm-space-sm); }
2778
2778
 
2779
2779
  /* Advanced display ------------------------------------------------------- */
2780
2780
  /* timelineRecipe label/description are `body`; binding it here lets the rail and timestamp derive from the same line box. */
@@ -2896,10 +2896,10 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2896
2896
  .hjm-load-more__spinner { inline-size: 1em; block-size: 1em; border: 2px solid var(--hjm-color-content-brand); border-inline-end-color: transparent; border-radius: 50%; animation: hjm-spin 800ms linear infinite; }
2897
2897
 
2898
2898
  /* Expansion renderers --------------------------------------------------- */
2899
- .hjm-steps { display: grid; grid-template-columns: repeat(var(--hjm-steps-connector-count), minmax(0, 1fr)) fit-content(calc(100% / var(--hjm-steps-count))); gap: 0; margin: 0; padding: 0; list-style: none; }
2899
+ .hjm-steps { display: grid; grid-template-columns: repeat(var(--hjm-steps-connector-count), minmax(0, 1fr)) fit-content(calc(100% / var(--hjm-steps-count))); gap: 0; margin: 0; padding: 0 var(--hjm-steps-inset); list-style: none; }
2900
2900
  .hjm-steps__step { min-inline-size: 0; display: grid; align-content: start; gap: var(--hjm-space-xs); }
2901
2901
  .hjm-steps__rail { display: flex; align-items: center; min-inline-size: 0; }
2902
- .hjm-steps__indicator { flex: 0 0 auto; inline-size: 1.5rem; block-size: 1.5rem; display: grid; place-items: center; border: 1px solid var(--hjm-color-border); border-radius: 50%; color: var(--hjm-color-text-muted); background: var(--hjm-color-bg); font-size: var(--hjm-type-label-size); font-weight: var(--hjm-text-emphasis-medium); }
2902
+ .hjm-steps__indicator { flex: 0 0 auto; inline-size: var(--hjm-steps-indicator-size); block-size: var(--hjm-steps-indicator-size); display: grid; place-items: center; border: 1px solid var(--hjm-color-border); border-radius: 50%; color: var(--hjm-color-text-muted); background: var(--hjm-color-bg); font-size: var(--hjm-type-label-size); font-weight: var(--hjm-text-emphasis-medium); }
2903
2903
  .hjm-steps__connector { flex: 1 1 auto; block-size: 1px; background: var(--hjm-color-border); }
2904
2904
  .hjm-steps__step[data-status="current"] .hjm-steps__indicator { border: 2px solid var(--hjm-color-content-brand); color: var(--hjm-color-content-brand); }
2905
2905
  .hjm-steps__step[data-status="complete"] .hjm-steps__indicator { border-color: var(--hjm-accent-success); color: var(--hjm-accent-success); background: var(--hjm-color-bg); }
@@ -3076,7 +3076,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3076
3076
  /* TDS-inspired screen rhythm: shared Native recipes, content-driven height. See docs/screen-chrome.md. */
3077
3077
  /* Intrinsic action columns keep short labels readable at 320px; the title wraps
3078
3078
  in the remaining space instead of squeezing actions to equal-width tracks. */
3079
- .hjm-top-bar { display: grid; grid-template-columns: minmax(var(--hjm-control-min-touch-target), auto) minmax(0, 1fr) minmax(var(--hjm-control-min-touch-target), auto); align-items: center; min-block-size: var(--hjm-top-bar-min-height); gap: var(--hjm-top-bar-gap); padding-inline: var(--hjm-top-bar-padding); padding-block-start: max(env(safe-area-inset-top, 0px), var(--hjm-top-bar-safe-area)); background: var(--hjm-color-bg); }
3079
+ .hjm-top-bar { display: grid; grid-template-columns: minmax(var(--hjm-control-button-small), auto) minmax(0, 1fr) minmax(var(--hjm-control-button-small), auto); align-items: center; min-block-size: var(--hjm-top-bar-min-height); gap: var(--hjm-top-bar-gap); padding-inline: var(--hjm-top-bar-padding); padding-block-start: max(env(safe-area-inset-top, 0px), var(--hjm-top-bar-safe-area)); background: var(--hjm-color-bg); }
3080
3080
  .hjm-top-bar[data-surface="transparent"] { background: transparent; }
3081
3081
  .hjm-top-bar__leading, .hjm-top-bar__trailing { display: flex; flex-wrap: wrap; align-items: center; min-inline-size: 0; gap: var(--hjm-top-bar-gap); }
3082
3082
  .hjm-top-bar__trailing { justify-content: flex-end; }
@@ -3097,7 +3097,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3097
3097
  the remainder so the gap is max(footer, inset), not their sum (2026-10-17 "버튼 아래 여백이 많아"). */
3098
3098
  .hjm-bottom-cta[data-placement="screen-footer"] { position: static; box-sizing: border-box; inline-size: 100%; min-inline-size: 0; min-block-size: 0; padding: 0 0 max(0px, calc(var(--hjm-bottom-cta-safe-area) - var(--hjm-bottom-cta-parent-padding))); border: 0; background: none; box-shadow: none; }
3099
3099
  .hjm-bottom-cta__description { margin: 0; font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); color: var(--hjm-color-text-muted); overflow-wrap: anywhere; }
3100
- .hjm-bottom-cta__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-bottom-cta-gap); }
3100
+ .hjm-bottom-cta__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-bottom-cta-actions-gap); }
3101
3101
  /* bottomCtaRecipe.contentMaxWidth: the surface spans the viewport, its copy and actions stop at the reading width. */
3102
3102
  .hjm-bottom-cta__description, .hjm-bottom-cta__actions { box-sizing: border-box; inline-size: 100%; max-inline-size: var(--hjm-bottom-cta-content-max-width); margin-inline: auto; }
3103
3103
  .hjm-bottom-cta__actions > div { flex: 1 1 calc(var(--hjm-control-min-touch-target) * 3); min-inline-size: 0; }
@@ -3302,14 +3302,14 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3302
3302
  .hjm-tabs__indicator { position: absolute; bottom: 0; height: 2px; border-radius: var(--hjm-radius-full); background: var(--hjm-color-content-brand); pointer-events: none; }
3303
3303
  /* indicator="filled" (tabsRecipe.indicators.filled): a surface-accent pill behind the selected tab, no list divider.
3304
3304
  The moving pill spans the tab height and sits under the labels. */
3305
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__list { border: 0; }
3306
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab { border-radius: var(--hjm-radius-full); }
3307
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab[data-state="selected"] { color: var(--hjm-color-content-brand); }
3308
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab[data-state="selected"]::after { display: none; }
3309
- .hjm-tabs[data-indicator="filled"]:not([data-indicator-motion="moving"]) .hjm-tabs__tab[data-state="selected"] { background: var(--hjm-color-surface-accent); }
3310
- .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; }
3311
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__list { position: relative; }
3312
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab { z-index: 1; }
3305
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__list { border: 0; }
3306
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__tab { border-radius: var(--hjm-radius-full); }
3307
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__tab[data-state="selected"] { color: var(--hjm-color-content-brand); }
3308
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__tab[data-state="selected"]::after { display: none; }
3309
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]):not([data-indicator-motion="moving"]) .hjm-tabs__tab[data-state="selected"] { background: var(--hjm-color-surface-accent); }
3310
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__indicator { top: 0; bottom: 0; height: auto; border-radius: var(--hjm-radius-full); background: var(--hjm-color-surface-accent); z-index: 0; }
3311
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__list { position: relative; }
3312
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__tab { z-index: 1; }
3313
3313
  .hjm-tabs__gooey { height: 6px; }
3314
3314
 
3315
3315
  /* Capsule destinations retain route/link semantics. At larger text sizes we show
@@ -3626,6 +3626,8 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3626
3626
  /* Filter pills keep the same native radio semantics and 44px hit target. The inner
3627
3627
  surface is compact; the group wraps at large type instead of clipping labels. */
3628
3628
  .hjm-segmented[data-presentation="pills"] { background:transparent; border:0; padding:0; }
3629
+ /* Pills wrap instead of scrolling. The connected track's scrollport would clip the outer focus outline at the first/last option. */
3630
+ .hjm-segmented[data-presentation="pills"] .hjm-segmented__scroller { overflow:visible; }
3629
3631
  /* Keep the inset pill behind the whole line at large text (40px text / 28px fill was observed). Native already includes this inset as vertical padding. */
3630
3632
  .hjm-segmented[data-presentation="pills"] .hjm-segmented__items { flex-wrap:wrap; gap:var(--hjm-space-xs); overflow:visible; padding:0; border:0; border-radius:0; background:transparent; }
3631
3633
  .hjm-segmented[data-presentation="pills"] .hjm-segmented__item { padding-block:var(--hjm-space-xxs); position:relative; flex:0 1 auto; min-inline-size:0; min-block-size:max(var(--hjm-control-min-touch-target), calc(var(--hjm-segmented-height) + 2 * var(--hjm-space-xxs))); border:0; border-radius:var(--hjm-radius-full); background:transparent; box-shadow:none; padding-inline:var(--hjm-space-md); font-size:var(--hjm-type-label-size); line-height:var(--hjm-type-label-line-height); color:var(--hjm-color-text-muted); }
@@ -3722,6 +3724,7 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3722
3724
  .hjm-card__media-badges { position: absolute; inset-block-start: var(--hjm-space-sm); inset-inline-end: var(--hjm-space-sm); display: flex; flex-wrap: wrap; gap: var(--hjm-space-xs); z-index: 1; }
3723
3725
  .hjm-card__scrim { position: absolute; inset: 0; background: var(--hjm-card-background-scrim); }
3724
3726
  .hjm-card__badges { display: flex; flex-wrap: wrap; gap: var(--hjm-space-xs); }
3727
+ .hjm-card__badges[data-align="center"] { justify-content: center; }
3725
3728
  .hjm-card[data-background-media="true"] > .hjm-card__body { position: relative; flex: 0 1 auto; margin-block-start: auto; }
3726
3729
  .hjm-card[data-background-media="true"] > .hjm-card__actions { position: relative; }
3727
3730
  .hjm-card[data-background-media="true"] .hjm-card__title,
@@ -3820,3 +3823,14 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3820
3823
  /* Commands remain independent of the passive record and rail. */
3821
3824
  .hjm-timeline__item:has(> .hjm-timeline__actions) { grid-template-columns: 1rem minmax(0, 1fr) auto; }
3822
3825
  .hjm-timeline__actions { align-self: center; display: flex; align-items: center; gap: var(--hjm-space-xs); }
3826
+
3827
+ /* Strong filled tabs use one opaque contrast pair for static and moving marks. */
3828
+ .hjm-tabs[data-indicator="solid"] .hjm-tabs__tab[data-state="selected"] { color: var(--hjm-color-on-primary); }
3829
+ .hjm-tabs[data-indicator="solid"]:not([data-indicator-motion="moving"]) .hjm-tabs__tab[data-state="selected"],
3830
+ .hjm-tabs[data-indicator="solid"] .hjm-tabs__indicator { background: var(--hjm-color-primary); }
3831
+ .hjm-segmented[data-selection-tone="brand"]:not([data-presentation="text"]) .hjm-segmented__item[data-state="checked"] { color: var(--hjm-color-on-primary); background: var(--hjm-color-primary); }
3832
+ .hjm-segmented[data-selection-tone="brand"][data-presentation="pills"] .hjm-segmented__item[data-state="checked"] { background: transparent; }
3833
+ .hjm-segmented[data-selection-tone="brand"][data-presentation="pills"] .hjm-segmented__item[data-state="checked"]::before { background: var(--hjm-color-primary); border-color: var(--hjm-color-primary); }
3834
+ .hjm-segmented[data-selection-tone="brand"] .hjm-segmented__highlight { background: var(--hjm-color-primary); }
3835
+ .hjm-segmented[data-selection-tone="brand"] .hjm-segmented__items[data-highlight-ready="true"] > .hjm-segmented__item[data-state="checked"],
3836
+ .hjm-segmented[data-selection-tone="brand"][data-presentation="pills"] .hjm-segmented__items[data-highlight-ready="true"] > .hjm-segmented__item[data-state="checked"]::before { background: transparent; }
@@ -246,7 +246,7 @@
246
246
  .hjm-button[data-size="small"]::after {
247
247
  content: "";
248
248
  position: absolute;
249
- inset: calc(-1 * var(--hjm-space-xxs));
249
+ inset: calc(-1 * var(--hjm-button-hit-slop, var(--hjm-space-xxs)));
250
250
  }
251
251
  /* Shape and label alignment are recipe axes; a product must not override
252
252
  border-radius or justify-content on the control itself. */
@@ -316,7 +316,7 @@
316
316
  }
317
317
  .hjm-icon-button[data-shape="circle"] { border-radius: var(--hjm-radius-full); }
318
318
  .hjm-icon-button[data-size="small"] { inline-size: var(--hjm-control-button-small); block-size: var(--hjm-control-button-small); min-inline-size: 0; min-block-size: 0; }
319
- .hjm-icon-button[data-size="small"]::after { content: ""; position: absolute; inset: calc(-1 * var(--hjm-space-xxs)); }
319
+ .hjm-icon-button[data-size="small"]::after { content: ""; position: absolute; inset: calc(-1 * var(--hjm-button-hit-slop, var(--hjm-space-xxs))); }
320
320
  .hjm-icon-button[data-size="large"] { inline-size: var(--hjm-control-button-large); block-size: var(--hjm-control-button-large); }
321
321
  /* Edge alignment (iconButtonRecipe.edge / resolveButtonEdgeOverhang, 2026-10-17 measured audit): a ghost control on a
322
322
  container edge keeps its hit area but overhangs that edge, so its glyph slot or label sits on the inset like text.
@@ -333,8 +333,8 @@
333
333
  :is(.hjm-icon-button, .hjm-button):is([data-edge="end"], [data-edge="both"]) { margin-inline-end: calc(-1 * var(--hjm-edge-overhang, 0px)); }
334
334
  /* NotificationBell wraps a default (ghost, medium) IconButton with a badge anchored to its own end, so the bell root
335
335
  takes the same overhang and the badge keeps the icon's corner (2026-10-17 1.27.2 adoption: Spint's top-bar bell). */
336
- .hjm-notification-bell:is([data-edge="start"], [data-edge="both"]) { margin-inline-start: calc(-1 * var(--hjm-icon-button-edge-medium)); }
337
- .hjm-notification-bell:is([data-edge="end"], [data-edge="both"]) { margin-inline-end: calc(-1 * var(--hjm-icon-button-edge-medium)); }
336
+ .hjm-notification-bell:is([data-edge="start"], [data-edge="both"]) { margin-inline-start: calc(-1 * var(--hjm-icon-button-edge-small)); }
337
+ .hjm-notification-bell:is([data-edge="end"], [data-edge="both"]) { margin-inline-end: calc(-1 * var(--hjm-icon-button-edge-small)); }
338
338
  .hjm-link { display: inline-flex; align-items: center; gap: var(--hjm-space-xxs); max-inline-size: 100%; min-inline-size: 0; overflow-wrap: anywhere; color: var(--hjm-color-content-brand); text-underline-offset: 0.18em; }
339
339
  .hjm-link[data-tone="neutral"] { color: var(--hjm-color-text-body); }
340
340
  .hjm-link[data-variant="standalone"] { min-block-size: var(--hjm-control-min-touch-target); min-inline-size: var(--hjm-control-min-touch-target); justify-content: center; text-decoration: none; }
@@ -1021,7 +1021,7 @@
1021
1021
  /* cardRecipe.title.emphasis "strong": without a weight the h2–h6 element fell back to the UA bold (700). */
1022
1022
  .hjm-card__title { margin: 0; color: var(--hjm-color-text); font-size: var(--hjm-type-title-size); line-height: var(--hjm-type-title-line-height); font-weight: var(--hjm-text-emphasis-strong); overflow-wrap: anywhere; }
1023
1023
  .hjm-card__description { margin: 0; overflow-wrap: anywhere; }
1024
- .hjm-card__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-space-sm); padding: 0 var(--hjm-space-md) var(--hjm-space-md); }
1024
+ .hjm-card__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-space-xs); padding: 0 var(--hjm-space-md) var(--hjm-space-md); }
1025
1025
  /* Row rhythm reads the recipe density variables so the stylesheet does not
1026
1026
  carry a second copy of the same numbers. */
1027
1027
  /* Inline placement (listRowRecipe.surfaceBleed, 2026-10-17 user decision): the row text sits on the container's content
@@ -1136,14 +1136,14 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1136
1136
  .hjm-statistic[data-density="compact"] { gap: var(--hjm-space-xxs); }
1137
1137
  .hjm-statistic[data-density="compact"][data-presentation="surface"] { padding: var(--hjm-space-sm); }
1138
1138
  /* Text variants follow statisticRecipe as Native does: label `label` (compact `caption`) semibold, value `heading`
1139
- (compact `title`) heavy, affix `body` semibold in content.body, hint and trend `caption` (trend bold). Until 2026-10-06
1139
+ (compact `bodyLarge`) heavy, affix `body` semibold in content.body, hint and trend `caption` (trend bold). Until 2026-10-06
1140
1140
  Web drew a body-size medium label, a title-size value at both densities (scaled twice by --hjm-text-scale, which the
1141
1141
  --hjm-type-* sizes already include) and muted affixes. */
1142
1142
  .hjm-statistic__label { color: var(--hjm-color-text-muted); font-size: var(--hjm-type-label-size); line-height: var(--hjm-type-label-line-height); font-weight: var(--hjm-text-emphasis-medium); overflow-wrap: anywhere; }
1143
1143
  .hjm-statistic[data-density="compact"] .hjm-statistic__label { font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); }
1144
1144
  .hjm-statistic__value-row { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--hjm-space-xxs); min-inline-size: 0; }
1145
1145
  .hjm-statistic__value { color: var(--hjm-statistic-value-color, var(--hjm-color-text)); font-size: var(--hjm-type-heading-size); line-height: var(--hjm-type-heading-line-height); font-weight: var(--hjm-font-weight-semibold); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
1146
- .hjm-statistic[data-density="compact"] .hjm-statistic__value { font-size: var(--hjm-type-title-size); line-height: var(--hjm-type-title-line-height); }
1146
+ .hjm-statistic[data-density="compact"] .hjm-statistic__value { font-size: var(--hjm-type-body-large-size); line-height: var(--hjm-type-body-large-line-height); }
1147
1147
  .hjm-statistic__affix { color: var(--hjm-color-text-body); font-size: var(--hjm-type-body-size); line-height: var(--hjm-type-body-line-height); font-weight: var(--hjm-text-emphasis-medium); }
1148
1148
  .hjm-statistic__hint { color: var(--hjm-color-text-muted); font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); }
1149
1149
  .hjm-statistic__trend { display: inline-flex; align-items: center; gap: var(--hjm-space-xxs); font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); font-weight: var(--hjm-text-emphasis-medium); overflow-wrap: anywhere; }
@@ -1187,7 +1187,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
1187
1187
  .hjm-result[data-status="info"] .hjm-result__icon { color: var(--hjm-accent-info); background: var(--hjm-color-bg); }
1188
1188
  .hjm-result__title { margin: 0; color: var(--hjm-color-text); font-size: var(--hjm-type-title-large-size); line-height: var(--hjm-type-title-large-line-height); font-weight: var(--hjm-type-title-large-weight); overflow-wrap: anywhere; }
1189
1189
  .hjm-result__description { max-inline-size: 40rem; margin: 0; color: var(--hjm-color-text-muted); overflow-wrap: anywhere; }
1190
- .hjm-result__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--hjm-space-sm); margin-block-start: var(--hjm-space-xs); }
1190
+ .hjm-result__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--hjm-space-xs); margin-block-start: var(--hjm-space-xs); }
1191
1191
  .hjm-progress { display: grid; gap: var(--hjm-space-xs); }
1192
1192
  .hjm-progress__copy { display: flex; justify-content: space-between; gap: var(--hjm-space-md); color: var(--hjm-color-text-body); }
1193
1193
  /*
@@ -2584,7 +2584,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2584
2584
  flex: 0 0 auto;
2585
2585
  flex-wrap: wrap;
2586
2586
  justify-content: flex-end;
2587
- gap: var(--hjm-space-sm);
2587
+ gap: var(--hjm-space-xs);
2588
2588
  padding: var(--hjm-space-sm) var(--hjm-space-lg)
2589
2589
  max(var(--hjm-space-sm), env(safe-area-inset-bottom, 0px));
2590
2590
  }
@@ -2775,7 +2775,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2775
2775
  .hjm-form__fields { display: grid; gap: var(--hjm-space-lg); }
2776
2776
  .hjm-form[data-density="compact"] .hjm-form__fields { gap: var(--hjm-space-sm); }
2777
2777
  .hjm-form__error { margin-block-start: var(--hjm-space-sm); color: var(--hjm-color-danger); }
2778
- .hjm-form__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-space-sm); margin-block-start: var(--hjm-space-sm); }
2778
+ .hjm-form__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-space-xs); margin-block-start: var(--hjm-space-sm); }
2779
2779
 
2780
2780
  /* Advanced display ------------------------------------------------------- */
2781
2781
  /* timelineRecipe label/description are `body`; binding it here lets the rail and timestamp derive from the same line box. */
@@ -2897,10 +2897,10 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
2897
2897
  .hjm-load-more__spinner { inline-size: 1em; block-size: 1em; border: 2px solid var(--hjm-color-content-brand); border-inline-end-color: transparent; border-radius: 50%; animation: hjm-spin 800ms linear infinite; }
2898
2898
 
2899
2899
  /* Expansion renderers --------------------------------------------------- */
2900
- .hjm-steps { display: grid; grid-template-columns: repeat(var(--hjm-steps-connector-count), minmax(0, 1fr)) fit-content(calc(100% / var(--hjm-steps-count))); gap: 0; margin: 0; padding: 0; list-style: none; }
2900
+ .hjm-steps { display: grid; grid-template-columns: repeat(var(--hjm-steps-connector-count), minmax(0, 1fr)) fit-content(calc(100% / var(--hjm-steps-count))); gap: 0; margin: 0; padding: 0 var(--hjm-steps-inset); list-style: none; }
2901
2901
  .hjm-steps__step { min-inline-size: 0; display: grid; align-content: start; gap: var(--hjm-space-xs); }
2902
2902
  .hjm-steps__rail { display: flex; align-items: center; min-inline-size: 0; }
2903
- .hjm-steps__indicator { flex: 0 0 auto; inline-size: 1.5rem; block-size: 1.5rem; display: grid; place-items: center; border: 1px solid var(--hjm-color-border); border-radius: 50%; color: var(--hjm-color-text-muted); background: var(--hjm-color-bg); font-size: var(--hjm-type-label-size); font-weight: var(--hjm-text-emphasis-medium); }
2903
+ .hjm-steps__indicator { flex: 0 0 auto; inline-size: var(--hjm-steps-indicator-size); block-size: var(--hjm-steps-indicator-size); display: grid; place-items: center; border: 1px solid var(--hjm-color-border); border-radius: 50%; color: var(--hjm-color-text-muted); background: var(--hjm-color-bg); font-size: var(--hjm-type-label-size); font-weight: var(--hjm-text-emphasis-medium); }
2904
2904
  .hjm-steps__connector { flex: 1 1 auto; block-size: 1px; background: var(--hjm-color-border); }
2905
2905
  .hjm-steps__step[data-status="current"] .hjm-steps__indicator { border: 2px solid var(--hjm-color-content-brand); color: var(--hjm-color-content-brand); }
2906
2906
  .hjm-steps__step[data-status="complete"] .hjm-steps__indicator { border-color: var(--hjm-accent-success); color: var(--hjm-accent-success); background: var(--hjm-color-bg); }
@@ -3077,7 +3077,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3077
3077
  /* TDS-inspired screen rhythm: shared Native recipes, content-driven height. See docs/screen-chrome.md. */
3078
3078
  /* Intrinsic action columns keep short labels readable at 320px; the title wraps
3079
3079
  in the remaining space instead of squeezing actions to equal-width tracks. */
3080
- .hjm-top-bar { display: grid; grid-template-columns: minmax(var(--hjm-control-min-touch-target), auto) minmax(0, 1fr) minmax(var(--hjm-control-min-touch-target), auto); align-items: center; min-block-size: var(--hjm-top-bar-min-height); gap: var(--hjm-top-bar-gap); padding-inline: var(--hjm-top-bar-padding); padding-block-start: max(env(safe-area-inset-top, 0px), var(--hjm-top-bar-safe-area)); background: var(--hjm-color-bg); }
3080
+ .hjm-top-bar { display: grid; grid-template-columns: minmax(var(--hjm-control-button-small), auto) minmax(0, 1fr) minmax(var(--hjm-control-button-small), auto); align-items: center; min-block-size: var(--hjm-top-bar-min-height); gap: var(--hjm-top-bar-gap); padding-inline: var(--hjm-top-bar-padding); padding-block-start: max(env(safe-area-inset-top, 0px), var(--hjm-top-bar-safe-area)); background: var(--hjm-color-bg); }
3081
3081
  .hjm-top-bar[data-surface="transparent"] { background: transparent; }
3082
3082
  .hjm-top-bar__leading, .hjm-top-bar__trailing { display: flex; flex-wrap: wrap; align-items: center; min-inline-size: 0; gap: var(--hjm-top-bar-gap); }
3083
3083
  .hjm-top-bar__trailing { justify-content: flex-end; }
@@ -3098,7 +3098,7 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3098
3098
  the remainder so the gap is max(footer, inset), not their sum (2026-10-17 "버튼 아래 여백이 많아"). */
3099
3099
  .hjm-bottom-cta[data-placement="screen-footer"] { position: static; box-sizing: border-box; inline-size: 100%; min-inline-size: 0; min-block-size: 0; padding: 0 0 max(0px, calc(var(--hjm-bottom-cta-safe-area) - var(--hjm-bottom-cta-parent-padding))); border: 0; background: none; box-shadow: none; }
3100
3100
  .hjm-bottom-cta__description { margin: 0; font-size: var(--hjm-type-caption-size); line-height: var(--hjm-type-caption-line-height); color: var(--hjm-color-text-muted); overflow-wrap: anywhere; }
3101
- .hjm-bottom-cta__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-bottom-cta-gap); }
3101
+ .hjm-bottom-cta__actions { display: flex; flex-wrap: wrap; gap: var(--hjm-bottom-cta-actions-gap); }
3102
3102
  /* bottomCtaRecipe.contentMaxWidth: the surface spans the viewport, its copy and actions stop at the reading width. */
3103
3103
  .hjm-bottom-cta__description, .hjm-bottom-cta__actions { box-sizing: border-box; inline-size: 100%; max-inline-size: var(--hjm-bottom-cta-content-max-width); margin-inline: auto; }
3104
3104
  .hjm-bottom-cta__actions > div { flex: 1 1 calc(var(--hjm-control-min-touch-target) * 3); min-inline-size: 0; }
@@ -3303,14 +3303,14 @@ button.hjm-list-row, a.hjm-list-row { cursor: pointer; }
3303
3303
  .hjm-tabs__indicator { position: absolute; bottom: 0; height: 2px; border-radius: var(--hjm-radius-full); background: var(--hjm-color-content-brand); pointer-events: none; }
3304
3304
  /* indicator="filled" (tabsRecipe.indicators.filled): a surface-accent pill behind the selected tab, no list divider.
3305
3305
  The moving pill spans the tab height and sits under the labels. */
3306
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__list { border: 0; }
3307
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab { border-radius: var(--hjm-radius-full); }
3308
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab[data-state="selected"] { color: var(--hjm-color-content-brand); }
3309
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab[data-state="selected"]::after { display: none; }
3310
- .hjm-tabs[data-indicator="filled"]:not([data-indicator-motion="moving"]) .hjm-tabs__tab[data-state="selected"] { background: var(--hjm-color-surface-accent); }
3311
- .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; }
3312
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__list { position: relative; }
3313
- .hjm-tabs[data-indicator="filled"] .hjm-tabs__tab { z-index: 1; }
3306
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__list { border: 0; }
3307
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__tab { border-radius: var(--hjm-radius-full); }
3308
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__tab[data-state="selected"] { color: var(--hjm-color-content-brand); }
3309
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__tab[data-state="selected"]::after { display: none; }
3310
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]):not([data-indicator-motion="moving"]) .hjm-tabs__tab[data-state="selected"] { background: var(--hjm-color-surface-accent); }
3311
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__indicator { top: 0; bottom: 0; height: auto; border-radius: var(--hjm-radius-full); background: var(--hjm-color-surface-accent); z-index: 0; }
3312
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__list { position: relative; }
3313
+ .hjm-tabs:is([data-indicator="filled"],[data-indicator="solid"]) .hjm-tabs__tab { z-index: 1; }
3314
3314
  .hjm-tabs__gooey { height: 6px; }
3315
3315
 
3316
3316
  /* Capsule destinations retain route/link semantics. At larger text sizes we show
@@ -3627,6 +3627,8 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3627
3627
  /* Filter pills keep the same native radio semantics and 44px hit target. The inner
3628
3628
  surface is compact; the group wraps at large type instead of clipping labels. */
3629
3629
  .hjm-segmented[data-presentation="pills"] { background:transparent; border:0; padding:0; }
3630
+ /* Pills wrap instead of scrolling. The connected track's scrollport would clip the outer focus outline at the first/last option. */
3631
+ .hjm-segmented[data-presentation="pills"] .hjm-segmented__scroller { overflow:visible; }
3630
3632
  /* Keep the inset pill behind the whole line at large text (40px text / 28px fill was observed). Native already includes this inset as vertical padding. */
3631
3633
  .hjm-segmented[data-presentation="pills"] .hjm-segmented__items { flex-wrap:wrap; gap:var(--hjm-space-xs); overflow:visible; padding:0; border:0; border-radius:0; background:transparent; }
3632
3634
  .hjm-segmented[data-presentation="pills"] .hjm-segmented__item { padding-block:var(--hjm-space-xxs); position:relative; flex:0 1 auto; min-inline-size:0; min-block-size:max(var(--hjm-control-min-touch-target), calc(var(--hjm-segmented-height) + 2 * var(--hjm-space-xxs))); border:0; border-radius:var(--hjm-radius-full); background:transparent; box-shadow:none; padding-inline:var(--hjm-space-md); font-size:var(--hjm-type-label-size); line-height:var(--hjm-type-label-line-height); color:var(--hjm-color-text-muted); }
@@ -3723,6 +3725,7 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3723
3725
  .hjm-card__media-badges { position: absolute; inset-block-start: var(--hjm-space-sm); inset-inline-end: var(--hjm-space-sm); display: flex; flex-wrap: wrap; gap: var(--hjm-space-xs); z-index: 1; }
3724
3726
  .hjm-card__scrim { position: absolute; inset: 0; background: var(--hjm-card-background-scrim); }
3725
3727
  .hjm-card__badges { display: flex; flex-wrap: wrap; gap: var(--hjm-space-xs); }
3728
+ .hjm-card__badges[data-align="center"] { justify-content: center; }
3726
3729
  .hjm-card[data-background-media="true"] > .hjm-card__body { position: relative; flex: 0 1 auto; margin-block-start: auto; }
3727
3730
  .hjm-card[data-background-media="true"] > .hjm-card__actions { position: relative; }
3728
3731
  .hjm-card[data-background-media="true"] .hjm-card__title,
@@ -3822,4 +3825,15 @@ button.hjm-chat-message__media-item:focus-visible { outline: 2px solid var(--hjm
3822
3825
  .hjm-timeline__item:has(> .hjm-timeline__actions) { grid-template-columns: 1rem minmax(0, 1fr) auto; }
3823
3826
  .hjm-timeline__actions { align-self: center; display: flex; align-items: center; gap: var(--hjm-space-xs); }
3824
3827
 
3828
+ /* Strong filled tabs use one opaque contrast pair for static and moving marks. */
3829
+ .hjm-tabs[data-indicator="solid"] .hjm-tabs__tab[data-state="selected"] { color: var(--hjm-color-on-primary); }
3830
+ .hjm-tabs[data-indicator="solid"]:not([data-indicator-motion="moving"]) .hjm-tabs__tab[data-state="selected"],
3831
+ .hjm-tabs[data-indicator="solid"] .hjm-tabs__indicator { background: var(--hjm-color-primary); }
3832
+ .hjm-segmented[data-selection-tone="brand"]:not([data-presentation="text"]) .hjm-segmented__item[data-state="checked"] { color: var(--hjm-color-on-primary); background: var(--hjm-color-primary); }
3833
+ .hjm-segmented[data-selection-tone="brand"][data-presentation="pills"] .hjm-segmented__item[data-state="checked"] { background: transparent; }
3834
+ .hjm-segmented[data-selection-tone="brand"][data-presentation="pills"] .hjm-segmented__item[data-state="checked"]::before { background: var(--hjm-color-primary); border-color: var(--hjm-color-primary); }
3835
+ .hjm-segmented[data-selection-tone="brand"] .hjm-segmented__highlight { background: var(--hjm-color-primary); }
3836
+ .hjm-segmented[data-selection-tone="brand"] .hjm-segmented__items[data-highlight-ready="true"] > .hjm-segmented__item[data-state="checked"],
3837
+ .hjm-segmented[data-selection-tone="brand"][data-presentation="pills"] .hjm-segmented__items[data-highlight-ready="true"] > .hjm-segmented__item[data-state="checked"]::before { background: transparent; }
3838
+
3825
3839
  }
@@ -25,8 +25,8 @@ function Failure({ failed, label }) {
25
25
  export function SplitAction(props) {
26
26
  const mode = resolveTaskChoice(props.modes, props.modeId), action = useCompositionAction(props.disabled);
27
27
  const locked = props.disabled === true || action.pending;
28
- // The mode menu is a chevron tool at the action's end, inline gap 12 (button.md); a second text button read as an unrelated action (2026-10-17 review).
29
- return _jsxs(Stack, { gap: "sm", children: [_jsxs(Stack, { axis: "inline", gap: "sm", align: "center", children: [_jsx(Button, { loading: action.pending, disabled: props.disabled === true || mode.disabled === true, onClick: () => { if (!mode.disabled)
28
+ // The mode menu is a chevron tool at the action's end, shared inline gap 8 (button.md); a second text button read as an unrelated action (2026-10-17 review).
29
+ return _jsxs(Stack, { gap: "sm", children: [_jsxs(Stack, { axis: "inline", gap: "xs", align: "center", children: [_jsx(Button, { loading: action.pending, disabled: props.disabled === true || mode.disabled === true, onClick: () => { if (!mode.disabled)
30
30
  void action.run(mode.id, () => props.onExecute(mode.id)); }, children: props.executeLabel(mode) }), _jsx(ChoiceMenu, { trigger: "icon", choices: props.modes, selectedId: mode.id, label: props.menuLabel, dismissLabel: props.dismissLabel, disabled: locked, onChoose: id => { if (!locked && !resolveTaskChoice(props.modes, id).disabled)
31
31
  props.onModeChange(id); } })] }), _jsx(Failure, { failed: action.failed, label: props.errorLabel })] });
32
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"task-controls.js","sourceRoot":"","sources":["../src/task-controls.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { resolveTaskChoice, resolvePasswordRules, resolvePriceOption, type TaskChoice, type PasswordRule, type PriceOption } from \"@hjmds/design-contracts/task-controls\";\nimport { Button, IconButton } from \"./actions.js\";\nimport { Icon } from \"./supplemental-display.js\";\nimport { Menu } from \"./overlays.js\";\nimport { PasswordField, type PasswordFieldProps } from \"./forms.js\";\nimport { RadioGroup } from \"./selection.js\";\nimport { Card } from \"./display.js\";\nimport { Progress } from \"./feedback.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\n\ntype ChoiceMenuProps = Readonly<{ choices: readonly TaskChoice[]; selectedId: string; label: string; dismissLabel: string; disabled: boolean; pending?: boolean; onChoose: (id: string) => void;\n /** `icon`: a chevron tool beside the action it modifies (SplitAction); the label stays the accessible name. */\n trigger?: \"text\" | \"icon\" }>;\nfunction ChoiceMenu(props: ChoiceMenuProps) {\n const available = props.choices.some(choice => !choice.disabled);\n if (props.trigger === \"icon\" && !available) return <IconButton label={props.label} tone=\"secondary\" disabled><Icon name=\"chevronDown\" /></IconButton>;\n if (!available) return <Button disabled>{props.label}</Button>;\n const trigger = props.trigger === \"icon\" ? <IconButton label={props.label} tone=\"secondary\"><Icon name=\"chevronDown\" /></IconButton> : <Button tone=\"secondary\" loading={props.pending === true}>{props.label}</Button>;\n return <Menu presentation=\"anchored\" label={props.label} trigger={trigger} disabled={props.disabled}\n items={props.choices} selectionMode=\"single\" value={props.selectedId} onValueChange={() => undefined} onActionAfterDismiss={props.onChoose} />;\n}\nfunction Failure({ failed, label }: Readonly<{ failed: boolean; label: string }>) {\n return failed ? <Text role=\"alert\" tone=\"danger\" style={{ textAlign: \"center\" }}>{label}</Text> : null;\n}\n\nexport type SplitActionProps = Readonly<{\n modes: readonly TaskChoice[]; modeId: string; onModeChange: (id: string) => void;\n onExecute: (modeId: string) => void | Promise<void>; executeLabel: (mode: TaskChoice) => string;\n menuLabel: string; dismissLabel: string; errorLabel: string; disabled?: boolean;\n}>;\nexport function SplitAction(props: SplitActionProps) {\n const mode = resolveTaskChoice(props.modes, props.modeId), action = useCompositionAction(props.disabled);\n const locked = props.disabled === true || action.pending;\n // The mode menu is a chevron tool at the action's end, inline gap 12 (button.md); a second text button read as an unrelated action (2026-10-17 review).\n return <Stack gap=\"sm\"><Stack axis=\"inline\" gap=\"sm\" align=\"center\">\n <Button loading={action.pending} disabled={props.disabled === true || mode.disabled === true} onClick={() => { if (!mode.disabled) void action.run(mode.id, () => props.onExecute(mode.id)); }}>{props.executeLabel(mode)}</Button>\n <ChoiceMenu trigger=\"icon\" choices={props.modes} selectedId={mode.id} label={props.menuLabel} dismissLabel={props.dismissLabel} disabled={locked} onChoose={id => { if (!locked && !resolveTaskChoice(props.modes, id).disabled) props.onModeChange(id); }} />\n </Stack><Failure failed={action.failed} label={props.errorLabel} /></Stack>;\n}\n\nexport type AccountSwitcherProps = Readonly<{\n accounts: readonly TaskChoice[]; accountId: string; label: string; dismissLabel: string; errorLabel: string;\n onSwitch: (accountId: string) => void | Promise<void>; disabled?: boolean;\n}>;\nexport function AccountSwitcher(props: AccountSwitcherProps) {\n const account = resolveTaskChoice(props.accounts, props.accountId), action = useCompositionAction(props.disabled);\n const locked = props.disabled === true || action.pending;\n // The controlled account stays confirmed until the product completes the session change.\n return <Stack gap=\"sm\"><Text emphasis=\"strong\">{account.label}</Text><ChoiceMenu choices={props.accounts} selectedId={account.id} label={props.label} dismissLabel={props.dismissLabel} disabled={locked} pending={action.pending}\n onChoose={id => { if (id !== account.id && !resolveTaskChoice(props.accounts, id).disabled) void action.run(id, () => props.onSwitch(id)); }} />\n <Failure failed={action.failed} label={props.errorLabel} /></Stack>;\n}\n\nexport type PasswordRulesFieldProps = Omit<PasswordFieldProps, \"value\" | \"defaultValue\" | \"onValueChange\"> & Readonly<{\n label: string; value: string; onValueChange: (value: string) => void; rules: readonly PasswordRule[];\n progressLabel: string; summaryLabel: (satisfied: number, total: number) => string;\n ruleLabel: (rule: ReturnType<typeof resolvePasswordRules>[\"rules\"][number]) => string;\n}>;\nexport function PasswordRulesField({ rules, progressLabel, summaryLabel, ruleLabel, ...field }: PasswordRulesFieldProps) {\n const state = resolvePasswordRules(field.value, rules);\n return <Stack gap=\"sm\"><PasswordField {...field} />\n <Progress label={progressLabel} value={state.satisfied} max={state.total} valueText={summaryLabel(state.satisfied, state.total)} />\n {state.rules.map(rule => <Text key={rule.id} variant=\"caption\" tone={rule.satisfied ? \"primary\" : \"muted\"}>{ruleLabel(rule)}</Text>)}\n </Stack>;\n}\n\nexport type PricingChoiceProps = Readonly<{\n label: string; options: readonly PriceOption[]; selectedId: string; onSelectionChange: (id: string) => void;\n formatPrice: (option: PriceOption) => string; continueLabel: string; errorLabel: string;\n onContinue: (option: PriceOption) => void | Promise<void>; disabled?: boolean;\n}>;\nexport function PricingChoice(props: PricingChoiceProps) {\n const option = resolvePriceOption(props.options, props.selectedId), action = useCompositionAction(props.disabled);\n const locked = props.disabled === true || action.pending;\n return <Card title={props.label}><Stack gap=\"md\"><RadioGroup accessibilityLabel={props.label} presentation=\"card\" value={option.id} disabled={locked}\n items={props.options.map(price => ({ value: price.id, label: price.label, description: props.formatPrice(price), disabled: price.disabled === true }))}\n onValueChange={id => { if (id !== null && !locked && !resolveTaskChoice(props.options, id).disabled) props.onSelectionChange(id); }} />\n <Text emphasis=\"strong\">{props.formatPrice(option)}</Text>\n {option.benefits.map((benefit, index) => <Text key={index}>{benefit}</Text>)}\n <Button loading={action.pending} disabled={props.disabled === true || option.disabled === true} onClick={() => { if (!option.disabled) void action.run(option.id, () => props.onContinue(Object.freeze({ ...option, benefits: Object.freeze([...option.benefits]) }))); }}>{props.continueLabel}</Button>\n <Failure failed={action.failed} label={props.errorLabel} /></Stack></Card>;\n}\n"]}
1
+ {"version":3,"file":"task-controls.js","sourceRoot":"","sources":["../src/task-controls.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { resolveTaskChoice, resolvePasswordRules, resolvePriceOption, type TaskChoice, type PasswordRule, type PriceOption } from \"@hjmds/design-contracts/task-controls\";\nimport { Button, IconButton } from \"./actions.js\";\nimport { Icon } from \"./supplemental-display.js\";\nimport { Menu } from \"./overlays.js\";\nimport { PasswordField, type PasswordFieldProps } from \"./forms.js\";\nimport { RadioGroup } from \"./selection.js\";\nimport { Card } from \"./display.js\";\nimport { Progress } from \"./feedback.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\n\ntype ChoiceMenuProps = Readonly<{ choices: readonly TaskChoice[]; selectedId: string; label: string; dismissLabel: string; disabled: boolean; pending?: boolean; onChoose: (id: string) => void;\n /** `icon`: a chevron tool beside the action it modifies (SplitAction); the label stays the accessible name. */\n trigger?: \"text\" | \"icon\" }>;\nfunction ChoiceMenu(props: ChoiceMenuProps) {\n const available = props.choices.some(choice => !choice.disabled);\n if (props.trigger === \"icon\" && !available) return <IconButton label={props.label} tone=\"secondary\" disabled><Icon name=\"chevronDown\" /></IconButton>;\n if (!available) return <Button disabled>{props.label}</Button>;\n const trigger = props.trigger === \"icon\" ? <IconButton label={props.label} tone=\"secondary\"><Icon name=\"chevronDown\" /></IconButton> : <Button tone=\"secondary\" loading={props.pending === true}>{props.label}</Button>;\n return <Menu presentation=\"anchored\" label={props.label} trigger={trigger} disabled={props.disabled}\n items={props.choices} selectionMode=\"single\" value={props.selectedId} onValueChange={() => undefined} onActionAfterDismiss={props.onChoose} />;\n}\nfunction Failure({ failed, label }: Readonly<{ failed: boolean; label: string }>) {\n return failed ? <Text role=\"alert\" tone=\"danger\" style={{ textAlign: \"center\" }}>{label}</Text> : null;\n}\n\nexport type SplitActionProps = Readonly<{\n modes: readonly TaskChoice[]; modeId: string; onModeChange: (id: string) => void;\n onExecute: (modeId: string) => void | Promise<void>; executeLabel: (mode: TaskChoice) => string;\n menuLabel: string; dismissLabel: string; errorLabel: string; disabled?: boolean;\n}>;\nexport function SplitAction(props: SplitActionProps) {\n const mode = resolveTaskChoice(props.modes, props.modeId), action = useCompositionAction(props.disabled);\n const locked = props.disabled === true || action.pending;\n // The mode menu is a chevron tool at the action's end, shared inline gap 8 (button.md); a second text button read as an unrelated action (2026-10-17 review).\n return <Stack gap=\"sm\"><Stack axis=\"inline\" gap=\"xs\" align=\"center\">\n <Button loading={action.pending} disabled={props.disabled === true || mode.disabled === true} onClick={() => { if (!mode.disabled) void action.run(mode.id, () => props.onExecute(mode.id)); }}>{props.executeLabel(mode)}</Button>\n <ChoiceMenu trigger=\"icon\" choices={props.modes} selectedId={mode.id} label={props.menuLabel} dismissLabel={props.dismissLabel} disabled={locked} onChoose={id => { if (!locked && !resolveTaskChoice(props.modes, id).disabled) props.onModeChange(id); }} />\n </Stack><Failure failed={action.failed} label={props.errorLabel} /></Stack>;\n}\n\nexport type AccountSwitcherProps = Readonly<{\n accounts: readonly TaskChoice[]; accountId: string; label: string; dismissLabel: string; errorLabel: string;\n onSwitch: (accountId: string) => void | Promise<void>; disabled?: boolean;\n}>;\nexport function AccountSwitcher(props: AccountSwitcherProps) {\n const account = resolveTaskChoice(props.accounts, props.accountId), action = useCompositionAction(props.disabled);\n const locked = props.disabled === true || action.pending;\n // The controlled account stays confirmed until the product completes the session change.\n return <Stack gap=\"sm\"><Text emphasis=\"strong\">{account.label}</Text><ChoiceMenu choices={props.accounts} selectedId={account.id} label={props.label} dismissLabel={props.dismissLabel} disabled={locked} pending={action.pending}\n onChoose={id => { if (id !== account.id && !resolveTaskChoice(props.accounts, id).disabled) void action.run(id, () => props.onSwitch(id)); }} />\n <Failure failed={action.failed} label={props.errorLabel} /></Stack>;\n}\n\nexport type PasswordRulesFieldProps = Omit<PasswordFieldProps, \"value\" | \"defaultValue\" | \"onValueChange\"> & Readonly<{\n label: string; value: string; onValueChange: (value: string) => void; rules: readonly PasswordRule[];\n progressLabel: string; summaryLabel: (satisfied: number, total: number) => string;\n ruleLabel: (rule: ReturnType<typeof resolvePasswordRules>[\"rules\"][number]) => string;\n}>;\nexport function PasswordRulesField({ rules, progressLabel, summaryLabel, ruleLabel, ...field }: PasswordRulesFieldProps) {\n const state = resolvePasswordRules(field.value, rules);\n return <Stack gap=\"sm\"><PasswordField {...field} />\n <Progress label={progressLabel} value={state.satisfied} max={state.total} valueText={summaryLabel(state.satisfied, state.total)} />\n {state.rules.map(rule => <Text key={rule.id} variant=\"caption\" tone={rule.satisfied ? \"primary\" : \"muted\"}>{ruleLabel(rule)}</Text>)}\n </Stack>;\n}\n\nexport type PricingChoiceProps = Readonly<{\n label: string; options: readonly PriceOption[]; selectedId: string; onSelectionChange: (id: string) => void;\n formatPrice: (option: PriceOption) => string; continueLabel: string; errorLabel: string;\n onContinue: (option: PriceOption) => void | Promise<void>; disabled?: boolean;\n}>;\nexport function PricingChoice(props: PricingChoiceProps) {\n const option = resolvePriceOption(props.options, props.selectedId), action = useCompositionAction(props.disabled);\n const locked = props.disabled === true || action.pending;\n return <Card title={props.label}><Stack gap=\"md\"><RadioGroup accessibilityLabel={props.label} presentation=\"card\" value={option.id} disabled={locked}\n items={props.options.map(price => ({ value: price.id, label: price.label, description: props.formatPrice(price), disabled: price.disabled === true }))}\n onValueChange={id => { if (id !== null && !locked && !resolveTaskChoice(props.options, id).disabled) props.onSelectionChange(id); }} />\n <Text emphasis=\"strong\">{props.formatPrice(option)}</Text>\n {option.benefits.map((benefit, index) => <Text key={index}>{benefit}</Text>)}\n <Button loading={action.pending} disabled={props.disabled === true || option.disabled === true} onClick={() => { if (!option.disabled) void action.run(option.id, () => props.onContinue(Object.freeze({ ...option, benefits: Object.freeze([...option.benefits]) }))); }}>{props.continueLabel}</Button>\n <Failure failed={action.failed} label={props.errorLabel} /></Stack></Card>;\n}\n"]}
@@ -51,6 +51,6 @@ export function TransferTask(props) {
51
51
  const status = action.pending ? cancelRequested ? props.labels.cancelling : observation ? props.labels.transferring : props.labels.queued
52
52
  : cancelled ? props.labels.cancelled : phase === "success" ? props.labels.success : action.failed ? props.labels.error : null;
53
53
  const known = observation !== null && observation.total !== null;
54
- return _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { axis: "inline", gap: "sm", wrap: true, children: [_jsx(Button, { loading: action.pending, disabled: props.disabled === true, onClick: start, children: props.labels.start }), action.pending ? _jsx(Button, { tone: "secondary", disabled: props.disabled === true || cancelRequested, onClick: cancel, children: props.labels.cancel }) : null] }), action.pending || known ? _jsx(Progress, { label: status ?? props.labels.transferring, ...(known ? { value: observation.completed, max: observation.total } : {}), ...(observation ? { valueText: props.formatProgress(observation) } : {}) }) : null, observation && !action.pending && !known ? _jsx(Text, { variant: "caption", children: props.formatProgress(observation) }) : null, status && !action.pending ? _jsx(Text, { role: action.failed && !cancelled ? "alert" : "status", style: { textAlign: "center" }, tone: action.failed && !cancelled ? "danger" : "primary", children: status }) : null] });
54
+ return _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { axis: "inline", gap: "xs", wrap: true, children: [_jsx(Button, { loading: action.pending, disabled: props.disabled === true, onClick: start, children: props.labels.start }), action.pending ? _jsx(Button, { tone: "secondary", disabled: props.disabled === true || cancelRequested, onClick: cancel, children: props.labels.cancel }) : null] }), action.pending || known ? _jsx(Progress, { label: status ?? props.labels.transferring, ...(known ? { value: observation.completed, max: observation.total } : {}), ...(observation ? { valueText: props.formatProgress(observation) } : {}) }) : null, observation && !action.pending && !known ? _jsx(Text, { variant: "caption", children: props.formatProgress(observation) }) : null, status && !action.pending ? _jsx(Text, { role: action.failed && !cancelled ? "alert" : "status", style: { textAlign: "center" }, tone: action.failed && !cancelled ? "danger" : "primary", children: status }) : null] });
55
55
  }
56
56
  //# sourceMappingURL=transfer-task.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transfer-task.js","sourceRoot":"","sources":["../src/transfer-task.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { isConfirmedTransferAbort, resolveTransferObservation, type TransferObservation, type TransferContext } from \"@hjmds/design-contracts/transfer-task\";\nimport { Button } from \"./actions.js\";\nimport { Progress } from \"./feedback.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\nexport type TransferTaskProps = Readonly<{\n onTransfer: (context: TransferContext) => Promise<void>;\n labels: Readonly<{ start: string; queued: string; transferring: string; cancel: string; cancelling: string; cancelled: string; success: string; error: string }>;\n formatProgress: (observation: TransferObservation) => string; disabled?: boolean;\n}>;\nexport function TransferTask(props: TransferTaskProps) {\n const action = useCompositionAction(props.disabled), mounted = useRef(true), disabled = useRef(props.disabled);\n disabled.current = props.disabled;\n const request = useRef<AbortController | null>(null), latest = useRef<TransferObservation | null>(null);\n const [observation, setObservation] = useState<TransferObservation | null>(null), [cancelRequested, setCancelRequested] = useState(false), [cancelled, setCancelled] = useState(false);\n useEffect(() => { mounted.current = true; return () => { mounted.current = false; request.current?.abort(); request.current = null; }; }, []);\n const start = () => {\n if (!mounted.current || disabled.current || request.current) return;\n const controller = new AbortController(); let accepting = true; request.current = controller; latest.current = null;\n setObservation(null); setCancelled(false); setCancelRequested(false);\n void action.run(\"transfer\", async () => {\n try {\n await props.onTransfer({ signal: controller.signal, report: next => {\n if (!accepting || !mounted.current || request.current !== controller || controller.signal.aborted) return;\n const value = resolveTransferObservation(next, latest.current); latest.current = value; setObservation(value);\n } });\n } catch (error) {\n if (mounted.current && request.current === controller && isConfirmedTransferAbort(error, controller.signal)) setCancelled(true);\n throw error;\n } finally { accepting = false; }\n }).then(() => { if (request.current === controller) request.current = null; });\n };\n const cancel = () => { if (mounted.current && !disabled.current && request.current && !request.current.signal.aborted) { setCancelRequested(true); request.current.abort(); } };\n const phase = action.session.getSnapshot().status;\n const status = action.pending ? cancelRequested ? props.labels.cancelling : observation ? props.labels.transferring : props.labels.queued\n : cancelled ? props.labels.cancelled : phase === \"success\" ? props.labels.success : action.failed ? props.labels.error : null;\n const known = observation !== null && observation.total !== null;\n return <Stack gap=\"md\"><Stack axis=\"inline\" gap=\"sm\" wrap>\n <Button loading={action.pending} disabled={props.disabled === true} onClick={start}>{props.labels.start}</Button>\n {action.pending ? <Button tone=\"secondary\" disabled={props.disabled === true || cancelRequested} onClick={cancel}>{props.labels.cancel}</Button> : null}</Stack>\n {action.pending || known ? <Progress label={status ?? props.labels.transferring} {...(known ? { value: observation.completed, max: observation.total! } : {})}\n {...(observation ? { valueText: props.formatProgress(observation) } : {})} /> : null}\n {observation && !action.pending && !known ? <Text variant=\"caption\">{props.formatProgress(observation)}</Text> : null}\n {status && !action.pending ? <Text role={action.failed && !cancelled ? \"alert\" : \"status\"} style={{ textAlign: \"center\" }} tone={action.failed && !cancelled ? \"danger\" : \"primary\"}>{status}</Text> : null}\n </Stack>;\n}\n"]}
1
+ {"version":3,"file":"transfer-task.js","sourceRoot":"","sources":["../src/transfer-task.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { isConfirmedTransferAbort, resolveTransferObservation, type TransferObservation, type TransferContext } from \"@hjmds/design-contracts/transfer-task\";\nimport { Button } from \"./actions.js\";\nimport { Progress } from \"./feedback.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\nexport type TransferTaskProps = Readonly<{\n onTransfer: (context: TransferContext) => Promise<void>;\n labels: Readonly<{ start: string; queued: string; transferring: string; cancel: string; cancelling: string; cancelled: string; success: string; error: string }>;\n formatProgress: (observation: TransferObservation) => string; disabled?: boolean;\n}>;\nexport function TransferTask(props: TransferTaskProps) {\n const action = useCompositionAction(props.disabled), mounted = useRef(true), disabled = useRef(props.disabled);\n disabled.current = props.disabled;\n const request = useRef<AbortController | null>(null), latest = useRef<TransferObservation | null>(null);\n const [observation, setObservation] = useState<TransferObservation | null>(null), [cancelRequested, setCancelRequested] = useState(false), [cancelled, setCancelled] = useState(false);\n useEffect(() => { mounted.current = true; return () => { mounted.current = false; request.current?.abort(); request.current = null; }; }, []);\n const start = () => {\n if (!mounted.current || disabled.current || request.current) return;\n const controller = new AbortController(); let accepting = true; request.current = controller; latest.current = null;\n setObservation(null); setCancelled(false); setCancelRequested(false);\n void action.run(\"transfer\", async () => {\n try {\n await props.onTransfer({ signal: controller.signal, report: next => {\n if (!accepting || !mounted.current || request.current !== controller || controller.signal.aborted) return;\n const value = resolveTransferObservation(next, latest.current); latest.current = value; setObservation(value);\n } });\n } catch (error) {\n if (mounted.current && request.current === controller && isConfirmedTransferAbort(error, controller.signal)) setCancelled(true);\n throw error;\n } finally { accepting = false; }\n }).then(() => { if (request.current === controller) request.current = null; });\n };\n const cancel = () => { if (mounted.current && !disabled.current && request.current && !request.current.signal.aborted) { setCancelRequested(true); request.current.abort(); } };\n const phase = action.session.getSnapshot().status;\n const status = action.pending ? cancelRequested ? props.labels.cancelling : observation ? props.labels.transferring : props.labels.queued\n : cancelled ? props.labels.cancelled : phase === \"success\" ? props.labels.success : action.failed ? props.labels.error : null;\n const known = observation !== null && observation.total !== null;\n return <Stack gap=\"md\"><Stack axis=\"inline\" gap=\"xs\" wrap>\n <Button loading={action.pending} disabled={props.disabled === true} onClick={start}>{props.labels.start}</Button>\n {action.pending ? <Button tone=\"secondary\" disabled={props.disabled === true || cancelRequested} onClick={cancel}>{props.labels.cancel}</Button> : null}</Stack>\n {action.pending || known ? <Progress label={status ?? props.labels.transferring} {...(known ? { value: observation.completed, max: observation.total! } : {})}\n {...(observation ? { valueText: props.formatProgress(observation) } : {})} /> : null}\n {observation && !action.pending && !known ? <Text variant=\"caption\">{props.formatProgress(observation)}</Text> : null}\n {status && !action.pending ? <Text role={action.failed && !cancelled ? \"alert\" : \"status\"} style={{ textAlign: \"center\" }} tone={action.failed && !cancelled ? \"danger\" : \"primary\"}>{status}</Text> : null}\n </Stack>;\n}\n"]}
@@ -8,6 +8,6 @@ import { Stack, Surface, Text } from "./layout.js";
8
8
  export function VoiceNote({ descriptor, labels, formatTime, artwork, onPlayingChange, onSeek, onRetry, layoutStyle }) {
9
9
  const media = resolveVoiceNote(descriptor);
10
10
  const error = media.state === "error";
11
- return _jsx(Surface, { padding: "lg", ...(layoutStyle === undefined ? {} : { layoutStyle }), children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { axis: "inline", gap: "md", children: [artwork ? _jsx(Asset, { descriptor: { kind: "image", decorative: true, shape: "circle" }, children: artwork }) : null, _jsx(Text, { emphasis: "strong", children: media.title })] }), _jsx(Slider, { label: labels.seek, min: 0, max: media.duration || 1, step: 0.1, value: media.position, disabled: !media.canSeek, onValueChange: onSeek, getValueText: formatTime }), _jsxs(Text, { tone: "muted", children: [formatTime(media.position), media.duration === null ? "" : ` / ${formatTime(media.duration)}`] }), error ? _jsx(Text, { role: "alert", children: labels.error }) : null, _jsxs(Stack, { axis: "inline", gap: "sm", children: [_jsx(Button, { disabled: !media.canPlay, loading: media.state === "loading", onClick: () => onPlayingChange(media.state !== "playing"), children: media.state === "loading" ? labels.loading : media.state === "playing" ? labels.pause : labels.play }), error && onRetry ? _jsx(Button, { disabled: media.disabled ?? false, onClick: onRetry, children: labels.retry }) : null] })] }) });
11
+ return _jsx(Surface, { padding: "lg", ...(layoutStyle === undefined ? {} : { layoutStyle }), children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { axis: "inline", gap: "md", children: [artwork ? _jsx(Asset, { descriptor: { kind: "image", decorative: true, shape: "circle" }, children: artwork }) : null, _jsx(Text, { emphasis: "strong", children: media.title })] }), _jsx(Slider, { label: labels.seek, min: 0, max: media.duration || 1, step: 0.1, value: media.position, disabled: !media.canSeek, onValueChange: onSeek, getValueText: formatTime }), _jsxs(Text, { tone: "muted", children: [formatTime(media.position), media.duration === null ? "" : ` / ${formatTime(media.duration)}`] }), error ? _jsx(Text, { role: "alert", children: labels.error }) : null, _jsxs(Stack, { axis: "inline", gap: "xs", children: [_jsx(Button, { disabled: !media.canPlay, loading: media.state === "loading", onClick: () => onPlayingChange(media.state !== "playing"), children: media.state === "loading" ? labels.loading : media.state === "playing" ? labels.pause : labels.play }), error && onRetry ? _jsx(Button, { disabled: media.disabled ?? false, onClick: onRetry, children: labels.retry }) : null] })] }) });
12
12
  }
13
13
  //# sourceMappingURL=voice-note.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"voice-note.js","sourceRoot":"","sources":["../src/voice-note.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,gBAAgB,EAA4B,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAgBnD,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAkB;IAClI,MAAM,KAAK,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,OAAO,CAAC;IACtC,OAAO,KAAC,OAAO,IAAC,OAAO,EAAC,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,YAAE,MAAC,KAAK,IAAC,GAAG,EAAC,IAAI,aAClG,MAAC,KAAK,IAAC,IAAI,EAAC,QAAQ,EAAC,GAAG,EAAC,IAAI,aAC1B,OAAO,CAAC,CAAC,CAAC,KAAC,KAAK,IAAC,UAAU,EAAE,EAAC,IAAI,EAAC,OAAO,EAAC,UAAU,EAAC,IAAI,EAAC,KAAK,EAAC,QAAQ,EAAC,YAAG,OAAO,GAAS,CAAC,CAAC,CAAC,IAAI,EACrG,KAAC,IAAI,IAAC,QAAQ,EAAC,QAAQ,YAAE,KAAK,CAAC,KAAK,GAAQ,IACtC,EACR,KAAC,MAAM,IAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,GAAK,EAC9K,MAAC,IAAI,IAAC,IAAI,EAAC,OAAO,aAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAQ,EACxH,KAAK,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,YAAE,MAAM,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,EACxD,MAAC,KAAK,IAAC,IAAI,EAAC,QAAQ,EAAC,GAAG,EAAC,IAAI,aAC3B,KAAC,MAAM,IAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,YAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAU,EAC9O,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,EAAE,OAAO,EAAE,OAAO,YAAG,MAAM,CAAC,KAAK,GAAU,CAAC,CAAC,CAAC,IAAI,IACzG,IACF,GAAU,CAAC;AACrB,CAAC","sourcesContent":["import type { ReactNode } from \"react\";\nimport { resolveVoiceNote, type VoiceNoteDescriptor } from \"@hjmds/design-contracts/voice-note\";\nimport { Asset } from \"./asset.js\";\nimport { Button } from \"./actions.js\";\nimport { Slider } from \"./slider.js\";\nimport { Stack, Surface, Text } from \"./layout.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type VoiceNoteProps = Readonly<{\n descriptor: VoiceNoteDescriptor;\n labels: Readonly<{ play: string; pause: string; seek: string; loading: string; error: string; retry: string; backward: string; forward: string }>;\n /** Localized elapsed/duration text and slider accessible value. */\n formatTime: (seconds: number) => string;\n artwork?: ReactNode;\n onPlayingChange: (playing: boolean) => void;\n onSeek: (seconds: number) => void;\n onRetry?: () => void;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Audio engine, network, file permission and playback lifecycle remain host-owned. */\nexport function VoiceNote({ descriptor, labels, formatTime, artwork, onPlayingChange, onSeek, onRetry, layoutStyle }: VoiceNoteProps) {\n const media = resolveVoiceNote(descriptor);\n const error = media.state === \"error\";\n return <Surface padding=\"lg\" {...(layoutStyle === undefined ? {} : { layoutStyle })}><Stack gap=\"md\">\n <Stack axis=\"inline\" gap=\"md\">\n {artwork ? <Asset descriptor={{kind:\"image\",decorative:true,shape:\"circle\"}}>{artwork}</Asset> : null}\n <Text emphasis=\"strong\">{media.title}</Text>\n </Stack>\n <Slider label={labels.seek} min={0} max={media.duration || 1} step={0.1} value={media.position} disabled={!media.canSeek} onValueChange={onSeek} getValueText={formatTime} />\n <Text tone=\"muted\">{formatTime(media.position)}{media.duration === null ? \"\" : ` / ${formatTime(media.duration)}`}</Text>\n {error ? <Text role=\"alert\">{labels.error}</Text> : null}\n <Stack axis=\"inline\" gap=\"sm\">\n <Button disabled={!media.canPlay} loading={media.state === \"loading\"} onClick={() => onPlayingChange(media.state !== \"playing\")}>{media.state === \"loading\" ? labels.loading : media.state === \"playing\" ? labels.pause : labels.play}</Button>\n {error && onRetry ? <Button disabled={media.disabled ?? false} onClick={onRetry}>{labels.retry}</Button> : null}\n </Stack>\n </Stack></Surface>;\n}\n"]}
1
+ {"version":3,"file":"voice-note.js","sourceRoot":"","sources":["../src/voice-note.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,gBAAgB,EAA4B,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAgBnD,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAkB;IAClI,MAAM,KAAK,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,OAAO,CAAC;IACtC,OAAO,KAAC,OAAO,IAAC,OAAO,EAAC,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,YAAE,MAAC,KAAK,IAAC,GAAG,EAAC,IAAI,aAClG,MAAC,KAAK,IAAC,IAAI,EAAC,QAAQ,EAAC,GAAG,EAAC,IAAI,aAC1B,OAAO,CAAC,CAAC,CAAC,KAAC,KAAK,IAAC,UAAU,EAAE,EAAC,IAAI,EAAC,OAAO,EAAC,UAAU,EAAC,IAAI,EAAC,KAAK,EAAC,QAAQ,EAAC,YAAG,OAAO,GAAS,CAAC,CAAC,CAAC,IAAI,EACrG,KAAC,IAAI,IAAC,QAAQ,EAAC,QAAQ,YAAE,KAAK,CAAC,KAAK,GAAQ,IACtC,EACR,KAAC,MAAM,IAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,GAAK,EAC9K,MAAC,IAAI,IAAC,IAAI,EAAC,OAAO,aAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAQ,EACxH,KAAK,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,YAAE,MAAM,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,EACxD,MAAC,KAAK,IAAC,IAAI,EAAC,QAAQ,EAAC,GAAG,EAAC,IAAI,aAC3B,KAAC,MAAM,IAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,YAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAU,EAC9O,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,EAAE,OAAO,EAAE,OAAO,YAAG,MAAM,CAAC,KAAK,GAAU,CAAC,CAAC,CAAC,IAAI,IACzG,IACF,GAAU,CAAC;AACrB,CAAC","sourcesContent":["import type { ReactNode } from \"react\";\nimport { resolveVoiceNote, type VoiceNoteDescriptor } from \"@hjmds/design-contracts/voice-note\";\nimport { Asset } from \"./asset.js\";\nimport { Button } from \"./actions.js\";\nimport { Slider } from \"./slider.js\";\nimport { Stack, Surface, Text } from \"./layout.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type VoiceNoteProps = Readonly<{\n descriptor: VoiceNoteDescriptor;\n labels: Readonly<{ play: string; pause: string; seek: string; loading: string; error: string; retry: string; backward: string; forward: string }>;\n /** Localized elapsed/duration text and slider accessible value. */\n formatTime: (seconds: number) => string;\n artwork?: ReactNode;\n onPlayingChange: (playing: boolean) => void;\n onSeek: (seconds: number) => void;\n onRetry?: () => void;\n /** Canonical layout-only placement on the root element. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\n/** Audio engine, network, file permission and playback lifecycle remain host-owned. */\nexport function VoiceNote({ descriptor, labels, formatTime, artwork, onPlayingChange, onSeek, onRetry, layoutStyle }: VoiceNoteProps) {\n const media = resolveVoiceNote(descriptor);\n const error = media.state === \"error\";\n return <Surface padding=\"lg\" {...(layoutStyle === undefined ? {} : { layoutStyle })}><Stack gap=\"md\">\n <Stack axis=\"inline\" gap=\"md\">\n {artwork ? <Asset descriptor={{kind:\"image\",decorative:true,shape:\"circle\"}}>{artwork}</Asset> : null}\n <Text emphasis=\"strong\">{media.title}</Text>\n </Stack>\n <Slider label={labels.seek} min={0} max={media.duration || 1} step={0.1} value={media.position} disabled={!media.canSeek} onValueChange={onSeek} getValueText={formatTime} />\n <Text tone=\"muted\">{formatTime(media.position)}{media.duration === null ? \"\" : ` / ${formatTime(media.duration)}`}</Text>\n {error ? <Text role=\"alert\">{labels.error}</Text> : null}\n <Stack axis=\"inline\" gap=\"xs\">\n <Button disabled={!media.canPlay} loading={media.state === \"loading\"} onClick={() => onPlayingChange(media.state !== \"playing\")}>{media.state === \"loading\" ? labels.loading : media.state === \"playing\" ? labels.pause : labels.play}</Button>\n {error && onRetry ? <Button disabled={media.disabled ?? false} onClick={onRetry}>{labels.retry}</Button> : null}\n </Stack>\n </Stack></Surface>;\n}\n"]}