@krizaka/ui 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-I5RVM5CW.js → chunk-2FRWNSRO.js} +2 -6
- package/dist/chunk-2FRWNSRO.js.map +1 -0
- package/dist/chunk-BTMFZ66D.js +135 -0
- package/dist/chunk-BTMFZ66D.js.map +1 -0
- package/dist/chunk-C2BRIFJG.js +13 -0
- package/dist/chunk-C2BRIFJG.js.map +1 -0
- package/dist/chunk-KDXRYUD3.js +39 -0
- package/dist/chunk-KDXRYUD3.js.map +1 -0
- package/dist/{chunk-2PUDI7JS.js → chunk-ZKVQGXLD.js} +5 -3
- package/dist/{chunk-2PUDI7JS.js.map → chunk-ZKVQGXLD.js.map} +1 -1
- package/dist/command.js +1 -1
- package/dist/confirm-button.js +1 -1
- package/dist/data-table.js +3 -2
- package/dist/data-table.js.map +1 -1
- package/dist/date-picker.d.ts +3 -1
- package/dist/date-picker.js +13 -13
- package/dist/date-picker.js.map +1 -1
- package/dist/dropdown-menu.js +1 -1
- package/dist/filter-bar.js +3 -2
- package/dist/form.d.ts +23 -12
- package/dist/form.js +11 -47
- package/dist/form.js.map +1 -1
- package/dist/glow-card.js +1 -1
- package/dist/image-cropper.js +3 -1
- package/dist/image-cropper.js.map +1 -1
- package/dist/marquee.js +1 -1
- package/dist/native/form.d.ts +60 -15
- package/dist/native/form.js +103 -42
- package/dist/native/form.js.map +1 -1
- package/dist/native.d.ts +206 -2
- package/dist/native.js +1172 -541
- package/dist/native.js.map +1 -1
- package/dist/nav-bar.js +1 -1
- package/dist/popover.js +1 -1
- package/dist/radio-group.d.ts +66 -32
- package/dist/radio-group.js +54 -10
- package/dist/radio-group.js.map +1 -1
- package/dist/slider.d.ts +96 -35
- package/dist/slider.js +67 -13
- package/dist/slider.js.map +1 -1
- package/dist/terminal-replay.js +34 -60
- package/dist/terminal-replay.js.map +1 -1
- package/dist/toast.js +1 -1
- package/dist/tooltip.js +1 -1
- package/dist/video-player.js +3 -1
- package/dist/video-player.js.map +1 -1
- package/package.json +6 -6
- package/registry/combobox.json +225 -3
- package/registry/date-picker.json +16 -2
- package/registry/dialog.json +74 -3
- package/registry/examples/combobox/native/multiple.d.ts +4 -0
- package/registry/examples/combobox/native/multiple.dark.png +0 -0
- package/registry/examples/combobox/native/multiple.js +15 -0
- package/registry/examples/combobox/native/multiple.light.png +0 -0
- package/registry/examples/combobox/native/multiple.tsx +15 -0
- package/registry/examples/combobox/native/search.d.ts +4 -0
- package/registry/examples/combobox/native/search.dark.png +0 -0
- package/registry/examples/combobox/native/search.js +15 -0
- package/registry/examples/combobox/native/search.light.png +0 -0
- package/registry/examples/combobox/native/search.tsx +15 -0
- package/registry/examples/dialog/native/sheet.d.ts +4 -0
- package/registry/examples/dialog/native/sheet.dark.png +0 -0
- package/registry/examples/dialog/native/sheet.js +21 -0
- package/registry/examples/dialog/native/sheet.light.png +0 -0
- package/registry/examples/dialog/native/sheet.tsx +18 -0
- package/registry/examples/field/native/select.d.ts +4 -0
- package/registry/examples/field/native/select.dark.png +0 -0
- package/registry/examples/field/native/select.js +29 -0
- package/registry/examples/field/native/select.light.png +0 -0
- package/registry/examples/field/native/select.tsx +25 -0
- package/registry/examples/form/native/audience.dark.png +0 -0
- package/registry/examples/form/native/audience.light.png +0 -0
- package/registry/examples/form/native/profile.dark.png +0 -0
- package/registry/examples/form/native/profile.light.png +0 -0
- package/registry/examples/form/native/settings.dark.png +0 -0
- package/registry/examples/form/native/settings.light.png +0 -0
- package/registry/examples/form/native/sign-up.dark.png +0 -0
- package/registry/examples/form/native/sign-up.light.png +0 -0
- package/registry/examples/form/native/upload.dark.png +0 -0
- package/registry/examples/form/native/upload.light.png +0 -0
- package/registry/examples/radio-group/buttons.d.ts +4 -0
- package/registry/examples/radio-group/buttons.js +17 -0
- package/registry/examples/radio-group/buttons.tsx +21 -0
- package/registry/examples/radio-group/cards.js +1 -1
- package/registry/examples/radio-group/cards.tsx +2 -2
- package/registry/examples/radio-group/native/buttons.d.ts +4 -0
- package/registry/examples/radio-group/native/buttons.dark.png +0 -0
- package/registry/examples/radio-group/native/buttons.js +25 -0
- package/registry/examples/radio-group/native/buttons.light.png +0 -0
- package/registry/examples/radio-group/native/buttons.tsx +21 -0
- package/registry/examples/radio-group/native/cards.d.ts +4 -0
- package/registry/examples/radio-group/native/cards.dark.png +0 -0
- package/registry/examples/radio-group/native/cards.js +23 -0
- package/registry/examples/radio-group/native/cards.light.png +0 -0
- package/registry/examples/radio-group/native/cards.tsx +19 -0
- package/registry/examples/radio-group/native/list.d.ts +4 -0
- package/registry/examples/radio-group/native/list.dark.png +0 -0
- package/registry/examples/radio-group/native/list.js +24 -0
- package/registry/examples/radio-group/native/list.light.png +0 -0
- package/registry/examples/radio-group/native/list.tsx +20 -0
- package/registry/examples/radio-group/native/segmented.d.ts +4 -0
- package/registry/examples/radio-group/native/segmented.dark.png +0 -0
- package/registry/examples/radio-group/native/segmented.js +24 -0
- package/registry/examples/radio-group/native/segmented.light.png +0 -0
- package/registry/examples/radio-group/native/segmented.tsx +20 -0
- package/registry/examples/radio-group/segmented.d.ts +4 -0
- package/registry/examples/radio-group/segmented.js +15 -0
- package/registry/examples/radio-group/segmented.tsx +12 -0
- package/registry/examples/slider/floating.d.ts +4 -0
- package/registry/examples/slider/floating.js +11 -0
- package/registry/examples/slider/floating.tsx +12 -0
- package/registry/examples/slider/native/default.d.ts +4 -0
- package/registry/examples/slider/native/default.dark.png +0 -0
- package/registry/examples/slider/native/default.js +14 -0
- package/registry/examples/slider/native/default.light.png +0 -0
- package/registry/examples/slider/native/default.tsx +13 -0
- package/registry/examples/slider/native/range.d.ts +4 -0
- package/registry/examples/slider/native/range.dark.png +0 -0
- package/registry/examples/slider/native/range.js +12 -0
- package/registry/examples/slider/native/range.light.png +0 -0
- package/registry/examples/slider/native/range.tsx +11 -0
- package/registry/examples/slider/native/stepped.d.ts +4 -0
- package/registry/examples/slider/native/stepped.dark.png +0 -0
- package/registry/examples/slider/native/stepped.js +28 -0
- package/registry/examples/slider/native/stepped.light.png +0 -0
- package/registry/examples/slider/native/stepped.tsx +24 -0
- package/registry/examples/slider/native/vertical.d.ts +4 -0
- package/registry/examples/slider/native/vertical.dark.png +0 -0
- package/registry/examples/slider/native/vertical.js +16 -0
- package/registry/examples/slider/native/vertical.light.png +0 -0
- package/registry/examples/slider/native/vertical.tsx +13 -0
- package/registry/examples/slider/price-range.d.ts +4 -0
- package/registry/examples/slider/price-range.js +29 -0
- package/registry/examples/slider/price-range.tsx +27 -0
- package/registry/examples/slider/stepped.d.ts +4 -0
- package/registry/examples/slider/stepped.js +28 -0
- package/registry/examples/slider/stepped.tsx +24 -0
- package/registry/examples/slider/vertical.d.ts +4 -0
- package/registry/examples/slider/vertical.js +15 -0
- package/registry/examples/slider/vertical.tsx +14 -0
- package/registry/examples/switch/native/default.d.ts +4 -0
- package/registry/examples/switch/native/default.dark.png +0 -0
- package/registry/examples/switch/native/default.js +16 -0
- package/registry/examples/switch/native/default.light.png +0 -0
- package/registry/examples/switch/native/default.tsx +13 -0
- package/registry/examples/switch/native/settings.d.ts +4 -0
- package/registry/examples/switch/native/settings.dark.png +0 -0
- package/registry/examples/switch/native/settings.js +20 -0
- package/registry/examples/switch/native/settings.light.png +0 -0
- package/registry/examples/switch/native/settings.tsx +17 -0
- package/registry/field.json +15 -3
- package/registry/form.json +250 -42
- package/registry/index.json +24 -20
- package/registry/radio-group.json +227 -15
- package/registry/slider.json +314 -13
- package/registry/switch.json +115 -3
- package/registry/tabs.json +2 -2
- package/skills/choice-controls/SKILL.md +39 -0
- package/skills/form/SKILL.md +3 -0
- package/dist/chunk-I5RVM5CW.js.map +0 -1
- package/dist/chunk-RUPRYJ45.js +0 -82
- package/dist/chunk-RUPRYJ45.js.map +0 -1
package/dist/slider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/slider/slider.tsx"],"sourcesContent":["// Client. Radix Slider: role=\"slider\" on each thumb, arrows / Page Up / Page Down / Home / End, a range with two\n// thumbs. The words arrive as props: `label` names the thumb(s), `formatValue` gives the aria-valuetext and the value\n// shown next to a visible label.\nimport { Slider as SliderPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const slider = tv({\n slots: {\n root: \"flex flex-col gap-2\",\n header: \"flex items-baseline justify-between gap-3 text-xs font-semibold text-fg-secondary\",\n value: \"font-mono text-[11px] tabular-nums text-fg-secondary\",\n control:\n \"relative flex h-6 w-full touch-none select-none items-center data-disabled:cursor-not-allowed data-disabled:opacity-40\",\n track: \"relative h-1.5 w-full grow overflow-hidden rounded-full bg-surface-3\",\n range: \"absolute h-full rounded-full bg-linear-to-r from-accent to-accent-2\",\n thumb:\n \"block h-5 w-5 cursor-grab rounded-full border border-border-strong bg-fg-on-media shadow-md transition-[box-shadow] \" +\n \"focus-visible:outline-hidden focus-visible:ring-4 focus-visible:ring-ring/50 active:cursor-grabbing\",\n },\n});\n\nexport type SliderValue = number | [number, number];\n\nexport type SliderProps<V extends SliderValue = number> = Omit<\n React.ComponentProps<typeof SliderPrimitive.Root>,\n \"value\" | \"defaultValue\" | \"onValueChange\" | \"onValueCommit\" | \"children\"\n> & {\n /** One number (a thumb) or two (a range). Controlled with `onValueChange`. */\n value?: V;\n /** The value at first, uncontrolled: one number, or two for a range. */\n defaultValue?: V;\n /** Called on every move, with one number or two. */\n onValueChange?: (value: V) => void;\n /** Called once the drag or the key press ends. */\n onValueCommit?: (value: V) => void;\n /** The accessible name — of the thumb, or of both thumbs of a range — passed translated. */\n label: string;\n /** For a range: each thumb's own name ([\"Minimum\", \"Maximum\"]), passed translated. */\n thumbLabels?: [string, string];\n /** The value as words (aria-valuetext) and as shown: \"1.5×\", \"−12 %\", \"3 min\". */\n formatValue?: (value: number) => string;\n /** Shows the label and the value above the track. Without it, `label` only names the thumb. */\n showLabel?: boolean;\n /** Fills from this value instead of the minimum (a ±range from 0: brightness, balance). One thumb only. */\n origin?: number;\n};\n\nconst toArray = (v: SliderValue | undefined) => (v === undefined ? undefined : typeof v === \"number\" ? [v] : v);\n\n/** A slider on one value or a range: `value`/`defaultValue` + `onValueChange`, `min`, `max`, `step`. */\nexport function Slider<V extends SliderValue = number>({\n value,\n defaultValue,\n onValueChange,\n onValueCommit,\n label,\n thumbLabels,\n formatValue = String,\n showLabel,\n origin,\n min = 0,\n max = 100,\n className,\n ...props\n}: SliderProps<V>) {\n const s = slider();\n const range = Array.isArray(value ?? defaultValue);\n const [inner, setInner] = React.useState(() => toArray(defaultValue) ?? [min]);\n const current = toArray(value) ?? inner;\n const out = (next: number[]) => (range ? next : next[0]) as V;\n const pct = (v: number) => ((v - min) / (max - min || 1)) * 100;\n const fill =\n origin !== undefined && !range\n ? { left: `${Math.min(pct(origin), pct(current[0]))}%`, right: `${100 - Math.max(pct(origin), pct(current[0]))}%` }\n : undefined;\n const id = React.useId();\n return (\n <div className={s.root({ className })} data-range={range ? \"\" : undefined}>\n {showLabel && (\n <div className={s.header()}>\n <span id={`${id}-label`}>{label}</span>\n <span className={s.value()} aria-hidden>\n {current.map(formatValue).join(\" – \")}\n </span>\n </div>\n )}\n <SliderPrimitive.Root\n min={min}\n max={max}\n value={current}\n onValueChange={(next) => {\n if (value === undefined) setInner(next);\n onValueChange?.(out(next));\n }}\n onValueCommit={onValueCommit && ((next) => onValueCommit(out(next)))}\n className={s.control()}\n {...props}\n >\n <SliderPrimitive.Track className={s.track()}>\n {fill ? <span data-origin=\"\" className={s.range()} style={fill} /> : <SliderPrimitive.Range className={s.range()} />}\n </SliderPrimitive.Track>\n {current.map((v, i) => (\n <SliderPrimitive.Thumb\n key={i}\n aria-label={range ? (thumbLabels?.[i] ?? label) : showLabel ? undefined : label}\n aria-labelledby={!range && showLabel ? `${id}-label` : undefined}\n aria-valuetext={formatValue(v)}\n className={s.thumb()}\n />\n ))}\n </SliderPrimitive.Root>\n </div>\n );\n}\n"],"mappings":";;;AAGA,SAAS,UAAU,uBAAuB;AAC1C,YAAY,WAAW;AACvB,SAAS,UAAU;AA2EX,SACE,KADF;AAzED,IAAM,SAAS,GAAG;AAAA,EACvB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,SACE;AAAA,IACF,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OACE;AAAA,EAEJ;AACF,CAAC;AA4BD,IAAM,UAAU,CAAC,MAAgC,MAAM,SAAY,SAAY,OAAO,MAAM,WAAW,CAAC,CAAC,IAAI;AAGtG,SAAS,OAAuC;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,MAAM;AAAA,EACN;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAM,IAAI,OAAO;AACjB,QAAM,QAAQ,MAAM,QAAQ,SAAS,YAAY;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAS,MAAM,QAAQ,YAAY,KAAK,CAAC,GAAG,CAAC;AAC7E,QAAM,UAAU,QAAQ,KAAK,KAAK;AAClC,QAAM,MAAM,CAAC,SAAoB,QAAQ,OAAO,KAAK,CAAC;AACtD,QAAM,MAAM,CAAC,OAAgB,IAAI,QAAQ,MAAM,OAAO,KAAM;AAC5D,QAAM,OACJ,WAAW,UAAa,CAAC,QACrB,EAAE,MAAM,GAAG,KAAK,IAAI,IAAI,MAAM,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,GAAG,MAAM,KAAK,IAAI,IAAI,MAAM,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,IAChH;AACN,QAAM,KAAW,YAAM;AACvB,SACE,qBAAC,SAAI,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,cAAY,QAAQ,KAAK,QAC7D;AAAA,iBACC,qBAAC,SAAI,WAAW,EAAE,OAAO,GACvB;AAAA,0BAAC,UAAK,IAAI,GAAG,EAAE,UAAW,iBAAM;AAAA,MAChC,oBAAC,UAAK,WAAW,EAAE,MAAM,GAAG,eAAW,MACpC,kBAAQ,IAAI,WAAW,EAAE,KAAK,UAAK,GACtC;AAAA,OACF;AAAA,IAEF;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP,eAAe,CAAC,SAAS;AACvB,cAAI,UAAU,OAAW,UAAS,IAAI;AACtC,0BAAgB,IAAI,IAAI,CAAC;AAAA,QAC3B;AAAA,QACA,eAAe,kBAAkB,CAAC,SAAS,cAAc,IAAI,IAAI,CAAC;AAAA,QAClE,WAAW,EAAE,QAAQ;AAAA,QACpB,GAAG;AAAA,QAEJ;AAAA,8BAAC,gBAAgB,OAAhB,EAAsB,WAAW,EAAE,MAAM,GACvC,iBAAO,oBAAC,UAAK,eAAY,IAAG,WAAW,EAAE,MAAM,GAAG,OAAO,MAAM,IAAK,oBAAC,gBAAgB,OAAhB,EAAsB,WAAW,EAAE,MAAM,GAAG,GACpH;AAAA,UACC,QAAQ,IAAI,CAAC,GAAG,MACf;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cAEC,cAAY,QAAS,cAAc,CAAC,KAAK,QAAS,YAAY,SAAY;AAAA,cAC1E,mBAAiB,CAAC,SAAS,YAAY,GAAG,EAAE,WAAW;AAAA,cACvD,kBAAgB,YAAY,CAAC;AAAA,cAC7B,WAAW,EAAE,MAAM;AAAA;AAAA,YAJd;AAAA,UAKP,CACD;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/slider/slider.tsx"],"sourcesContent":["// Client. Radix Slider: role=\"slider\" on each thumb, arrows / Page Up / Page Down / Home / End, a range with two\n// thumbs, horizontal or vertical. The words arrive as props: `label` names the thumb(s), `formatValue` gives the\n// aria-valuetext and the value shown (above the track, or floating over the thumb while it is touched). `ticks` draws the\n// graduations of the steps (and `marks` their words under the track); `tone` fills with the brand's gradient or its\n// accent alone.\nimport { Slider as SliderPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { tv } from \"../tv\";\n\nexport const slider = tv({\n slots: {\n root: \"flex flex-col gap-2 data-[orientation=vertical]:h-full data-[orientation=vertical]:items-center\",\n header: \"flex items-baseline justify-between gap-3 text-xs font-semibold text-fg-secondary\",\n value: \"font-mono text-[11px] tabular-nums text-fg-secondary\",\n control:\n \"relative flex touch-none select-none items-center data-disabled:cursor-not-allowed data-disabled:opacity-40 \" +\n \"data-[orientation=horizontal]:h-6 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-6 data-[orientation=vertical]:flex-col\",\n track:\n \"relative grow overflow-hidden rounded-full bg-surface-3 inset-shadow-well \" +\n \"data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\",\n range: \"absolute rounded-full data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\",\n thumb:\n \"group/thumb relative block h-5 w-5 cursor-grab rounded-full border-2 border-accent bg-fg-on-media shadow-raised transition-[box-shadow,scale] duration-150 \" +\n \"hover:scale-110 focus-visible:outline-hidden focus-visible:focus-ring active:scale-110 active:cursor-grabbing motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:active:scale-100\",\n // The value over the thumb: shown while the thumb is hovered, focused or held.\n bubble:\n \"pointer-events-none absolute bottom-full left-1/2 mb-2.5 -translate-x-1/2 whitespace-nowrap rounded-lg bg-fg px-2 py-1 font-mono text-[11px] font-semibold tabular-nums text-surface-0 opacity-0 shadow-floating \" +\n \"transition-opacity duration-150 after:absolute after:left-1/2 after:top-full after:-translate-x-1/2 after:border-4 after:border-transparent after:border-t-fg \" +\n \"group-hover/thumb:opacity-100 group-focus-visible/thumb:opacity-100 group-active/thumb:opacity-100 data-[always]:opacity-100 motion-reduce:transition-none\",\n bubbleVertical: \"bottom-auto left-full top-1/2 mb-0 ml-3 -translate-x-0 -translate-y-1/2 after:hidden\",\n ticks: \"pointer-events-none absolute inset-0\",\n tick: \"absolute rounded-full bg-border-strong data-[on]:bg-on-accent/70 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:h-1 data-[orientation=horizontal]:w-px data-[orientation=horizontal]:-translate-x-1/2 data-[orientation=horizontal]:-translate-y-1/2 data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:h-px data-[orientation=vertical]:w-1 data-[orientation=vertical]:-translate-x-1/2 data-[orientation=vertical]:translate-y-1/2\",\n marks: \"relative h-4 w-full text-[11px] font-medium tabular-nums text-fg-secondary\",\n mark: \"absolute top-0 -translate-x-1/2 whitespace-nowrap first:translate-x-0 last:-translate-x-full\",\n },\n variants: {\n tone: {\n /** The brand's signature: accent → accent-2, a glow under it. */\n gradient: { range: \"bg-linear-to-r from-accent to-accent-2 shadow-glow data-[orientation=vertical]:bg-linear-to-t\" },\n /** The accent alone. */\n solid: { range: \"bg-accent\" },\n },\n size: {\n md: {},\n lg: {\n track: \"data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5\",\n thumb: \"h-6 w-6\",\n control: \"data-[orientation=horizontal]:h-8 data-[orientation=vertical]:w-8\",\n },\n },\n },\n defaultVariants: { tone: \"gradient\", size: \"md\" },\n});\n\nexport type SliderValue = number | [number, number];\n\nexport type SliderMark = {\n /** Where the mark stands. */\n value: number;\n /** Its words under the track (\"0\", \"50 %\", \"Max\"), passed translated. */\n label: string;\n};\n\nexport type SliderProps<V extends SliderValue = number> = Omit<\n React.ComponentProps<typeof SliderPrimitive.Root>,\n \"value\" | \"defaultValue\" | \"onValueChange\" | \"onValueCommit\" | \"children\"\n> & {\n /** One number (a thumb) or two (a range). Controlled with `onValueChange`. */\n value?: V;\n /** The value at first, uncontrolled: one number, or two for a range. */\n defaultValue?: V;\n /** Called on every move, with one number or two. */\n onValueChange?: (value: V) => void;\n /** Called once the drag or the key press ends. */\n onValueCommit?: (value: V) => void;\n /** The accessible name — of the thumb, or of both thumbs of a range — passed translated. */\n label: string;\n /** For a range: each thumb's own name ([\"Minimum\", \"Maximum\"]), passed translated. */\n thumbLabels?: [string, string];\n /** The value as words (aria-valuetext) and as shown: \"1.5×\", \"−12 %\", \"3 min\". */\n formatValue?: (value: number) => string;\n /** Shows the label and the value above the track. Without it, `label` only names the thumb. */\n showLabel?: boolean;\n /** Shows the value floating over the thumb while it is hovered, focused or held (`\"always\"`: at all times). */\n floatingValue?: boolean | \"always\";\n /** Fills from this value instead of the minimum (a ±range from 0: brightness, balance). One horizontal thumb only. */\n origin?: number;\n /** Draws a graduation at every step (`true`, up to 40 of them) or at the values given. */\n ticks?: boolean | readonly number[];\n /** Words under the track at some values (`[{ value: 0, label: \"0\" }, { value: 100, label: \"100\" }]`). Horizontal only. */\n marks?: readonly SliderMark[];\n /**\n * `gradient` (the brand's accent → accent-2, a glow) or `solid` (the accent alone).\n * @default \"gradient\"\n */\n tone?: \"gradient\" | \"solid\";\n /**\n * md · lg (a thicker track and thumb: a hero control, a touch screen).\n * @default \"md\"\n */\n size?: \"md\" | \"lg\";\n};\n\nconst toArray = (v: SliderValue | undefined) => (v === undefined ? undefined : typeof v === \"number\" ? [v] : v);\n\n/** A slider on one value or a range: `value`/`defaultValue` + `onValueChange`, `min`, `max`, `step`, `orientation`. */\nexport function Slider<V extends SliderValue = number>({\n value,\n defaultValue,\n onValueChange,\n onValueCommit,\n label,\n thumbLabels,\n formatValue = String,\n showLabel,\n floatingValue,\n origin,\n ticks,\n marks,\n tone,\n size,\n min = 0,\n max = 100,\n step = 1,\n orientation = \"horizontal\",\n className,\n ...props\n}: SliderProps<V>) {\n const s = slider({ tone, size });\n const range = Array.isArray(value ?? defaultValue);\n const vertical = orientation === \"vertical\";\n const [inner, setInner] = React.useState(() => toArray(defaultValue) ?? [min]);\n const current = toArray(value) ?? inner;\n const out = (next: number[]) => (range ? next : next[0]) as V;\n const pct = (v: number) => ((v - min) / (max - min || 1)) * 100;\n const fill =\n origin !== undefined && !range && !vertical\n ? { left: `${Math.min(pct(origin), pct(current[0]!))}%`, right: `${100 - Math.max(pct(origin), pct(current[0]!))}%` }\n : undefined;\n const lo = range ? Math.min(...current) : (origin ?? min);\n const hi = range ? Math.max(...current) : current[0]!;\n const tickValues = ticks === true ? (max - min) / step <= 40 ? Array.from({ length: Math.round((max - min) / step) + 1 }, (_, i) => min + i * step) : [] : ticks ? [...ticks] : [];\n const id = React.useId();\n return (\n <div className={s.root({ className })} data-range={range ? \"\" : undefined} data-orientation={orientation}>\n {showLabel && (\n <div className={s.header()}>\n <span id={`${id}-label`}>{label}</span>\n <span className={s.value()} aria-hidden>\n {current.map(formatValue).join(\" – \")}\n </span>\n </div>\n )}\n <SliderPrimitive.Root\n min={min}\n max={max}\n step={step}\n orientation={orientation}\n value={current}\n onValueChange={(next) => {\n if (value === undefined) setInner(next);\n onValueChange?.(out(next));\n }}\n onValueCommit={onValueCommit && ((next) => onValueCommit(out(next)))}\n className={s.control()}\n {...props}\n >\n <SliderPrimitive.Track className={s.track()}>\n {fill ? <span data-origin=\"\" data-orientation={orientation} className={s.range()} style={fill} /> : <SliderPrimitive.Range className={s.range()} />}\n {tickValues.length ? (\n <span aria-hidden className={s.ticks()}>\n {tickValues.map((t) => (\n <span\n key={t}\n data-orientation={orientation}\n data-on={t > lo && t < hi ? \"\" : undefined}\n className={s.tick()}\n style={vertical ? { bottom: `${pct(t)}%` } : { left: `${pct(t)}%` }}\n />\n ))}\n </span>\n ) : null}\n </SliderPrimitive.Track>\n {current.map((v, i) => (\n <SliderPrimitive.Thumb\n key={i}\n aria-label={range ? (thumbLabels?.[i] ?? label) : showLabel ? undefined : label}\n aria-labelledby={!range && showLabel ? `${id}-label` : undefined}\n aria-valuetext={formatValue(v)}\n className={s.thumb()}\n >\n {floatingValue ? (\n <span aria-hidden data-always={floatingValue === \"always\" ? \"\" : undefined} className={s.bubble({ className: vertical ? s.bubbleVertical() : undefined })}>\n {formatValue(v)}\n </span>\n ) : null}\n </SliderPrimitive.Thumb>\n ))}\n </SliderPrimitive.Root>\n {marks?.length && !vertical ? (\n <div aria-hidden className={s.marks()}>\n {marks.map((m) => (\n <span key={m.value} className={s.mark()} style={{ left: `${pct(m.value)}%` }}>\n {m.label}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/terminal-replay.js
CHANGED
|
@@ -1,43 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import {
|
|
3
|
+
CheckGlyph,
|
|
4
|
+
PauseGlyph,
|
|
5
|
+
PlayGlyph,
|
|
6
|
+
RetryGlyph
|
|
7
|
+
} from "./chunk-KDXRYUD3.js";
|
|
2
8
|
import {
|
|
3
9
|
cn
|
|
4
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2FRWNSRO.js";
|
|
5
11
|
|
|
6
12
|
// src/terminal-replay/terminal-replay.tsx
|
|
7
13
|
import * as React from "react";
|
|
8
|
-
|
|
9
|
-
// src/glyphs.tsx
|
|
10
14
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
function Glyph({ className = "h-4 w-4", fill, children }) {
|
|
12
|
-
return /* @__PURE__ */ jsx(
|
|
13
|
-
"svg",
|
|
14
|
-
{
|
|
15
|
-
viewBox: "0 0 24 24",
|
|
16
|
-
className,
|
|
17
|
-
fill: fill ? "currentColor" : "none",
|
|
18
|
-
stroke: fill ? "none" : "currentColor",
|
|
19
|
-
strokeWidth: "2",
|
|
20
|
-
strokeLinecap: "round",
|
|
21
|
-
strokeLinejoin: "round",
|
|
22
|
-
"aria-hidden": true,
|
|
23
|
-
focusable: "false",
|
|
24
|
-
children
|
|
25
|
-
}
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
var PlayGlyph = (p) => /* @__PURE__ */ jsx(Glyph, { ...p, fill: true, children: /* @__PURE__ */ jsx("path", { d: "M8 5.5v13a1 1 0 0 0 1.5.86l10.4-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5Z" }) });
|
|
29
|
-
var PauseGlyph = (p) => /* @__PURE__ */ jsxs(Glyph, { ...p, fill: true, children: [
|
|
30
|
-
/* @__PURE__ */ jsx("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1" }),
|
|
31
|
-
/* @__PURE__ */ jsx("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1" })
|
|
32
|
-
] });
|
|
33
|
-
var CheckGlyph = (p) => /* @__PURE__ */ jsx(Glyph, { ...p, children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
|
|
34
|
-
var RetryGlyph = (p) => /* @__PURE__ */ jsxs(Glyph, { ...p, children: [
|
|
35
|
-
/* @__PURE__ */ jsx("path", { d: "M4 12a8 8 0 1 0 2.3-5.7L4 8.5" }),
|
|
36
|
-
/* @__PURE__ */ jsx("path", { d: "M4 4v4.5h4.5" })
|
|
37
|
-
] });
|
|
38
|
-
|
|
39
|
-
// src/terminal-replay/terminal-replay.tsx
|
|
40
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
41
15
|
var TONE = {
|
|
42
16
|
muted: "text-fg-secondary",
|
|
43
17
|
accent: "text-fg-accent",
|
|
@@ -130,7 +104,7 @@ function TerminalReplay({
|
|
|
130
104
|
const control = state === "playing" ? { text: pauseLabel, Glyph: PauseGlyph } : state === "done" ? { text: replayLabel, Glyph: RetryGlyph } : { text: playLabel, Glyph: PlayGlyph };
|
|
131
105
|
const commands = steps.map((s) => s.command).join("\n");
|
|
132
106
|
const transcript = steps.map((s) => [`${prompt} ${s.command}`, ...(s.output ?? []).map((o) => line(o).text)].join("\n")).join("\n\n");
|
|
133
|
-
return /* @__PURE__ */
|
|
107
|
+
return /* @__PURE__ */ jsxs(
|
|
134
108
|
"figure",
|
|
135
109
|
{
|
|
136
110
|
ref: root,
|
|
@@ -140,26 +114,26 @@ function TerminalReplay({
|
|
|
140
114
|
style: { "--kz-replay-rows": rows, ...style },
|
|
141
115
|
...props,
|
|
142
116
|
children: [
|
|
143
|
-
/* @__PURE__ */
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
-
/* @__PURE__ */
|
|
146
|
-
/* @__PURE__ */
|
|
147
|
-
/* @__PURE__ */
|
|
117
|
+
/* @__PURE__ */ jsxs("figcaption", { className: "flex items-center gap-3 border-b border-border-subtle bg-surface-2 px-3 py-2 text-xs text-fg-secondary", children: [
|
|
118
|
+
/* @__PURE__ */ jsxs("span", { "aria-hidden": true, className: "flex shrink-0 gap-1.5", children: [
|
|
119
|
+
/* @__PURE__ */ jsx("span", { className: "h-2.5 w-2.5 rounded-full bg-border-strong" }),
|
|
120
|
+
/* @__PURE__ */ jsx("span", { className: "h-2.5 w-2.5 rounded-full bg-border-strong" }),
|
|
121
|
+
/* @__PURE__ */ jsx("span", { className: "h-2.5 w-2.5 rounded-full bg-border-strong" })
|
|
148
122
|
] }),
|
|
149
|
-
/* @__PURE__ */
|
|
150
|
-
/* @__PURE__ */
|
|
123
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-mono", children: title }),
|
|
124
|
+
/* @__PURE__ */ jsxs(
|
|
151
125
|
"button",
|
|
152
126
|
{
|
|
153
127
|
type: "button",
|
|
154
128
|
onClick: toggle,
|
|
155
129
|
className: "inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 font-medium text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg focus-visible:outline-2 focus-visible:outline-ring",
|
|
156
130
|
children: [
|
|
157
|
-
/* @__PURE__ */
|
|
131
|
+
/* @__PURE__ */ jsx(control.Glyph, { className: "h-3.5 w-3.5" }),
|
|
158
132
|
control.text
|
|
159
133
|
]
|
|
160
134
|
}
|
|
161
135
|
),
|
|
162
|
-
copyLabel && /* @__PURE__ */
|
|
136
|
+
copyLabel && /* @__PURE__ */ jsxs(
|
|
163
137
|
"button",
|
|
164
138
|
{
|
|
165
139
|
type: "button",
|
|
@@ -167,13 +141,13 @@ function TerminalReplay({
|
|
|
167
141
|
"data-copied": copied ? "" : void 0,
|
|
168
142
|
className: "inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 font-medium text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg focus-visible:outline-2 focus-visible:outline-ring",
|
|
169
143
|
children: [
|
|
170
|
-
copied ? /* @__PURE__ */
|
|
171
|
-
/* @__PURE__ */
|
|
144
|
+
copied ? /* @__PURE__ */ jsx(CheckGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(CopyGlyph, {}),
|
|
145
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", children: copied ? copiedLabel ?? copyLabel : copyLabel })
|
|
172
146
|
]
|
|
173
147
|
}
|
|
174
148
|
)
|
|
175
149
|
] }),
|
|
176
|
-
/* @__PURE__ */
|
|
150
|
+
/* @__PURE__ */ jsxs(
|
|
177
151
|
"div",
|
|
178
152
|
{
|
|
179
153
|
ref: screenRef,
|
|
@@ -182,36 +156,36 @@ function TerminalReplay({
|
|
|
182
156
|
tabIndex: 0,
|
|
183
157
|
className: "kz-replay-screen relative overflow-auto bg-surface-0 px-4 py-3 font-mono text-[0.8125rem] leading-relaxed text-fg focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring",
|
|
184
158
|
children: [
|
|
185
|
-
/* @__PURE__ */
|
|
159
|
+
/* @__PURE__ */ jsxs("div", { "aria-hidden": true, className: "kz-replay-live", children: [
|
|
186
160
|
steps.slice(0, state === "idle" ? 0 : shown + 1).map((step, i) => {
|
|
187
161
|
const current = !whole && i === shown;
|
|
188
162
|
const typed = current ? step.command.slice(0, pos.chars) : step.command;
|
|
189
163
|
const output = (step.output ?? []).slice(0, current ? pos.lines : void 0);
|
|
190
164
|
const typing = current && pos.chars < step.command.length;
|
|
191
|
-
return /* @__PURE__ */
|
|
192
|
-
/* @__PURE__ */
|
|
193
|
-
/* @__PURE__ */
|
|
165
|
+
return /* @__PURE__ */ jsxs("div", { className: i > 0 ? "mt-3" : void 0, children: [
|
|
166
|
+
/* @__PURE__ */ jsxs("div", { className: "whitespace-pre", children: [
|
|
167
|
+
/* @__PURE__ */ jsxs("span", { className: "text-fg-accent select-none", children: [
|
|
194
168
|
prompt,
|
|
195
169
|
" "
|
|
196
170
|
] }),
|
|
197
|
-
/* @__PURE__ */
|
|
198
|
-
typing && /* @__PURE__ */
|
|
171
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: typed }),
|
|
172
|
+
typing && /* @__PURE__ */ jsx("span", { className: "kz-replay-caret" })
|
|
199
173
|
] }),
|
|
200
174
|
output.map((entry, j) => {
|
|
201
175
|
const { text, tone } = line(entry);
|
|
202
|
-
return /* @__PURE__ */
|
|
176
|
+
return /* @__PURE__ */ jsx("div", { className: cn("min-h-[1lh] whitespace-pre", tone ? TONE[tone] : "text-fg"), children: text }, j);
|
|
203
177
|
})
|
|
204
178
|
] }, i);
|
|
205
179
|
}),
|
|
206
|
-
(state === "idle" || whole) && /* @__PURE__ */
|
|
207
|
-
/* @__PURE__ */
|
|
180
|
+
(state === "idle" || whole) && /* @__PURE__ */ jsxs("div", { className: cn("whitespace-pre", state !== "idle" && "mt-3"), children: [
|
|
181
|
+
/* @__PURE__ */ jsxs("span", { className: "text-fg-accent select-none", children: [
|
|
208
182
|
prompt,
|
|
209
183
|
" "
|
|
210
184
|
] }),
|
|
211
|
-
/* @__PURE__ */
|
|
185
|
+
/* @__PURE__ */ jsx("span", { className: "kz-replay-caret" })
|
|
212
186
|
] })
|
|
213
187
|
] }),
|
|
214
|
-
/* @__PURE__ */
|
|
188
|
+
/* @__PURE__ */ jsx("pre", { className: "sr-only", children: transcript })
|
|
215
189
|
]
|
|
216
190
|
}
|
|
217
191
|
)
|
|
@@ -220,9 +194,9 @@ function TerminalReplay({
|
|
|
220
194
|
);
|
|
221
195
|
}
|
|
222
196
|
function CopyGlyph() {
|
|
223
|
-
return /* @__PURE__ */
|
|
224
|
-
/* @__PURE__ */
|
|
225
|
-
/* @__PURE__ */
|
|
197
|
+
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", className: "h-3.5 w-3.5", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, focusable: "false", children: [
|
|
198
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "11", height: "11", rx: "2" }),
|
|
199
|
+
/* @__PURE__ */ jsx("path", { d: "M5 15V6a2 2 0 0 1 2-2h8" })
|
|
226
200
|
] });
|
|
227
201
|
}
|
|
228
202
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/terminal-replay/terminal-replay.tsx","../src/glyphs.tsx"],"sourcesContent":["// Client (a timer, an observer). A terminal session played back: each command is typed at the prompt, its output\n// arrives line by line, the screen follows the last line like a real terminal. What a reader sees without the motion\n// is the session itself: the server renders it whole (no JavaScript, reduced motion and assistive technology all get\n// the full transcript), the screen has a fixed height (no layout shift while it plays), the replay starts when it\n// scrolls into view, waits while it is off screen, and pauses or replays on demand (WCAG 2.2.2). A bounded chain of\n// timeouts, never a permanent animation frame.\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { CheckGlyph, PauseGlyph, PlayGlyph, RetryGlyph } from \"../glyphs\";\n\n/** The colour of an output line — the tokens' text roles, all AA on the screen: secondary, accent, success, warning, danger. */\nexport type TerminalTone = \"muted\" | \"accent\" | \"success\" | \"warning\" | \"danger\";\n\nexport interface TerminalLine {\n /** The line as the program printed it (no colour codes). */\n text: string;\n /** Its colour, when the program coloured it. */\n tone?: TerminalTone;\n}\n\nexport interface TerminalStep {\n /** What is typed at the prompt (`npx orazaka install`). */\n command: string;\n /** What it printed, line by line. */\n output?: readonly (string | TerminalLine)[];\n}\n\nexport type TerminalReplayProps = Omit<React.ComponentProps<\"figure\">, \"children\" | \"title\"> & {\n /** The session, in order. */\n steps: readonly TerminalStep[];\n /** The accessible name of the screen (“Terminal session: install, start, dev”) — passed translated. */\n label: string;\n /** The window's title, shown in its bar (`~/projects — zsh`). */\n title?: React.ReactNode;\n /**\n * What the prompt shows before each command.\n * @default \"$\"\n */\n prompt?: string;\n /** The play button's words (the replay is waiting or paused). */\n playLabel: string;\n /** The pause button's words (the replay is running). */\n pauseLabel: string;\n /** The replay button's words (the session is shown whole). */\n replayLabel: string;\n /** The copy button's words: it copies the commands, one per line. Without it, no copy button. */\n copyLabel?: string;\n /** What the copy button says once the commands are copied (announced politely). */\n copiedLabel?: string;\n /**\n * Milliseconds per typed character.\n * @default 34\n */\n typingSpeed?: number;\n /**\n * Milliseconds between two output lines.\n * @default 70\n */\n lineDelay?: number;\n /**\n * Milliseconds between a command's last line and the next command.\n * @default 900\n */\n stepDelay?: number;\n /**\n * The screen's height, in lines; longer sessions scroll.\n * @default 18\n */\n rows?: number;\n /**\n * Plays the first time it scrolls into view. `false`: the session rests whole until the reader presses play.\n * @default true\n */\n autoPlay?: boolean;\n};\n\ntype Phase = \"final\" | \"idle\" | \"playing\" | \"paused\" | \"done\";\ninterface Position {\n step: number;\n chars: number;\n lines: number;\n}\n\nconst TONE: Record<TerminalTone, string> = {\n muted: \"text-fg-secondary\",\n accent: \"text-fg-accent\",\n success: \"text-fg-success\",\n warning: \"text-fg-warning\",\n danger: \"text-fg-danger\",\n};\n\nconst line = (entry: string | TerminalLine): TerminalLine => (typeof entry === \"string\" ? { text: entry } : entry);\n\n/** The next position of the replay and how long to wait for it; null at the end of the session. */\nfunction advance(steps: readonly TerminalStep[], pos: Position, timing: { typing: number; line: number; step: number }): [Position, number] | null {\n const current = steps[pos.step];\n if (!current) return null;\n const output = current.output ?? [];\n if (pos.chars < current.command.length) {\n // A space is a beat longer: the rhythm of someone typing, not of a printer.\n const pause = current.command[pos.chars] === \" \" ? timing.typing * 2 : timing.typing;\n return [{ ...pos, chars: pos.chars + 1 }, pause];\n }\n if (pos.lines < output.length) return [{ ...pos, lines: pos.lines + 1 }, pos.lines === 0 ? timing.line * 4 : timing.line];\n if (pos.step < steps.length - 1) return [{ step: pos.step + 1, chars: 0, lines: 0 }, timing.step];\n return null;\n}\n\n/** A terminal session played back: commands typed at the prompt, their output line by line — or the whole session at rest. */\nexport function TerminalReplay({\n steps,\n label,\n title,\n prompt = \"$\",\n playLabel,\n pauseLabel,\n replayLabel,\n copyLabel,\n copiedLabel,\n typingSpeed = 34,\n lineDelay = 70,\n stepDelay = 900,\n rows = 18,\n autoPlay = true,\n className,\n style,\n ...props\n}: TerminalReplayProps) {\n const root = React.useRef<HTMLElement>(null);\n const screenRef = React.useRef<HTMLDivElement>(null);\n // The server renders the session whole; the client takes it over unless the reader asked for less motion.\n const [phase, setPhase] = React.useState<Phase>(\"final\");\n const [pos, setPos] = React.useState<Position>({ step: 0, chars: 0, lines: 0 });\n const [visible, setVisible] = React.useState(false);\n const [copied, setCopied] = React.useState(false);\n\n React.useEffect(() => {\n const el = root.current;\n if (!el || !autoPlay || window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n setPhase(\"idle\");\n // Starts the first time it is seen; off screen, it waits where it is.\n const io = new IntersectionObserver(\n ([entry]) => {\n const seen = Boolean(entry?.isIntersecting);\n setVisible(seen);\n if (seen) setPhase((current) => (current === \"idle\" ? \"playing\" : current));\n },\n { threshold: 0.35 },\n );\n io.observe(el);\n return () => io.disconnect();\n }, [autoPlay]);\n\n const ended = phase === \"playing\" && !advance(steps, pos, { typing: typingSpeed, line: lineDelay, step: stepDelay });\n // The end of the session is where the replay stands, not an event: derived, never set from an effect.\n const state: Phase = ended ? \"done\" : phase;\n\n React.useEffect(() => {\n if (phase !== \"playing\" || !visible) return;\n const next = advance(steps, pos, { typing: typingSpeed, line: lineDelay, step: stepDelay });\n if (!next) return;\n const timer = setTimeout(() => setPos(next[0]), next[1]);\n return () => clearTimeout(timer);\n }, [phase, visible, pos, steps, typingSpeed, lineDelay, stepDelay]);\n\n // The screen follows the last line while it plays, like a terminal.\n React.useEffect(() => {\n const el = screenRef.current;\n if (el && state === \"playing\") el.scrollTop = el.scrollHeight;\n }, [pos, state]);\n\n React.useEffect(() => {\n if (!copied) return;\n const timer = setTimeout(() => setCopied(false), 1800);\n return () => clearTimeout(timer);\n }, [copied]);\n\n const whole = state === \"final\" || state === \"done\";\n const shown = whole ? steps.length - 1 : pos.step;\n const toggle = () => {\n if (state === \"playing\") setPhase(\"paused\");\n else if (state === \"paused\" || state === \"idle\") setPhase(\"playing\");\n else {\n // Played on demand — also under reduced motion: the reader asked for it. Off screen it would wait: it is on screen.\n setVisible(true);\n setPos({ step: 0, chars: 0, lines: 0 });\n setPhase(\"playing\");\n }\n };\n const control =\n state === \"playing\"\n ? { text: pauseLabel, Glyph: PauseGlyph }\n : state === \"done\"\n ? { text: replayLabel, Glyph: RetryGlyph }\n : { text: playLabel, Glyph: PlayGlyph };\n const commands = steps.map((s) => s.command).join(\"\\n\");\n const transcript = steps.map((s) => [`${prompt} ${s.command}`, ...(s.output ?? []).map((o) => line(o).text)].join(\"\\n\")).join(\"\\n\\n\");\n\n return (\n <figure\n ref={root}\n data-state={state}\n data-autoplay={autoPlay ? \"\" : undefined}\n className={cn(\"kz-replay overflow-hidden rounded-xl border border-border-default bg-surface-1 shadow-md\", className)}\n style={{ \"--kz-replay-rows\": rows, ...style } as React.CSSProperties}\n {...props}\n >\n <figcaption className=\"flex items-center gap-3 border-b border-border-subtle bg-surface-2 px-3 py-2 text-xs text-fg-secondary\">\n <span aria-hidden className=\"flex shrink-0 gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-border-strong\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-border-strong\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-border-strong\" />\n </span>\n <span className=\"min-w-0 flex-1 truncate font-mono\">{title}</span>\n <button\n type=\"button\"\n onClick={toggle}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 font-medium text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg focus-visible:outline-2 focus-visible:outline-ring\"\n >\n <control.Glyph className=\"h-3.5 w-3.5\" />\n {control.text}\n </button>\n {copyLabel && (\n <button\n type=\"button\"\n onClick={() => navigator.clipboard?.writeText(commands).then(() => setCopied(true))}\n data-copied={copied ? \"\" : undefined}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 font-medium text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg focus-visible:outline-2 focus-visible:outline-ring\"\n >\n {copied ? <CheckGlyph className=\"h-3.5 w-3.5\" /> : <CopyGlyph />}\n <span aria-live=\"polite\">{copied ? (copiedLabel ?? copyLabel) : copyLabel}</span>\n </button>\n )}\n </figcaption>\n <div\n ref={screenRef}\n role=\"region\"\n aria-label={label}\n tabIndex={0}\n className=\"kz-replay-screen relative overflow-auto bg-surface-0 px-4 py-3 font-mono text-[0.8125rem] leading-relaxed text-fg focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring\"\n >\n <div aria-hidden className=\"kz-replay-live\">\n {steps.slice(0, state === \"idle\" ? 0 : shown + 1).map((step, i) => {\n const current = !whole && i === shown;\n const typed = current ? step.command.slice(0, pos.chars) : step.command;\n const output = (step.output ?? []).slice(0, current ? pos.lines : undefined);\n const typing = current && pos.chars < step.command.length;\n return (\n <div key={i} className={i > 0 ? \"mt-3\" : undefined}>\n <div className=\"whitespace-pre\">\n <span className=\"text-fg-accent select-none\">{prompt} </span>\n <span className=\"font-medium\">{typed}</span>\n {typing && <span className=\"kz-replay-caret\" />}\n </div>\n {output.map((entry, j) => {\n const { text, tone } = line(entry);\n return (\n <div key={j} className={cn(\"min-h-[1lh] whitespace-pre\", tone ? TONE[tone] : \"text-fg\")}>\n {text}\n </div>\n );\n })}\n </div>\n );\n })}\n {(state === \"idle\" || whole) && (\n <div className={cn(\"whitespace-pre\", state !== \"idle\" && \"mt-3\")}>\n <span className=\"text-fg-accent select-none\">{prompt} </span>\n <span className=\"kz-replay-caret\" />\n </div>\n )}\n </div>\n <pre className=\"sr-only\">{transcript}</pre>\n </div>\n </figure>\n );\n}\n\n/** Two sheets, one over the other: copy. Drawn here, like the other glyphs of the primitives (decorative). */\nfunction CopyGlyph() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-3.5 w-3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden focusable=\"false\">\n <rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\" />\n <path d=\"M5 15V6a2 2 0 0 1 2-2h8\" />\n </svg>\n );\n}\n","// The small line glyphs the data and media primitives draw (play, volume, search, chevrons…): decorative, 24-unit\n// box, `currentColor`, sized by the caller's class. Shared by the entries that need them; never exported on its own —\n// a product draws its own icons with @krizaka/icons.\nimport type * as React from \"react\";\n\ntype GlyphProps = { className?: string };\n\nfunction Glyph({ className = \"h-4 w-4\", fill, children }: GlyphProps & { fill?: boolean; children: React.ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n className={className}\n fill={fill ? \"currentColor\" : \"none\"}\n stroke={fill ? \"none\" : \"currentColor\"}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden\n focusable=\"false\"\n >\n {children}\n </svg>\n );\n}\n\nexport const PlayGlyph = (p: GlyphProps) => (\n <Glyph {...p} fill>\n <path d=\"M8 5.5v13a1 1 0 0 0 1.5.86l10.4-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5Z\" />\n </Glyph>\n);\nexport const PauseGlyph = (p: GlyphProps) => (\n <Glyph {...p} fill>\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </Glyph>\n);\nexport const VolumeGlyph = ({ muted, ...p }: GlyphProps & { muted?: boolean }) => (\n <Glyph {...p}>\n <path d=\"M11 5 6 9H3v6h3l5 4V5Z\" />\n {muted ? <path d=\"m22 9-6 6m0-6 6 6\" /> : <path d=\"M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13\" />}\n </Glyph>\n);\nexport const FullscreenGlyph = ({ exit, ...p }: GlyphProps & { exit?: boolean }) => (\n <Glyph {...p}>\n {exit ? <path d=\"M9 4v5H4m11-5v5h5M9 20v-5H4m11 5v-5h5\" /> : <path d=\"M4 9V4h5m6 0h5v5M4 15v5h5m11-5v5h-5\" />}\n </Glyph>\n);\nexport const CaptionsGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"M10 10.5a2 2 0 1 0 0 3M17 10.5a2 2 0 1 0 0 3\" />\n </Glyph>\n);\nexport const PipGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <rect x=\"12\" y=\"12\" width=\"6\" height=\"4\" rx=\"1\" />\n </Glyph>\n);\nexport const SearchGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.5-3.5\" />\n </Glyph>\n);\nexport const CloseGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </Glyph>\n);\nexport const ChevronGlyph = ({ direction = \"down\", ...p }: GlyphProps & { direction?: \"left\" | \"right\" | \"up\" | \"down\" }) => (\n <Glyph {...p}>\n <path d={{ left: \"m15 6-6 6 6 6\", right: \"m9 6 6 6-6 6\", up: \"m6 15 6-6 6 6\", down: \"m6 9 6 6 6-6\" }[direction]} />\n </Glyph>\n);\nexport const CheckGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M20 6 9 17l-5-5\" />\n </Glyph>\n);\nexport const PlusGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M12 5v14M5 12h14\" />\n </Glyph>\n);\nexport const CalendarGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"16\" rx=\"2\" />\n <path d=\"M3 10h18M8 3v4M16 3v4\" />\n </Glyph>\n);\nexport const UploadGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M12 16V4m0 0L7 9m5-5 5 5\" />\n <path d=\"M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\" />\n </Glyph>\n);\nexport const FileGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z\" />\n <path d=\"M14 3v5h5\" />\n </Glyph>\n);\nexport const CameraGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 8h3l2-3h6l2 3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1Z\" />\n <circle cx=\"12\" cy=\"13.5\" r=\"3.5\" />\n </Glyph>\n);\nexport const FlipGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 12a8 8 0 0 1 14-5.3M20 12a8 8 0 0 1-14 5.3\" />\n <path d=\"M18 3v4h-4M6 21v-4h4\" />\n </Glyph>\n);\nexport const RetryGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 12a8 8 0 1 0 2.3-5.7L4 8.5\" />\n <path d=\"M4 4v4.5h4.5\" />\n </Glyph>\n);\nexport const SortGlyph = ({ direction, ...p }: GlyphProps & { direction?: \"ascending\" | \"descending\" }) => (\n <Glyph {...p}>\n {direction === \"ascending\" ? <path d=\"m7 14 5-5 5 5\" /> : direction === \"descending\" ? <path d=\"m7 10 5 5 5-5\" /> : <path d=\"m8 9 4-4 4 4M8 15l4 4 4-4\" />}\n </Glyph>\n);\nexport const ArrowGlyph = ({ direction = \"right\", ...p }: GlyphProps & { direction?: \"left\" | \"right\" }) => (\n <Glyph {...p}>\n <path d={direction === \"left\" ? \"M19 12H5m0 0 6-6m-6 6 6 6\" : \"M5 12h14m0 0-6-6m6 6-6 6\"} />\n </Glyph>\n);\nexport const ExternalGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M14 4h6v6M20 4l-9 9\" />\n <path d=\"M18 14v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4\" />\n </Glyph>\n);\nexport const WarningGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M12 4 2.5 20h19L12 4Z\" />\n <path d=\"M12 10v4M12 17.2v.1\" />\n </Glyph>\n);\nexport const CopyGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\" />\n <path d=\"M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3\" />\n </Glyph>\n);\n"],"mappings":"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","names":["jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/terminal-replay/terminal-replay.tsx"],"sourcesContent":["// Client (a timer, an observer). A terminal session played back: each command is typed at the prompt, its output\n// arrives line by line, the screen follows the last line like a real terminal. What a reader sees without the motion\n// is the session itself: the server renders it whole (no JavaScript, reduced motion and assistive technology all get\n// the full transcript), the screen has a fixed height (no layout shift while it plays), the replay starts when it\n// scrolls into view, waits while it is off screen, and pauses or replays on demand (WCAG 2.2.2). A bounded chain of\n// timeouts, never a permanent animation frame.\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { CheckGlyph, PauseGlyph, PlayGlyph, RetryGlyph } from \"../glyphs\";\n\n/** The colour of an output line — the tokens' text roles, all AA on the screen: secondary, accent, success, warning, danger. */\nexport type TerminalTone = \"muted\" | \"accent\" | \"success\" | \"warning\" | \"danger\";\n\nexport interface TerminalLine {\n /** The line as the program printed it (no colour codes). */\n text: string;\n /** Its colour, when the program coloured it. */\n tone?: TerminalTone;\n}\n\nexport interface TerminalStep {\n /** What is typed at the prompt (`npx orazaka install`). */\n command: string;\n /** What it printed, line by line. */\n output?: readonly (string | TerminalLine)[];\n}\n\nexport type TerminalReplayProps = Omit<React.ComponentProps<\"figure\">, \"children\" | \"title\"> & {\n /** The session, in order. */\n steps: readonly TerminalStep[];\n /** The accessible name of the screen (“Terminal session: install, start, dev”) — passed translated. */\n label: string;\n /** The window's title, shown in its bar (`~/projects — zsh`). */\n title?: React.ReactNode;\n /**\n * What the prompt shows before each command.\n * @default \"$\"\n */\n prompt?: string;\n /** The play button's words (the replay is waiting or paused). */\n playLabel: string;\n /** The pause button's words (the replay is running). */\n pauseLabel: string;\n /** The replay button's words (the session is shown whole). */\n replayLabel: string;\n /** The copy button's words: it copies the commands, one per line. Without it, no copy button. */\n copyLabel?: string;\n /** What the copy button says once the commands are copied (announced politely). */\n copiedLabel?: string;\n /**\n * Milliseconds per typed character.\n * @default 34\n */\n typingSpeed?: number;\n /**\n * Milliseconds between two output lines.\n * @default 70\n */\n lineDelay?: number;\n /**\n * Milliseconds between a command's last line and the next command.\n * @default 900\n */\n stepDelay?: number;\n /**\n * The screen's height, in lines; longer sessions scroll.\n * @default 18\n */\n rows?: number;\n /**\n * Plays the first time it scrolls into view. `false`: the session rests whole until the reader presses play.\n * @default true\n */\n autoPlay?: boolean;\n};\n\ntype Phase = \"final\" | \"idle\" | \"playing\" | \"paused\" | \"done\";\ninterface Position {\n step: number;\n chars: number;\n lines: number;\n}\n\nconst TONE: Record<TerminalTone, string> = {\n muted: \"text-fg-secondary\",\n accent: \"text-fg-accent\",\n success: \"text-fg-success\",\n warning: \"text-fg-warning\",\n danger: \"text-fg-danger\",\n};\n\nconst line = (entry: string | TerminalLine): TerminalLine => (typeof entry === \"string\" ? { text: entry } : entry);\n\n/** The next position of the replay and how long to wait for it; null at the end of the session. */\nfunction advance(steps: readonly TerminalStep[], pos: Position, timing: { typing: number; line: number; step: number }): [Position, number] | null {\n const current = steps[pos.step];\n if (!current) return null;\n const output = current.output ?? [];\n if (pos.chars < current.command.length) {\n // A space is a beat longer: the rhythm of someone typing, not of a printer.\n const pause = current.command[pos.chars] === \" \" ? timing.typing * 2 : timing.typing;\n return [{ ...pos, chars: pos.chars + 1 }, pause];\n }\n if (pos.lines < output.length) return [{ ...pos, lines: pos.lines + 1 }, pos.lines === 0 ? timing.line * 4 : timing.line];\n if (pos.step < steps.length - 1) return [{ step: pos.step + 1, chars: 0, lines: 0 }, timing.step];\n return null;\n}\n\n/** A terminal session played back: commands typed at the prompt, their output line by line — or the whole session at rest. */\nexport function TerminalReplay({\n steps,\n label,\n title,\n prompt = \"$\",\n playLabel,\n pauseLabel,\n replayLabel,\n copyLabel,\n copiedLabel,\n typingSpeed = 34,\n lineDelay = 70,\n stepDelay = 900,\n rows = 18,\n autoPlay = true,\n className,\n style,\n ...props\n}: TerminalReplayProps) {\n const root = React.useRef<HTMLElement>(null);\n const screenRef = React.useRef<HTMLDivElement>(null);\n // The server renders the session whole; the client takes it over unless the reader asked for less motion.\n const [phase, setPhase] = React.useState<Phase>(\"final\");\n const [pos, setPos] = React.useState<Position>({ step: 0, chars: 0, lines: 0 });\n const [visible, setVisible] = React.useState(false);\n const [copied, setCopied] = React.useState(false);\n\n React.useEffect(() => {\n const el = root.current;\n if (!el || !autoPlay || window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n setPhase(\"idle\");\n // Starts the first time it is seen; off screen, it waits where it is.\n const io = new IntersectionObserver(\n ([entry]) => {\n const seen = Boolean(entry?.isIntersecting);\n setVisible(seen);\n if (seen) setPhase((current) => (current === \"idle\" ? \"playing\" : current));\n },\n { threshold: 0.35 },\n );\n io.observe(el);\n return () => io.disconnect();\n }, [autoPlay]);\n\n const ended = phase === \"playing\" && !advance(steps, pos, { typing: typingSpeed, line: lineDelay, step: stepDelay });\n // The end of the session is where the replay stands, not an event: derived, never set from an effect.\n const state: Phase = ended ? \"done\" : phase;\n\n React.useEffect(() => {\n if (phase !== \"playing\" || !visible) return;\n const next = advance(steps, pos, { typing: typingSpeed, line: lineDelay, step: stepDelay });\n if (!next) return;\n const timer = setTimeout(() => setPos(next[0]), next[1]);\n return () => clearTimeout(timer);\n }, [phase, visible, pos, steps, typingSpeed, lineDelay, stepDelay]);\n\n // The screen follows the last line while it plays, like a terminal.\n React.useEffect(() => {\n const el = screenRef.current;\n if (el && state === \"playing\") el.scrollTop = el.scrollHeight;\n }, [pos, state]);\n\n React.useEffect(() => {\n if (!copied) return;\n const timer = setTimeout(() => setCopied(false), 1800);\n return () => clearTimeout(timer);\n }, [copied]);\n\n const whole = state === \"final\" || state === \"done\";\n const shown = whole ? steps.length - 1 : pos.step;\n const toggle = () => {\n if (state === \"playing\") setPhase(\"paused\");\n else if (state === \"paused\" || state === \"idle\") setPhase(\"playing\");\n else {\n // Played on demand — also under reduced motion: the reader asked for it. Off screen it would wait: it is on screen.\n setVisible(true);\n setPos({ step: 0, chars: 0, lines: 0 });\n setPhase(\"playing\");\n }\n };\n const control =\n state === \"playing\"\n ? { text: pauseLabel, Glyph: PauseGlyph }\n : state === \"done\"\n ? { text: replayLabel, Glyph: RetryGlyph }\n : { text: playLabel, Glyph: PlayGlyph };\n const commands = steps.map((s) => s.command).join(\"\\n\");\n const transcript = steps.map((s) => [`${prompt} ${s.command}`, ...(s.output ?? []).map((o) => line(o).text)].join(\"\\n\")).join(\"\\n\\n\");\n\n return (\n <figure\n ref={root}\n data-state={state}\n data-autoplay={autoPlay ? \"\" : undefined}\n className={cn(\"kz-replay overflow-hidden rounded-xl border border-border-default bg-surface-1 shadow-md\", className)}\n style={{ \"--kz-replay-rows\": rows, ...style } as React.CSSProperties}\n {...props}\n >\n <figcaption className=\"flex items-center gap-3 border-b border-border-subtle bg-surface-2 px-3 py-2 text-xs text-fg-secondary\">\n <span aria-hidden className=\"flex shrink-0 gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-border-strong\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-border-strong\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-border-strong\" />\n </span>\n <span className=\"min-w-0 flex-1 truncate font-mono\">{title}</span>\n <button\n type=\"button\"\n onClick={toggle}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 font-medium text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg focus-visible:outline-2 focus-visible:outline-ring\"\n >\n <control.Glyph className=\"h-3.5 w-3.5\" />\n {control.text}\n </button>\n {copyLabel && (\n <button\n type=\"button\"\n onClick={() => navigator.clipboard?.writeText(commands).then(() => setCopied(true))}\n data-copied={copied ? \"\" : undefined}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-md px-2 py-1 font-medium text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg focus-visible:outline-2 focus-visible:outline-ring\"\n >\n {copied ? <CheckGlyph className=\"h-3.5 w-3.5\" /> : <CopyGlyph />}\n <span aria-live=\"polite\">{copied ? (copiedLabel ?? copyLabel) : copyLabel}</span>\n </button>\n )}\n </figcaption>\n <div\n ref={screenRef}\n role=\"region\"\n aria-label={label}\n tabIndex={0}\n className=\"kz-replay-screen relative overflow-auto bg-surface-0 px-4 py-3 font-mono text-[0.8125rem] leading-relaxed text-fg focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring\"\n >\n <div aria-hidden className=\"kz-replay-live\">\n {steps.slice(0, state === \"idle\" ? 0 : shown + 1).map((step, i) => {\n const current = !whole && i === shown;\n const typed = current ? step.command.slice(0, pos.chars) : step.command;\n const output = (step.output ?? []).slice(0, current ? pos.lines : undefined);\n const typing = current && pos.chars < step.command.length;\n return (\n <div key={i} className={i > 0 ? \"mt-3\" : undefined}>\n <div className=\"whitespace-pre\">\n <span className=\"text-fg-accent select-none\">{prompt} </span>\n <span className=\"font-medium\">{typed}</span>\n {typing && <span className=\"kz-replay-caret\" />}\n </div>\n {output.map((entry, j) => {\n const { text, tone } = line(entry);\n return (\n <div key={j} className={cn(\"min-h-[1lh] whitespace-pre\", tone ? TONE[tone] : \"text-fg\")}>\n {text}\n </div>\n );\n })}\n </div>\n );\n })}\n {(state === \"idle\" || whole) && (\n <div className={cn(\"whitespace-pre\", state !== \"idle\" && \"mt-3\")}>\n <span className=\"text-fg-accent select-none\">{prompt} </span>\n <span className=\"kz-replay-caret\" />\n </div>\n )}\n </div>\n <pre className=\"sr-only\">{transcript}</pre>\n </div>\n </figure>\n );\n}\n\n/** Two sheets, one over the other: copy. Drawn here, like the other glyphs of the primitives (decorative). */\nfunction CopyGlyph() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-3.5 w-3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden focusable=\"false\">\n <rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\" />\n <path d=\"M5 15V6a2 2 0 0 1 2-2h8\" />\n </svg>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/toast.js
CHANGED
package/dist/tooltip.js
CHANGED
package/dist/video-player.js
CHANGED
package/dist/video-player.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/video-player/video-player.tsx"],"sourcesContent":["// Client. A `<video>` with its own controls, accessible from end to end: a named region the keyboard reaches (Space /\n// K, arrows, J / L, M, F, C, Home / End, 0–9), every button named by a prop, the seek and volume sliders of\n// `@krizaka/ui/slider` with the time read aloud. HLS plays natively where the browser can (Safari, iOS) and through\n// hls.js elsewhere — loaded only then, by the app's `loadHls` (`() => import(\"hls.js\")`): an optional peer, never in\n// the bundle of a page that does not play HLS. A signed address can be resolved at the last moment (`src` as a\n// function), again after an error; the video pauses when it leaves the screen; autoplay waits under reduced motion.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { CaptionsGlyph, FullscreenGlyph, PauseGlyph, PipGlyph, PlayGlyph, RetryGlyph, VolumeGlyph } from \"../glyphs\";\nimport { clamp, formatClock, playSafely, useInView, usePrefersReducedMotion } from \"../media\";\nimport { Slider } from \"../slider\";\n\nexport const videoPlayer = tv({\n slots: {\n root:\n \"theme-dark group/player relative isolate w-full overflow-hidden rounded-xl bg-media text-fg-on-media \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-[idle]:cursor-none\",\n video: \"absolute inset-0 h-full w-full object-contain\",\n poster: \"pointer-events-none absolute inset-0 h-full w-full object-contain\",\n big:\n \"absolute left-1/2 top-1/2 z-10 inline-flex h-16 w-16 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full \" +\n \"bg-scrim-strong text-fg-on-media shadow-lg backdrop-blur-sm transition-transform hover:scale-105 motion-reduce:transition-none motion-reduce:hover:scale-100 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n bar:\n \"absolute inset-x-0 bottom-0 z-20 flex flex-col gap-1 bg-linear-to-t from-scrim-strong via-scrim to-transparent px-3 pb-2 pt-10 \" +\n \"transition-opacity duration-300 motion-reduce:transition-none group-data-[idle]/player:opacity-0 group-focus-within/player:opacity-100\",\n row: \"flex items-center gap-1\",\n button:\n \"inline-flex h-9 min-w-9 shrink-0 items-center justify-center gap-1 rounded-md px-1.5 text-xs font-semibold text-fg-on-media \" +\n \"transition-colors hover:bg-fg-on-media/15 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-pressed:bg-fg-on-media/20\",\n time: \"px-1.5 font-mono text-[11px] tabular-nums text-fg-on-media\",\n volume: \"hidden w-20 items-center sm:flex\",\n status: \"absolute inset-0 z-30 flex flex-col items-center justify-center gap-3 bg-scrim-strong p-6 text-center text-sm\",\n spinner: \"h-10 w-10 animate-spin rounded-full border-2 border-fg-on-media/30 border-t-fg-on-media motion-reduce:animate-none\",\n overlay: \"absolute inset-0 z-30\",\n },\n});\n\n/** What `loadHls` resolves to: the module of hls.js (only the members the player uses are typed). */\nexport type HlsModule = {\n default: {\n new (config?: Record<string, unknown>): {\n loadSource(url: string): void;\n attachMedia(media: HTMLMediaElement): void;\n destroy(): void;\n startLoad(): void;\n recoverMediaError(): void;\n on(event: string, listener: (event: string, data: { fatal?: boolean; type?: string }) => void): void;\n };\n isSupported(): boolean;\n Events: { ERROR: string };\n ErrorTypes: { NETWORK_ERROR: string; MEDIA_ERROR: string };\n };\n};\n\nexport type VideoTrack = {\n /** The WebVTT file. */\n src: string;\n /** Its language, BCP 47 (`\"fr\"`). */\n srcLang: string;\n /** Its name in the menu (\"Français\"), passed translated. */\n label: string;\n /** `subtitles` (default) or `captions` (with the sounds, for deaf viewers). */\n kind?: \"subtitles\" | \"captions\";\n /** Shown from the start. */\n default?: boolean;\n};\n\nexport type VideoPlayerLabels = {\n play: string;\n pause: string;\n mute: string;\n unmute: string;\n volume: string;\n seek: string;\n /** The speed button: `(rate) => \"Speed \" + rate + \"×\"`. */\n speed: (rate: number) => string;\n /** The captions button: `(track) => \"Subtitles: \" + (track ?? \"off\")`, the track's label or `null` when off. */\n captions: (track: string | null) => string;\n fullscreen: string;\n exitFullscreen: string;\n pictureInPicture: string;\n /** Read while the video waits for data. */\n loading: string;\n /** The video cannot play. */\n error: string;\n /** Tries again (resolves a signed `src` again). */\n retry: string;\n /** The seek slider's value: `(time, duration) => \"1:05 of 3:20\"`. */\n time: (time: string, duration: string) => string;\n};\n\nexport const videoPlayerLabels: VideoPlayerLabels = {\n play: \"Play\",\n pause: \"Pause\",\n mute: \"Mute\",\n unmute: \"Unmute\",\n volume: \"Volume\",\n seek: \"Seek\",\n speed: (rate) => `Speed ${rate}×`,\n captions: (track) => `Subtitles: ${track ?? \"off\"}`,\n fullscreen: \"Full screen\",\n exitFullscreen: \"Exit full screen\",\n pictureInPicture: \"Picture in picture\",\n loading: \"Loading\",\n error: \"This video cannot be played.\",\n retry: \"Try again\",\n time: (time, duration) => `${time} of ${duration}`,\n};\n\nexport type VideoPlayerProps = {\n /** The address of the video (MP4, WebM, or an HLS `.m3u8`), or a function that resolves a signed one when needed. */\n src: string | (() => Promise<string>);\n /** `auto` (default): HLS when the address ends in `.m3u8`. */\n type?: \"auto\" | \"hls\" | \"file\";\n /** Loads hls.js when the browser cannot play HLS itself: `() => import(\"hls.js\")`. Without it, HLS needs Safari. */\n loadHls?: () => Promise<HlsModule>;\n /** The accessible name of the player (\"Video: Sunset over Montréal\"), passed translated. */\n title: string;\n /** The picture shown before playback. */\n poster?: string;\n /** Starts playing, muted (browsers refuse sound without a gesture) — not under reduced motion. */\n autoPlay?: boolean;\n /** Starts muted. */\n muted?: boolean;\n /** Plays again from the start at the end. */\n loop?: boolean;\n /** How much to fetch before playback: `none` (a list of many videos), `metadata` (default), `auto`. */\n preload?: \"none\" | \"metadata\" | \"auto\";\n /** Subtitles or captions (WebVTT): the C key and a button cycle through them. */\n tracks?: readonly VideoTrack[];\n /** The speeds offered by the speed button. Default `[0.5, 1, 1.25, 1.5, 2]`. */\n playbackRates?: readonly number[];\n /** Pauses when less than a quarter of the player is on screen. Default `true`. */\n pauseWhenHidden?: boolean;\n /** Shows the controls (default). `false`: a bare video (a background loop) — give it no sound. */\n controls?: boolean;\n /** Covers the video and hides the controls: a gate (sign in, unlock, age check) drawn by the app. */\n overlay?: React.ReactNode;\n /** The width / height ratio of the frame. Default `\"16 / 9\"`; `\"9 / 16\"` for a vertical video. */\n aspectRatio?: string;\n /** Called when playback starts. */\n onPlay?: () => void;\n /** Called when playback pauses. */\n onPause?: () => void;\n /** Called at the end. */\n onEnded?: () => void;\n /** Called as the time moves, in seconds (a few times a second). */\n onTimeUpdate?: (time: number) => void;\n /** Called when the video cannot play (a network failure, an expired address). */\n onError?: (error: unknown) => void;\n /** The words, passed translated; English by default. */\n labels?: Partial<VideoPlayerLabels>;\n /** Classes merged on the root. */\n className?: string;\n};\n\nconst RATES = [0.5, 1, 1.25, 1.5, 2] as const;\nconst isHls = (url: string, type: VideoPlayerProps[\"type\"]) => type === \"hls\" || (type !== \"file\" && /\\.m3u8(\\?|#|$)/i.test(url));\n\n/** Shows the text track `index` of a video (and hides the others); -1 hides them all. */\nfunction showTrack(element: HTMLVideoElement, index: number) {\n const list = element.textTracks;\n for (let i = 0; i < list.length; i += 1) list[i]!.mode = i === index ? \"showing\" : \"disabled\";\n}\n\n/** A video player with accessible controls, HLS on demand, signed sources, subtitles, speed, full screen. */\nexport function VideoPlayer({\n src,\n type = \"auto\",\n loadHls,\n title,\n poster,\n autoPlay,\n muted: initiallyMuted = false,\n loop,\n preload = \"metadata\",\n tracks = [],\n playbackRates = RATES,\n pauseWhenHidden = true,\n controls = true,\n overlay,\n aspectRatio = \"16 / 9\",\n onPlay,\n onPause,\n onEnded,\n onTimeUpdate,\n onError,\n labels: given,\n className,\n}: VideoPlayerProps) {\n const labels = { ...videoPlayerLabels, ...given };\n const s = videoPlayer();\n const root = React.useRef<HTMLDivElement>(null);\n const video = React.useRef<HTMLVideoElement>(null);\n const reduce = usePrefersReducedMotion();\n const [resolved, setResolved] = React.useState<{ attempt: number; url: string } | null>(null);\n const [attempt, setAttempt] = React.useState(0);\n const [wanted, setWanted] = React.useState(false);\n const [playing, setPlaying] = React.useState(false);\n const [time, setTime] = React.useState(0);\n const [duration, setDuration] = React.useState(0);\n const [volume, setVolume] = React.useState(1);\n const [muted, setMuted] = React.useState(initiallyMuted || Boolean(autoPlay));\n const [rate, setRate] = React.useState(1);\n const [caption, setCaption] = React.useState(() => tracks.findIndex((t) => t.default));\n const [fullscreen, setFullscreen] = React.useState(false);\n const [waiting, setWaiting] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const [idle, setIdle] = React.useState(false);\n // The poster is an image over the video until the first play: it loads like any image (lazy, decoded once), the\n // same in every browser — the `poster` attribute is painted late and differently.\n const [started, setStarted] = React.useState(false);\n const idleTimer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const url = typeof src === \"string\" ? src : resolved?.attempt === attempt ? resolved.url : null;\n const needSource = typeof src === \"function\" && (preload !== \"none\" || wanted || (autoPlay && !reduce));\n\n // A signed source is resolved when it is needed, and again on \"Try again\".\n React.useEffect(() => {\n if (typeof src !== \"function\" || !needSource) return;\n let live = true;\n src().then(\n (next) => live && setResolved({ attempt, url: next }),\n (error: unknown) => {\n if (!live) return;\n setFailed(true);\n onError?.(error);\n },\n );\n return () => {\n live = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `onError` is a callback, not a reason to resolve again\n }, [src, needSource, attempt]);\n\n // Attaches the source: natively, or through hls.js when the browser needs it.\n React.useEffect(() => {\n const element = video.current;\n if (!element || !url) return;\n let hls: InstanceType<HlsModule[\"default\"]> | null = null;\n let live = true;\n const fail = (error: unknown) => {\n if (!live) return;\n setFailed(true);\n onError?.(error);\n };\n if (isHls(url, type) && !element.canPlayType(\"application/vnd.apple.mpegurl\")) {\n if (!loadHls) fail(new Error(\"VideoPlayer: HLS needs `loadHls` in this browser.\"));\n else\n loadHls().then(({ default: Hls }) => {\n if (!live) return;\n if (!Hls.isSupported()) return fail(new Error(\"VideoPlayer: HLS is not supported here.\"));\n hls = new Hls({ enableWorker: true, backBufferLength: 60 });\n hls.on(Hls.Events.ERROR, (_event, data) => {\n if (!data.fatal) return;\n if (data.type === Hls.ErrorTypes.NETWORK_ERROR) hls?.startLoad();\n else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) hls?.recoverMediaError();\n else fail(data);\n });\n hls.loadSource(url);\n hls.attachMedia(element);\n }, fail);\n } else element.src = url;\n return () => {\n live = false;\n hls?.destroy();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `onError` and `loadHls` are callbacks\n }, [url, type]);\n\n // Plays once the source is there, when it was asked for (a click before a signed source resolved, an autoplay).\n React.useEffect(() => {\n const element = video.current;\n if (!element || !url || !(wanted || (autoPlay && !reduce))) return;\n playSafely(element);\n }, [url, wanted, autoPlay, reduce]);\n\n React.useEffect(() => {\n if (video.current) showTrack(video.current, caption);\n }, [caption, tracks.length]);\n\n React.useEffect(() => {\n const change = () => setFullscreen(document.fullscreenElement === root.current);\n document.addEventListener(\"fullscreenchange\", change);\n return () => document.removeEventListener(\"fullscreenchange\", change);\n }, []);\n React.useEffect(() => () => clearTimeout(idleTimer.current), []);\n\n useInView(root, (visible) => !visible && video.current && !video.current.paused && video.current.pause(), pauseWhenHidden);\n\n const play = () => {\n setWanted(true);\n setFailed(false);\n playSafely(video.current);\n };\n const toggle = () => (playing ? video.current?.pause() : play());\n const seek = (to: number) => {\n if (!video.current) return;\n video.current.currentTime = clamp(to, 0, duration || video.current.duration || 0);\n setTime(video.current.currentTime);\n };\n const changeVolume = (to: number) => {\n if (!video.current) return;\n const next = clamp(to, 0, 1);\n video.current.volume = next;\n video.current.muted = next === 0;\n setVolume(next);\n setMuted(next === 0);\n };\n const toggleMute = () => {\n if (!video.current) return;\n if (muted && volume === 0) changeVolume(0.5);\n video.current.muted = !muted;\n setMuted(!muted);\n };\n const cycleRate = () => {\n const next = playbackRates[(playbackRates.indexOf(rate) + 1) % playbackRates.length] ?? 1;\n if (video.current) video.current.playbackRate = next;\n setRate(next);\n };\n const cycleCaption = () => tracks.length > 0 && setCaption((c) => (c + 2) % (tracks.length + 1) - 1);\n const toggleFullscreen = () => {\n const element = root.current;\n if (!element) return;\n if (document.fullscreenElement) void document.exitFullscreen();\n else if (element.requestFullscreen) void element.requestFullscreen();\n else (video.current as HTMLVideoElement & { webkitEnterFullscreen?: () => void })?.webkitEnterFullscreen?.();\n };\n const pip = () => {\n if (document.pictureInPictureElement) void document.exitPictureInPicture();\n else void video.current?.requestPictureInPicture?.();\n };\n const wake = () => {\n setIdle(false);\n clearTimeout(idleTimer.current);\n if (playing) idleTimer.current = setTimeout(() => setIdle(true), 2500);\n };\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n const target = event.target as HTMLElement;\n const inControl = target.getAttribute(\"role\") === \"slider\";\n const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;\n const now = video.current?.currentTime ?? 0;\n const actions: Record<string, (() => void) | undefined> = {\n \" \": target.tagName === \"BUTTON\" ? undefined : toggle,\n k: toggle,\n j: () => seek(now - 10),\n l: () => seek(now + 10),\n m: toggleMute,\n f: toggleFullscreen,\n c: tracks.length ? cycleCaption : undefined,\n ArrowLeft: inControl ? undefined : () => seek(now - 5),\n ArrowRight: inControl ? undefined : () => seek(now + 5),\n ArrowUp: inControl ? undefined : () => changeVolume(volume + 0.1),\n ArrowDown: inControl ? undefined : () => changeVolume(volume - 0.1),\n Home: inControl ? undefined : () => seek(0),\n End: inControl ? undefined : () => seek(duration),\n };\n const action = /^[0-9]$/.test(key) && !inControl ? () => seek((Number(key) / 10) * duration) : actions[key];\n if (!action || event.metaKey || event.ctrlKey || event.altKey) return;\n event.preventDefault();\n action();\n wake();\n };\n\n const showControls = controls && !overlay;\n const pipSupported = typeof document !== \"undefined\" && \"pictureInPictureEnabled\" in document && document.pictureInPictureEnabled;\n\n return (\n <div\n ref={root}\n role=\"region\"\n aria-label={title}\n tabIndex={showControls ? 0 : undefined}\n data-idle={idle && playing ? \"\" : undefined}\n data-state={playing ? \"playing\" : \"paused\"}\n className={s.root({ className })}\n style={{ aspectRatio }}\n onKeyDown={showControls ? onKeyDown : undefined}\n onPointerMove={showControls ? wake : undefined}\n onPointerLeave={() => playing && setIdle(true)}\n >\n <video\n ref={video}\n className={s.video()}\n preload={preload}\n loop={loop}\n muted={muted}\n playsInline\n crossOrigin={tracks.length ? \"anonymous\" : undefined}\n onClick={showControls ? toggle : undefined}\n onPlay={() => {\n setStarted(true);\n setPlaying(true);\n wake();\n onPlay?.();\n }}\n onPause={() => {\n setPlaying(false);\n setIdle(false);\n onPause?.();\n }}\n onEnded={() => onEnded?.()}\n onTimeUpdate={(event) => {\n setTime(event.currentTarget.currentTime);\n onTimeUpdate?.(event.currentTarget.currentTime);\n }}\n onDurationChange={(event) => setDuration(Number.isFinite(event.currentTarget.duration) ? event.currentTarget.duration : 0)}\n onWaiting={() => setWaiting(true)}\n onPlaying={() => setWaiting(false)}\n onCanPlay={() => setWaiting(false)}\n onError={(event) => {\n setFailed(true);\n onError?.(event.currentTarget.error);\n }}\n >\n {tracks.map((track) => (\n <track key={track.src} src={track.src} srcLang={track.srcLang} label={track.label} kind={track.kind ?? \"subtitles\"} default={track.default} />\n ))}\n </video>\n\n {poster && !started ? <img src={poster} alt=\"\" aria-hidden decoding=\"async\" className={s.poster()} /> : null}\n\n {showControls && !playing && !failed && !waiting && (\n <button type=\"button\" className={s.big()} aria-label={labels.play} onClick={play}>\n <PlayGlyph className=\"ml-1 h-7 w-7\" />\n </button>\n )}\n\n {waiting && !failed && (\n <div className=\"pointer-events-none absolute inset-0 z-10 flex items-center justify-center\">\n <span className={s.spinner()} aria-hidden />\n <span role=\"status\" className=\"sr-only\">\n {labels.loading}\n </span>\n </div>\n )}\n\n {failed && (\n <div role=\"alert\" className={s.status()}>\n <p>{labels.error}</p>\n <button\n type=\"button\"\n className={s.button({ className: \"border border-fg-on-media/40 px-3\" })}\n onClick={() => {\n setFailed(false);\n setAttempt((a) => a + 1);\n video.current?.load();\n }}\n >\n <RetryGlyph className=\"h-4 w-4\" />\n {labels.retry}\n </button>\n </div>\n )}\n\n {overlay ? <div className={s.overlay()}>{overlay}</div> : null}\n\n {showControls && (\n <div className={s.bar()}>\n <Slider\n label={labels.seek}\n min={0}\n max={duration || 1}\n step={0.1}\n value={Math.min(time, duration || 1)}\n onValueChange={seek}\n formatValue={(v) => labels.time(formatClock(v), formatClock(duration))}\n disabled={!duration}\n className=\"[&_[role=slider]]:h-4 [&_[role=slider]]:w-4\"\n />\n <div className={s.row()}>\n <button type=\"button\" className={s.button()} aria-label={playing ? labels.pause : labels.play} onClick={toggle}>\n {playing ? <PauseGlyph className=\"h-5 w-5\" /> : <PlayGlyph className=\"h-5 w-5\" />}\n </button>\n <button type=\"button\" className={s.button()} aria-label={muted ? labels.unmute : labels.mute} onClick={toggleMute}>\n <VolumeGlyph muted={muted} className=\"h-5 w-5\" />\n </button>\n <div className={s.volume()}>\n <Slider label={labels.volume} min={0} max={1} step={0.05} value={muted ? 0 : volume} onValueChange={changeVolume} formatValue={(v) => `${Math.round(v * 100)} %`} className=\"w-full\" />\n </div>\n <span className={s.time()} aria-hidden>\n {formatClock(time)} / {formatClock(duration)}\n </span>\n <span className=\"flex-1\" />\n {playbackRates.length > 1 && (\n <button type=\"button\" className={s.button()} aria-label={labels.speed(rate)} onClick={cycleRate}>\n {rate}×\n </button>\n )}\n {tracks.length > 0 && (\n <button type=\"button\" className={s.button()} aria-pressed={caption >= 0} aria-label={labels.captions(caption >= 0 ? (tracks[caption]?.label ?? null) : null)} onClick={cycleCaption}>\n <CaptionsGlyph className=\"h-5 w-5\" />\n </button>\n )}\n {pipSupported && (\n <button type=\"button\" className={s.button()} aria-label={labels.pictureInPicture} onClick={pip}>\n <PipGlyph className=\"h-5 w-5\" />\n </button>\n )}\n <button type=\"button\" className={s.button()} aria-label={fullscreen ? labels.exitFullscreen : labels.fullscreen} onClick={toggleFullscreen}>\n <FullscreenGlyph exit={fullscreen} className=\"h-5 w-5\" />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/video-player/video-player.tsx"],"sourcesContent":["// Client. A `<video>` with its own controls, accessible from end to end: a named region the keyboard reaches (Space /\n// K, arrows, J / L, M, F, C, Home / End, 0–9), every button named by a prop, the seek and volume sliders of\n// `@krizaka/ui/slider` with the time read aloud. HLS plays natively where the browser can (Safari, iOS) and through\n// hls.js elsewhere — loaded only then, by the app's `loadHls` (`() => import(\"hls.js\")`): an optional peer, never in\n// the bundle of a page that does not play HLS. A signed address can be resolved at the last moment (`src` as a\n// function), again after an error; the video pauses when it leaves the screen; autoplay waits under reduced motion.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { CaptionsGlyph, FullscreenGlyph, PauseGlyph, PipGlyph, PlayGlyph, RetryGlyph, VolumeGlyph } from \"../glyphs\";\nimport { clamp, formatClock, playSafely, useInView, usePrefersReducedMotion } from \"../media\";\nimport { Slider } from \"../slider\";\n\nexport const videoPlayer = tv({\n slots: {\n root:\n \"theme-dark group/player relative isolate w-full overflow-hidden rounded-xl bg-media text-fg-on-media \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-[idle]:cursor-none\",\n video: \"absolute inset-0 h-full w-full object-contain\",\n poster: \"pointer-events-none absolute inset-0 h-full w-full object-contain\",\n big:\n \"absolute left-1/2 top-1/2 z-10 inline-flex h-16 w-16 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full \" +\n \"bg-scrim-strong text-fg-on-media shadow-lg backdrop-blur-sm transition-transform hover:scale-105 motion-reduce:transition-none motion-reduce:hover:scale-100 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n bar:\n \"absolute inset-x-0 bottom-0 z-20 flex flex-col gap-1 bg-linear-to-t from-scrim-strong via-scrim to-transparent px-3 pb-2 pt-10 \" +\n \"transition-opacity duration-300 motion-reduce:transition-none group-data-[idle]/player:opacity-0 group-focus-within/player:opacity-100\",\n row: \"flex items-center gap-1\",\n button:\n \"inline-flex h-9 min-w-9 shrink-0 items-center justify-center gap-1 rounded-md px-1.5 text-xs font-semibold text-fg-on-media \" +\n \"transition-colors hover:bg-fg-on-media/15 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-pressed:bg-fg-on-media/20\",\n time: \"px-1.5 font-mono text-[11px] tabular-nums text-fg-on-media\",\n volume: \"hidden w-20 items-center sm:flex\",\n status: \"absolute inset-0 z-30 flex flex-col items-center justify-center gap-3 bg-scrim-strong p-6 text-center text-sm\",\n spinner: \"h-10 w-10 animate-spin rounded-full border-2 border-fg-on-media/30 border-t-fg-on-media motion-reduce:animate-none\",\n overlay: \"absolute inset-0 z-30\",\n },\n});\n\n/** What `loadHls` resolves to: the module of hls.js (only the members the player uses are typed). */\nexport type HlsModule = {\n default: {\n new (config?: Record<string, unknown>): {\n loadSource(url: string): void;\n attachMedia(media: HTMLMediaElement): void;\n destroy(): void;\n startLoad(): void;\n recoverMediaError(): void;\n on(event: string, listener: (event: string, data: { fatal?: boolean; type?: string }) => void): void;\n };\n isSupported(): boolean;\n Events: { ERROR: string };\n ErrorTypes: { NETWORK_ERROR: string; MEDIA_ERROR: string };\n };\n};\n\nexport type VideoTrack = {\n /** The WebVTT file. */\n src: string;\n /** Its language, BCP 47 (`\"fr\"`). */\n srcLang: string;\n /** Its name in the menu (\"Français\"), passed translated. */\n label: string;\n /** `subtitles` (default) or `captions` (with the sounds, for deaf viewers). */\n kind?: \"subtitles\" | \"captions\";\n /** Shown from the start. */\n default?: boolean;\n};\n\nexport type VideoPlayerLabels = {\n play: string;\n pause: string;\n mute: string;\n unmute: string;\n volume: string;\n seek: string;\n /** The speed button: `(rate) => \"Speed \" + rate + \"×\"`. */\n speed: (rate: number) => string;\n /** The captions button: `(track) => \"Subtitles: \" + (track ?? \"off\")`, the track's label or `null` when off. */\n captions: (track: string | null) => string;\n fullscreen: string;\n exitFullscreen: string;\n pictureInPicture: string;\n /** Read while the video waits for data. */\n loading: string;\n /** The video cannot play. */\n error: string;\n /** Tries again (resolves a signed `src` again). */\n retry: string;\n /** The seek slider's value: `(time, duration) => \"1:05 of 3:20\"`. */\n time: (time: string, duration: string) => string;\n};\n\nexport const videoPlayerLabels: VideoPlayerLabels = {\n play: \"Play\",\n pause: \"Pause\",\n mute: \"Mute\",\n unmute: \"Unmute\",\n volume: \"Volume\",\n seek: \"Seek\",\n speed: (rate) => `Speed ${rate}×`,\n captions: (track) => `Subtitles: ${track ?? \"off\"}`,\n fullscreen: \"Full screen\",\n exitFullscreen: \"Exit full screen\",\n pictureInPicture: \"Picture in picture\",\n loading: \"Loading\",\n error: \"This video cannot be played.\",\n retry: \"Try again\",\n time: (time, duration) => `${time} of ${duration}`,\n};\n\nexport type VideoPlayerProps = {\n /** The address of the video (MP4, WebM, or an HLS `.m3u8`), or a function that resolves a signed one when needed. */\n src: string | (() => Promise<string>);\n /** `auto` (default): HLS when the address ends in `.m3u8`. */\n type?: \"auto\" | \"hls\" | \"file\";\n /** Loads hls.js when the browser cannot play HLS itself: `() => import(\"hls.js\")`. Without it, HLS needs Safari. */\n loadHls?: () => Promise<HlsModule>;\n /** The accessible name of the player (\"Video: Sunset over Montréal\"), passed translated. */\n title: string;\n /** The picture shown before playback. */\n poster?: string;\n /** Starts playing, muted (browsers refuse sound without a gesture) — not under reduced motion. */\n autoPlay?: boolean;\n /** Starts muted. */\n muted?: boolean;\n /** Plays again from the start at the end. */\n loop?: boolean;\n /** How much to fetch before playback: `none` (a list of many videos), `metadata` (default), `auto`. */\n preload?: \"none\" | \"metadata\" | \"auto\";\n /** Subtitles or captions (WebVTT): the C key and a button cycle through them. */\n tracks?: readonly VideoTrack[];\n /** The speeds offered by the speed button. Default `[0.5, 1, 1.25, 1.5, 2]`. */\n playbackRates?: readonly number[];\n /** Pauses when less than a quarter of the player is on screen. Default `true`. */\n pauseWhenHidden?: boolean;\n /** Shows the controls (default). `false`: a bare video (a background loop) — give it no sound. */\n controls?: boolean;\n /** Covers the video and hides the controls: a gate (sign in, unlock, age check) drawn by the app. */\n overlay?: React.ReactNode;\n /** The width / height ratio of the frame. Default `\"16 / 9\"`; `\"9 / 16\"` for a vertical video. */\n aspectRatio?: string;\n /** Called when playback starts. */\n onPlay?: () => void;\n /** Called when playback pauses. */\n onPause?: () => void;\n /** Called at the end. */\n onEnded?: () => void;\n /** Called as the time moves, in seconds (a few times a second). */\n onTimeUpdate?: (time: number) => void;\n /** Called when the video cannot play (a network failure, an expired address). */\n onError?: (error: unknown) => void;\n /** The words, passed translated; English by default. */\n labels?: Partial<VideoPlayerLabels>;\n /** Classes merged on the root. */\n className?: string;\n};\n\nconst RATES = [0.5, 1, 1.25, 1.5, 2] as const;\nconst isHls = (url: string, type: VideoPlayerProps[\"type\"]) => type === \"hls\" || (type !== \"file\" && /\\.m3u8(\\?|#|$)/i.test(url));\n\n/** Shows the text track `index` of a video (and hides the others); -1 hides them all. */\nfunction showTrack(element: HTMLVideoElement, index: number) {\n const list = element.textTracks;\n for (let i = 0; i < list.length; i += 1) list[i]!.mode = i === index ? \"showing\" : \"disabled\";\n}\n\n/** A video player with accessible controls, HLS on demand, signed sources, subtitles, speed, full screen. */\nexport function VideoPlayer({\n src,\n type = \"auto\",\n loadHls,\n title,\n poster,\n autoPlay,\n muted: initiallyMuted = false,\n loop,\n preload = \"metadata\",\n tracks = [],\n playbackRates = RATES,\n pauseWhenHidden = true,\n controls = true,\n overlay,\n aspectRatio = \"16 / 9\",\n onPlay,\n onPause,\n onEnded,\n onTimeUpdate,\n onError,\n labels: given,\n className,\n}: VideoPlayerProps) {\n const labels = { ...videoPlayerLabels, ...given };\n const s = videoPlayer();\n const root = React.useRef<HTMLDivElement>(null);\n const video = React.useRef<HTMLVideoElement>(null);\n const reduce = usePrefersReducedMotion();\n const [resolved, setResolved] = React.useState<{ attempt: number; url: string } | null>(null);\n const [attempt, setAttempt] = React.useState(0);\n const [wanted, setWanted] = React.useState(false);\n const [playing, setPlaying] = React.useState(false);\n const [time, setTime] = React.useState(0);\n const [duration, setDuration] = React.useState(0);\n const [volume, setVolume] = React.useState(1);\n const [muted, setMuted] = React.useState(initiallyMuted || Boolean(autoPlay));\n const [rate, setRate] = React.useState(1);\n const [caption, setCaption] = React.useState(() => tracks.findIndex((t) => t.default));\n const [fullscreen, setFullscreen] = React.useState(false);\n const [waiting, setWaiting] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const [idle, setIdle] = React.useState(false);\n // The poster is an image over the video until the first play: it loads like any image (lazy, decoded once), the\n // same in every browser — the `poster` attribute is painted late and differently.\n const [started, setStarted] = React.useState(false);\n const idleTimer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const url = typeof src === \"string\" ? src : resolved?.attempt === attempt ? resolved.url : null;\n const needSource = typeof src === \"function\" && (preload !== \"none\" || wanted || (autoPlay && !reduce));\n\n // A signed source is resolved when it is needed, and again on \"Try again\".\n React.useEffect(() => {\n if (typeof src !== \"function\" || !needSource) return;\n let live = true;\n src().then(\n (next) => live && setResolved({ attempt, url: next }),\n (error: unknown) => {\n if (!live) return;\n setFailed(true);\n onError?.(error);\n },\n );\n return () => {\n live = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `onError` is a callback, not a reason to resolve again\n }, [src, needSource, attempt]);\n\n // Attaches the source: natively, or through hls.js when the browser needs it.\n React.useEffect(() => {\n const element = video.current;\n if (!element || !url) return;\n let hls: InstanceType<HlsModule[\"default\"]> | null = null;\n let live = true;\n const fail = (error: unknown) => {\n if (!live) return;\n setFailed(true);\n onError?.(error);\n };\n if (isHls(url, type) && !element.canPlayType(\"application/vnd.apple.mpegurl\")) {\n if (!loadHls) fail(new Error(\"VideoPlayer: HLS needs `loadHls` in this browser.\"));\n else\n loadHls().then(({ default: Hls }) => {\n if (!live) return;\n if (!Hls.isSupported()) return fail(new Error(\"VideoPlayer: HLS is not supported here.\"));\n hls = new Hls({ enableWorker: true, backBufferLength: 60 });\n hls.on(Hls.Events.ERROR, (_event, data) => {\n if (!data.fatal) return;\n if (data.type === Hls.ErrorTypes.NETWORK_ERROR) hls?.startLoad();\n else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) hls?.recoverMediaError();\n else fail(data);\n });\n hls.loadSource(url);\n hls.attachMedia(element);\n }, fail);\n } else element.src = url;\n return () => {\n live = false;\n hls?.destroy();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `onError` and `loadHls` are callbacks\n }, [url, type]);\n\n // Plays once the source is there, when it was asked for (a click before a signed source resolved, an autoplay).\n React.useEffect(() => {\n const element = video.current;\n if (!element || !url || !(wanted || (autoPlay && !reduce))) return;\n playSafely(element);\n }, [url, wanted, autoPlay, reduce]);\n\n React.useEffect(() => {\n if (video.current) showTrack(video.current, caption);\n }, [caption, tracks.length]);\n\n React.useEffect(() => {\n const change = () => setFullscreen(document.fullscreenElement === root.current);\n document.addEventListener(\"fullscreenchange\", change);\n return () => document.removeEventListener(\"fullscreenchange\", change);\n }, []);\n React.useEffect(() => () => clearTimeout(idleTimer.current), []);\n\n useInView(root, (visible) => !visible && video.current && !video.current.paused && video.current.pause(), pauseWhenHidden);\n\n const play = () => {\n setWanted(true);\n setFailed(false);\n playSafely(video.current);\n };\n const toggle = () => (playing ? video.current?.pause() : play());\n const seek = (to: number) => {\n if (!video.current) return;\n video.current.currentTime = clamp(to, 0, duration || video.current.duration || 0);\n setTime(video.current.currentTime);\n };\n const changeVolume = (to: number) => {\n if (!video.current) return;\n const next = clamp(to, 0, 1);\n video.current.volume = next;\n video.current.muted = next === 0;\n setVolume(next);\n setMuted(next === 0);\n };\n const toggleMute = () => {\n if (!video.current) return;\n if (muted && volume === 0) changeVolume(0.5);\n video.current.muted = !muted;\n setMuted(!muted);\n };\n const cycleRate = () => {\n const next = playbackRates[(playbackRates.indexOf(rate) + 1) % playbackRates.length] ?? 1;\n if (video.current) video.current.playbackRate = next;\n setRate(next);\n };\n const cycleCaption = () => tracks.length > 0 && setCaption((c) => (c + 2) % (tracks.length + 1) - 1);\n const toggleFullscreen = () => {\n const element = root.current;\n if (!element) return;\n if (document.fullscreenElement) void document.exitFullscreen();\n else if (element.requestFullscreen) void element.requestFullscreen();\n else (video.current as HTMLVideoElement & { webkitEnterFullscreen?: () => void })?.webkitEnterFullscreen?.();\n };\n const pip = () => {\n if (document.pictureInPictureElement) void document.exitPictureInPicture();\n else void video.current?.requestPictureInPicture?.();\n };\n const wake = () => {\n setIdle(false);\n clearTimeout(idleTimer.current);\n if (playing) idleTimer.current = setTimeout(() => setIdle(true), 2500);\n };\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n const target = event.target as HTMLElement;\n const inControl = target.getAttribute(\"role\") === \"slider\";\n const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;\n const now = video.current?.currentTime ?? 0;\n const actions: Record<string, (() => void) | undefined> = {\n \" \": target.tagName === \"BUTTON\" ? undefined : toggle,\n k: toggle,\n j: () => seek(now - 10),\n l: () => seek(now + 10),\n m: toggleMute,\n f: toggleFullscreen,\n c: tracks.length ? cycleCaption : undefined,\n ArrowLeft: inControl ? undefined : () => seek(now - 5),\n ArrowRight: inControl ? undefined : () => seek(now + 5),\n ArrowUp: inControl ? undefined : () => changeVolume(volume + 0.1),\n ArrowDown: inControl ? undefined : () => changeVolume(volume - 0.1),\n Home: inControl ? undefined : () => seek(0),\n End: inControl ? undefined : () => seek(duration),\n };\n const action = /^[0-9]$/.test(key) && !inControl ? () => seek((Number(key) / 10) * duration) : actions[key];\n if (!action || event.metaKey || event.ctrlKey || event.altKey) return;\n event.preventDefault();\n action();\n wake();\n };\n\n const showControls = controls && !overlay;\n const pipSupported = typeof document !== \"undefined\" && \"pictureInPictureEnabled\" in document && document.pictureInPictureEnabled;\n\n return (\n <div\n ref={root}\n role=\"region\"\n aria-label={title}\n tabIndex={showControls ? 0 : undefined}\n data-idle={idle && playing ? \"\" : undefined}\n data-state={playing ? \"playing\" : \"paused\"}\n className={s.root({ className })}\n style={{ aspectRatio }}\n onKeyDown={showControls ? onKeyDown : undefined}\n onPointerMove={showControls ? wake : undefined}\n onPointerLeave={() => playing && setIdle(true)}\n >\n <video\n ref={video}\n className={s.video()}\n preload={preload}\n loop={loop}\n muted={muted}\n playsInline\n crossOrigin={tracks.length ? \"anonymous\" : undefined}\n onClick={showControls ? toggle : undefined}\n onPlay={() => {\n setStarted(true);\n setPlaying(true);\n wake();\n onPlay?.();\n }}\n onPause={() => {\n setPlaying(false);\n setIdle(false);\n onPause?.();\n }}\n onEnded={() => onEnded?.()}\n onTimeUpdate={(event) => {\n setTime(event.currentTarget.currentTime);\n onTimeUpdate?.(event.currentTarget.currentTime);\n }}\n onDurationChange={(event) => setDuration(Number.isFinite(event.currentTarget.duration) ? event.currentTarget.duration : 0)}\n onWaiting={() => setWaiting(true)}\n onPlaying={() => setWaiting(false)}\n onCanPlay={() => setWaiting(false)}\n onError={(event) => {\n setFailed(true);\n onError?.(event.currentTarget.error);\n }}\n >\n {tracks.map((track) => (\n <track key={track.src} src={track.src} srcLang={track.srcLang} label={track.label} kind={track.kind ?? \"subtitles\"} default={track.default} />\n ))}\n </video>\n\n {poster && !started ? <img src={poster} alt=\"\" aria-hidden decoding=\"async\" className={s.poster()} /> : null}\n\n {showControls && !playing && !failed && !waiting && (\n <button type=\"button\" className={s.big()} aria-label={labels.play} onClick={play}>\n <PlayGlyph className=\"ml-1 h-7 w-7\" />\n </button>\n )}\n\n {waiting && !failed && (\n <div className=\"pointer-events-none absolute inset-0 z-10 flex items-center justify-center\">\n <span className={s.spinner()} aria-hidden />\n <span role=\"status\" className=\"sr-only\">\n {labels.loading}\n </span>\n </div>\n )}\n\n {failed && (\n <div role=\"alert\" className={s.status()}>\n <p>{labels.error}</p>\n <button\n type=\"button\"\n className={s.button({ className: \"border border-fg-on-media/40 px-3\" })}\n onClick={() => {\n setFailed(false);\n setAttempt((a) => a + 1);\n video.current?.load();\n }}\n >\n <RetryGlyph className=\"h-4 w-4\" />\n {labels.retry}\n </button>\n </div>\n )}\n\n {overlay ? <div className={s.overlay()}>{overlay}</div> : null}\n\n {showControls && (\n <div className={s.bar()}>\n <Slider\n label={labels.seek}\n min={0}\n max={duration || 1}\n step={0.1}\n value={Math.min(time, duration || 1)}\n onValueChange={seek}\n formatValue={(v) => labels.time(formatClock(v), formatClock(duration))}\n disabled={!duration}\n className=\"[&_[role=slider]]:h-4 [&_[role=slider]]:w-4\"\n />\n <div className={s.row()}>\n <button type=\"button\" className={s.button()} aria-label={playing ? labels.pause : labels.play} onClick={toggle}>\n {playing ? <PauseGlyph className=\"h-5 w-5\" /> : <PlayGlyph className=\"h-5 w-5\" />}\n </button>\n <button type=\"button\" className={s.button()} aria-label={muted ? labels.unmute : labels.mute} onClick={toggleMute}>\n <VolumeGlyph muted={muted} className=\"h-5 w-5\" />\n </button>\n <div className={s.volume()}>\n <Slider label={labels.volume} min={0} max={1} step={0.05} value={muted ? 0 : volume} onValueChange={changeVolume} formatValue={(v) => `${Math.round(v * 100)} %`} className=\"w-full\" />\n </div>\n <span className={s.time()} aria-hidden>\n {formatClock(time)} / {formatClock(duration)}\n </span>\n <span className=\"flex-1\" />\n {playbackRates.length > 1 && (\n <button type=\"button\" className={s.button()} aria-label={labels.speed(rate)} onClick={cycleRate}>\n {rate}×\n </button>\n )}\n {tracks.length > 0 && (\n <button type=\"button\" className={s.button()} aria-pressed={caption >= 0} aria-label={labels.captions(caption >= 0 ? (tracks[caption]?.label ?? null) : null)} onClick={cycleCaption}>\n <CaptionsGlyph className=\"h-5 w-5\" />\n </button>\n )}\n {pipSupported && (\n <button type=\"button\" className={s.button()} aria-label={labels.pictureInPicture} onClick={pip}>\n <PipGlyph className=\"h-5 w-5\" />\n </button>\n )}\n <button type=\"button\" className={s.button()} aria-label={fullscreen ? labels.exitFullscreen : labels.fullscreen} onClick={toggleFullscreen}>\n <FullscreenGlyph exit={fullscreen} className=\"h-5 w-5\" />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krizaka/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "The Krizaka front-end platform's components: the primitives (button, card, dialog, toast, menu, field, tabs, chip, switch, slider, command palette, theme, countdown…) on the semantic --kz-* tokens, the animated marks (Krizaka, Orazaka, Orochia, Yazakia, Yororis) and the motion signature — web and /native.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"krizaka",
|
|
@@ -518,7 +518,7 @@
|
|
|
518
518
|
],
|
|
519
519
|
"peerDependencies": {
|
|
520
520
|
"@krizaka/query": ">=0.1.0",
|
|
521
|
-
"@krizaka/tailwind": ">=3.
|
|
521
|
+
"@krizaka/tailwind": ">=3.3.0",
|
|
522
522
|
"@shopify/flash-list": ">=2",
|
|
523
523
|
"expo-camera": ">=16",
|
|
524
524
|
"expo-video": ">=2",
|
|
@@ -570,9 +570,9 @@
|
|
|
570
570
|
"typescript": "^5.9.0",
|
|
571
571
|
"vitest": "^4.1.11",
|
|
572
572
|
"zod": "^4.6.5",
|
|
573
|
-
"@krizaka/config": "0.3.0",
|
|
574
573
|
"@krizaka/query": "0.1.0",
|
|
575
|
-
"@krizaka/tokens": "3.
|
|
574
|
+
"@krizaka/tokens": "3.3.0",
|
|
575
|
+
"@krizaka/config": "0.3.0"
|
|
576
576
|
},
|
|
577
577
|
"engines": {
|
|
578
578
|
"node": ">=20"
|
|
@@ -622,7 +622,7 @@
|
|
|
622
622
|
{
|
|
623
623
|
"name": "./native (bundled into the app by Metro, never downloaded: a regression guard, not a budget)",
|
|
624
624
|
"path": "dist/native.js",
|
|
625
|
-
"limit": "
|
|
625
|
+
"limit": "31.5 kB",
|
|
626
626
|
"gzip": true
|
|
627
627
|
},
|
|
628
628
|
{
|
|
@@ -920,7 +920,7 @@
|
|
|
920
920
|
{
|
|
921
921
|
"name": "./radio-group",
|
|
922
922
|
"path": "dist/radio-group.js",
|
|
923
|
-
"limit": "
|
|
923
|
+
"limit": "12.5 kB",
|
|
924
924
|
"gzip": true,
|
|
925
925
|
"ignore": [
|
|
926
926
|
"tailwind-variants",
|