@arbidocs/blocks 0.5.122 → 0.5.123

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/ConnectedNativeDocxComparison-QKHCYD6W.cjs +1 -1
  2. package/dist/{ConnectedPresentation-AU25OW25.cjs → ConnectedPresentation-5DOJ35XL.cjs} +7 -7
  3. package/dist/{ConnectedPresentation-AU25OW25.cjs.map → ConnectedPresentation-5DOJ35XL.cjs.map} +1 -1
  4. package/dist/{ConnectedSlidesTab-I75LKKN7.cjs → ConnectedSlidesTab-7YMEDGUU.cjs} +40 -18
  5. package/dist/ConnectedSlidesTab-7YMEDGUU.cjs.map +1 -0
  6. package/dist/ConnectedStreamedWordArtifact-AG62232A.cjs +1 -1
  7. package/dist/{FilesViewerDialog-4352KIOG.cjs → FilesViewerDialog-2QCCRWF2.cjs} +7 -7
  8. package/dist/{FilesViewerDialog-4352KIOG.cjs.map → FilesViewerDialog-2QCCRWF2.cjs.map} +1 -1
  9. package/dist/agents/index.cjs +176 -176
  10. package/dist/{chunk-LHWOKOMP.cjs → chunk-46P7IDBX.cjs} +3 -3
  11. package/dist/chunk-46P7IDBX.cjs.map +1 -0
  12. package/dist/{chunk-USNTRL22.cjs → chunk-DWMWEALB.cjs} +288 -214
  13. package/dist/chunk-DWMWEALB.cjs.map +1 -0
  14. package/dist/{chunk-UTJ223TI.cjs → chunk-JD2HX2BR.cjs} +98 -78
  15. package/dist/chunk-JD2HX2BR.cjs.map +1 -0
  16. package/dist/{chunk-QG2U2JGX.cjs → chunk-L4CIFLCU.cjs} +4 -4
  17. package/dist/{chunk-QG2U2JGX.cjs.map → chunk-L4CIFLCU.cjs.map} +1 -1
  18. package/dist/{chunk-CQQQIZU4.cjs → chunk-NQML7IUC.cjs} +3 -3
  19. package/dist/{chunk-CQQQIZU4.cjs.map → chunk-NQML7IUC.cjs.map} +1 -1
  20. package/dist/{chunk-WRLGLZIA.cjs → chunk-SNPK3R3O.cjs} +5 -5
  21. package/dist/{chunk-WRLGLZIA.cjs.map → chunk-SNPK3R3O.cjs.map} +1 -1
  22. package/dist/{chunk-XGM4VVNV.cjs → chunk-WV7BCGCK.cjs} +19 -19
  23. package/dist/{chunk-XGM4VVNV.cjs.map → chunk-WV7BCGCK.cjs.map} +1 -1
  24. package/dist/connect/docx.cjs +1 -1
  25. package/dist/connect/index.cjs +161 -161
  26. package/dist/connect/viewer.cjs +5 -5
  27. package/dist/connection/presentation/ConnectedSlidesTab.js +35 -13
  28. package/dist/connection/presentation/ConnectedSlidesTab.js.map +2 -2
  29. package/dist/connection/viewer/ConnectedDocumentViewer.js +51 -60
  30. package/dist/connection/viewer/ConnectedDocumentViewer.js.map +2 -2
  31. package/dist/connection/viewer/ViewerMoreMenu.d.ts +18 -0
  32. package/dist/connection/viewer/ViewerMoreMenu.js +45 -0
  33. package/dist/connection/viewer/ViewerMoreMenu.js.map +7 -0
  34. package/dist/connection/viewer/pdf/PdfPassageHighlights.js +3 -2
  35. package/dist/connection/viewer/pdf/PdfPassageHighlights.js.map +2 -2
  36. package/dist/document-tools/DocumentToolButtons.js +2 -2
  37. package/dist/document-tools/DocumentToolButtons.js.map +2 -2
  38. package/dist/document-tools/index.cjs +13 -13
  39. package/dist/embed/index.cjs +2 -2
  40. package/dist/index.cjs +234 -234
  41. package/dist/presentation/BoxActionBar.d.ts +2 -2
  42. package/dist/presentation/BoxActionBar.js +62 -30
  43. package/dist/presentation/BoxActionBar.js.map +2 -2
  44. package/dist/presentation/ClipActionBar.js +60 -7
  45. package/dist/presentation/ClipActionBar.js.map +2 -2
  46. package/dist/presentation/ClipViewControls.d.ts +3 -3
  47. package/dist/presentation/ClipViewControls.js +59 -64
  48. package/dist/presentation/ClipViewControls.js.map +2 -2
  49. package/dist/presentation/SlidesEditor.js +56 -42
  50. package/dist/presentation/SlidesEditor.js.map +2 -2
  51. package/dist/presentation/StageItem.js +0 -5
  52. package/dist/presentation/StageItem.js.map +2 -2
  53. package/dist/styles.css +1 -1
  54. package/dist/workflow-canvas/index.cjs +12 -12
  55. package/node_modules/@arbidocs/coedit/package.json +1 -1
  56. package/package.json +5 -5
  57. package/dist/ConnectedSlidesTab-I75LKKN7.cjs.map +0 -1
  58. package/dist/chunk-LHWOKOMP.cjs.map +0 -1
  59. package/dist/chunk-USNTRL22.cjs.map +0 -1
  60. package/dist/chunk-UTJ223TI.cjs.map +0 -1
@@ -35,27 +35,6 @@ import { usePresentation } from "./store.js";
35
35
  import { newBox } from "./boxes.js";
36
36
  import { accentColor, isLightBackground } from "./theme.js";
37
37
  import { usePresenterKeys } from "./usePresenterKeys.js";
38
- function SlideAction({
39
- label,
40
- onClick,
41
- disabled,
42
- children
43
- }) {
44
- return /* @__PURE__ */ jsx(
45
- Button,
46
- {
47
- type: "button",
48
- size: "icon",
49
- variant: "ghost",
50
- className: "size-7 text-muted-foreground hover:text-foreground pointer-coarse:size-10",
51
- title: label,
52
- "aria-label": label,
53
- disabled,
54
- onClick,
55
- children
56
- }
57
- );
58
- }
59
38
  const SlideCard = memo(function SlideCard2({
60
39
  index,
61
40
  count,
@@ -67,7 +46,7 @@ const SlideCard = memo(function SlideCard2({
67
46
  }) {
68
47
  const empty = usePresentation((s) => {
69
48
  const slide = s.slides[index];
70
- return !!slide && !slide.items.length && !slide.boxes.length && slide.layout !== "title" && slide.layout !== "section";
49
+ return !!slide && !slide.items.length && !slide.boxes.length && !slide.points.trim() && slide.layout !== "title" && slide.layout !== "section";
71
50
  });
72
51
  const notes = usePresentation((s) => s.slides[index]?.notes ?? "");
73
52
  const textEditing = useMemo(
@@ -102,26 +81,61 @@ const SlideCard = memo(function SlideCard2({
102
81
  "data-testid": `slide-actions-${index}`,
103
82
  children: [
104
83
  /* @__PURE__ */ jsx(LayoutMenu, { index, compact: true }),
105
- /* @__PURE__ */ jsx(
106
- SlideAction,
107
- {
108
- label: "Move up",
109
- disabled: index === 0,
110
- onClick: () => actions.moveSlide(index, index - 1),
111
- children: /* @__PURE__ */ jsx(ArrowUp, { className: "size-3.5" })
112
- }
113
- ),
114
- /* @__PURE__ */ jsx(
115
- SlideAction,
116
- {
117
- label: "Move down",
118
- disabled: index === count - 1,
119
- onClick: () => actions.moveSlide(index, index + 1),
120
- children: /* @__PURE__ */ jsx(ArrowDown, { className: "size-3.5" })
121
- }
122
- ),
123
- /* @__PURE__ */ jsx(SlideAction, { label: "Duplicate", onClick: () => actions.duplicateSlide(index), children: /* @__PURE__ */ jsx(Copy, { className: "size-3.5" }) }),
124
- /* @__PURE__ */ jsx(SlideAction, { label: "Delete slide", onClick: () => actions.removeSlide(index), children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" }) })
84
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
85
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
86
+ Button,
87
+ {
88
+ type: "button",
89
+ size: "icon",
90
+ variant: "ghost",
91
+ className: "size-7 text-muted-foreground hover:text-foreground pointer-coarse:size-11",
92
+ title: "Slide actions",
93
+ "aria-label": `Slide ${index + 1} actions`,
94
+ "data-testid": `slide-menu-${index}`,
95
+ children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-3.5" })
96
+ }
97
+ ) }),
98
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", children: [
99
+ /* @__PURE__ */ jsxs(
100
+ DropdownMenuItem,
101
+ {
102
+ disabled: index === 0,
103
+ onSelect: () => actions.moveSlide(index, index - 1),
104
+ children: [
105
+ /* @__PURE__ */ jsx(ArrowUp, { className: "size-4" }),
106
+ /* @__PURE__ */ jsx("span", { className: "ml-2", children: "Move up" })
107
+ ]
108
+ }
109
+ ),
110
+ /* @__PURE__ */ jsxs(
111
+ DropdownMenuItem,
112
+ {
113
+ disabled: index === count - 1,
114
+ onSelect: () => actions.moveSlide(index, index + 1),
115
+ children: [
116
+ /* @__PURE__ */ jsx(ArrowDown, { className: "size-4" }),
117
+ /* @__PURE__ */ jsx("span", { className: "ml-2", children: "Move down" })
118
+ ]
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => actions.duplicateSlide(index), children: [
122
+ /* @__PURE__ */ jsx(Copy, { className: "size-4" }),
123
+ /* @__PURE__ */ jsx("span", { className: "ml-2", children: "Duplicate" })
124
+ ] }),
125
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
126
+ /* @__PURE__ */ jsxs(
127
+ DropdownMenuItem,
128
+ {
129
+ className: "text-destructive",
130
+ onSelect: () => actions.removeSlide(index),
131
+ children: [
132
+ /* @__PURE__ */ jsx(Trash2, { className: "size-4" }),
133
+ /* @__PURE__ */ jsx("span", { className: "ml-2", children: "Delete slide" })
134
+ ]
135
+ }
136
+ )
137
+ ] })
138
+ ] })
125
139
  ]
126
140
  }
127
141
  )
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/presentation/SlidesEditor.tsx"],
4
- "sourcesContent": ["/**\n * The presentation editor: its slides one under the other, scrolled like a PDF's pages, each one\n * edited where it is \u2014 clips dragged, resized and reshaped on it, its title and points typed on\n * it. Hovering a slide shows its actions (layout, move up or down, duplicate, delete); between two\n * slides, + inserts one. One small floating bar holds the rest (Design, the host's Save and other\n * actions, Present, \"\u2026\"), so no header takes room from the slides. The clips collected for the\n * presentation wait in the tray along the bottom, to be dragged onto any slide.\n *\n * The same editor shows wherever the host mounts it (the viewer's tab, or its own window).\n */\n\nimport { memo, useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport {\n ArrowDown,\n ArrowUp,\n Circle,\n Copy,\n Minus,\n MoreHorizontal,\n Play,\n Plus,\n RectangleHorizontal,\n Shapes,\n Square,\n Trash2,\n Type,\n} from 'lucide-react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n cn,\n} from '@arbidocs/react/ui'\nimport { ClipTray } from './ClipTray'\nimport { DesignMenu } from './DesignMenu'\nimport { LayoutMenu } from './LayoutMenu'\nimport { Notice } from './Notice'\nimport { PresentationStage } from './PresentationStage'\nimport type { SlideTextEditing } from './SlideDecor'\nimport { usePresentation } from './store'\nimport { newBox, type BoxShape } from './boxes'\nimport { accentColor, isLightBackground } from './theme'\nimport { usePresenterKeys } from './usePresenterKeys'\n\nexport interface SlidesEditorProps {\n /** Present: the host opens the shareable window. */\n onPresent: () => void\n /** In the floating bar, before Present (e.g. Save, pop out). */\n actions?: ReactNode\n /** At the top of the \"\u2026\" menu (e.g. New from template, Save as, Export). */\n menuItems?: ReactNode\n /** First in the clip tray (e.g. Generate image). */\n trayStart?: ReactNode\n onChooseLogo?: () => void\n onEditImage?: (clipId: string) => void\n onCropImage?: (clipId: string) => void\n}\n\ninterface Selection {\n index: number\n clipId: string\n}\n\n/** A small icon button of a slide's hover bar. */\nfunction SlideAction({\n label,\n onClick,\n disabled,\n children,\n}: {\n label: string\n onClick: () => void\n disabled?: boolean\n children: ReactNode\n}) {\n return (\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-7 text-muted-foreground hover:text-foreground pointer-coarse:size-10\"\n title={label}\n aria-label={label}\n disabled={disabled}\n onClick={onClick}\n >\n {children}\n </Button>\n )\n}\n\nconst SlideCard = memo(function SlideCard({\n index,\n count,\n current,\n selected,\n onSelect,\n onEditImage,\n onCropImage,\n}: {\n index: number\n count: number\n current: boolean\n selected: string | null\n onSelect: (index: number, clipId: string | null) => void\n onEditImage?: (clipId: string) => void\n onCropImage?: (clipId: string) => void\n}) {\n const empty = usePresentation((s) => {\n const slide = s.slides[index]\n return (\n !!slide &&\n !slide.items.length &&\n !slide.boxes.length &&\n slide.layout !== 'title' &&\n slide.layout !== 'section'\n )\n })\n const notes = usePresentation((s) => s.slides[index]?.notes ?? '')\n const textEditing = useMemo<SlideTextEditing>(\n () => ({\n onTitle: (title) => usePresentation.getState().setSlideCaption(title, index),\n onPoints: (points) => usePresentation.getState().setSlidePoints(points, index),\n }),\n [index]\n )\n const select = useCallback((clipId: string | null) => onSelect(index, clipId), [onSelect, index])\n const actions = usePresentation.getState()\n return (\n <div className=\"group relative flex gap-2\" data-testid={`slide-${index}`}>\n {/* The gutter: the slide's number, and its actions on hover (always on a touch screen's\n current slide), clear of the slide itself. */}\n <div className=\"flex w-9 shrink-0 flex-col items-center gap-0.5\">\n <span\n className={cn(\n 'py-1 text-xs tabular-nums',\n current ? 'font-semibold text-foreground' : 'text-muted-foreground'\n )}\n >\n {index + 1}\n </span>\n <div\n className={cn(\n 'flex flex-col items-center gap-0.5 transition-opacity',\n 'opacity-0 focus-within:opacity-100 group-hover:opacity-100',\n current && 'pointer-coarse:opacity-100'\n )}\n data-testid={`slide-actions-${index}`}\n >\n <LayoutMenu index={index} compact />\n <SlideAction\n label=\"Move up\"\n disabled={index === 0}\n onClick={() => actions.moveSlide(index, index - 1)}\n >\n <ArrowUp className=\"size-3.5\" />\n </SlideAction>\n <SlideAction\n label=\"Move down\"\n disabled={index === count - 1}\n onClick={() => actions.moveSlide(index, index + 1)}\n >\n <ArrowDown className=\"size-3.5\" />\n </SlideAction>\n <SlideAction label=\"Duplicate\" onClick={() => actions.duplicateSlide(index)}>\n <Copy className=\"size-3.5\" />\n </SlideAction>\n <SlideAction label=\"Delete slide\" onClick={() => actions.removeSlide(index)}>\n <Trash2 className=\"size-3.5\" />\n </SlideAction>\n </div>\n </div>\n <div\n className={cn(\n 'relative aspect-video min-w-0 flex-1 rounded-sm',\n current && 'ring-1 ring-primary/40 ring-offset-4 ring-offset-transparent'\n )}\n >\n <PresentationStage\n editable\n slideIndex={index}\n selected={selected}\n onSelect={select}\n onEditImage={onEditImage}\n onCropImage={onCropImage}\n textEditing={textEditing}\n overlay={\n empty && (\n <div className=\"pointer-events-none absolute inset-x-0 bottom-[8%] flex justify-center\">\n <span className=\"rounded-full bg-black/40 px-3 py-1 text-[1.4cqw] text-white/70\">\n Drag a clip here from the tray\n </span>\n </div>\n )\n }\n />\n {(notes || current) && (\n <textarea\n value={notes}\n rows={notes.split('\\n').length}\n placeholder=\"Speaker notes\"\n onChange={(event) =>\n usePresentation.getState().setSlideNotes(event.target.value, index)\n }\n className=\"mt-2 block w-full resize-none rounded-sm border-0 bg-transparent px-1 py-0.5 text-xs text-muted-foreground placeholder:text-muted-foreground/50 focus:bg-background focus:text-foreground focus:outline-none focus:ring-1 focus:ring-ring\"\n aria-label={`Speaker notes for slide ${index + 1}`}\n data-testid={`slide-notes-${index}`}\n />\n )}\n </div>\n </div>\n )\n})\n\nconst INSERTS: { shape: BoxShape; label: string; icon: ReactNode }[] = [\n { shape: 'text', label: 'Text box', icon: <Type className=\"size-4\" /> },\n { shape: 'rect', label: 'Rectangle', icon: <Square className=\"size-4\" /> },\n {\n shape: 'roundRect',\n label: 'Rounded rectangle',\n icon: <RectangleHorizontal className=\"size-4\" />,\n },\n { shape: 'ellipse', label: 'Ellipse', icon: <Circle className=\"size-4\" /> },\n { shape: 'line', label: 'Line', icon: <Minus className=\"size-4\" /> },\n]\n\n/** Insert a text box or a shape on the current slide; it is selected, ready to type in. */\nfunction InsertMenu({ onInserted }: { onInserted: (index: number, boxId: string) => void }) {\n const insert = (shape: BoxShape) => {\n const state = usePresentation.getState()\n const slide = state.slides[state.current]\n const box = newBox(shape, accentColor(state.theme.accent))\n // On a light slide (an imported deck's), text is dark.\n const dark = isLightBackground(slide?.background)\n const { id, ...rest } = dark\n ? {\n ...box,\n paragraphs: box.paragraphs.map((p) => ({\n ...p,\n runs: p.runs.map((r) => ({ color: '#1f2328', ...r })),\n })),\n }\n : box\n void id\n onInserted(state.current, state.addBox(rest))\n }\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-8 text-muted-foreground hover:text-foreground pointer-coarse:size-11\"\n title=\"Insert a text box or shape\"\n aria-label=\"Insert\"\n data-testid=\"slides-insert\"\n >\n <Shapes className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {INSERTS.map(({ shape, label, icon }) => (\n <DropdownMenuItem\n key={shape}\n onSelect={() => insert(shape)}\n data-testid={`slides-insert-${shape}`}\n >\n {icon}\n <span className=\"ml-2\">{label}</span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/** Between two slides (and after the last): + inserts a slide there, shown on hover. */\nfunction InsertSlide({ after, always }: { after: number; always?: boolean }) {\n return (\n <div className=\"group/insert relative flex h-8 items-center justify-center pl-11\">\n <button\n type=\"button\"\n onClick={() => usePresentation.getState().addSlide(undefined, after)}\n className={cn(\n 'flex items-center gap-1 rounded-full border border-dashed border-border bg-background px-2.5 py-0.5 text-xs text-muted-foreground transition-opacity hover:border-solid hover:text-foreground focus-visible:opacity-100',\n !always && 'opacity-0 group-hover/insert:opacity-100 pointer-coarse:opacity-100'\n )}\n aria-label={`New slide after slide ${after + 1}`}\n data-testid={`slide-insert-${after}`}\n >\n <Plus className=\"size-3\" />\n Slide\n </button>\n </div>\n )\n}\n\nexport function SlidesEditor({\n onPresent,\n actions,\n menuItems,\n trayStart,\n onChooseLogo,\n onEditImage,\n onCropImage,\n}: SlidesEditorProps) {\n const ids = usePresentation((s) => s.slides.map((slide) => slide.id).join(' '))\n const slideIds = useMemo(() => ids.split(' '), [ids])\n const current = usePresentation((s) => s.current)\n const captions = usePresentation((s) => s.captions)\n // Presenting in the presentation's own window: Present becomes Stop presenting.\n const presenting = usePresentation((s) => s.presenting)\n const [selection, setSelection] = useState<Selection | null>(null)\n const [view, setView] = useState<Window | null>(null)\n const list = useRef<HTMLDivElement>(null)\n const rootRef = useCallback((node: HTMLDivElement | null) => {\n if (node) setView(node.ownerDocument.defaultView)\n }, [])\n\n const onSelect = useCallback((index: number, clipId: string | null) => {\n setSelection(clipId ? { index, clipId } : null)\n }, [])\n const keySelection = useMemo(\n () => ({ clipId: selection?.clipId ?? null, clear: () => setSelection(null) }),\n [selection]\n )\n usePresenterKeys(view, false, keySelection)\n\n // A slide added, duplicated or moved becomes the current one: brought into view.\n useEffect(() => {\n const card = list.current?.querySelector(`[data-testid=\"slide-${current}\"]`)\n card?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })\n }, [current, slideIds.length])\n\n return (\n <div\n ref={rootRef}\n className=\"relative flex h-full min-h-0 flex-col bg-muted/30\"\n data-testid=\"slides-editor\"\n >\n <div ref={list} className=\"min-h-0 flex-1 overflow-y-auto\">\n <div className=\"mx-auto flex max-w-[1100px] flex-col pb-6 pl-2 pr-6 pt-14\">\n {slideIds.map((id, index) => (\n <div key={id}>\n <SlideCard\n index={index}\n count={slideIds.length}\n current={index === current}\n selected={selection?.index === index ? selection.clipId : null}\n onSelect={onSelect}\n onEditImage={onEditImage}\n onCropImage={onCropImage}\n />\n <InsertSlide after={index} always={index === slideIds.length - 1} />\n </div>\n ))}\n </div>\n </div>\n\n {/* The rest, in one small floating bar: no header row. */}\n <div\n className=\"absolute right-4 top-2.5 z-40 flex items-center gap-0.5 rounded-full border border-border bg-background p-1 shadow-md\"\n data-testid=\"slides-toolbar\"\n >\n <DesignMenu onChooseLogo={onChooseLogo} />\n <InsertMenu onInserted={(index, boxId) => setSelection({ index, clipId: boxId })} />\n {actions}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-8 text-muted-foreground hover:text-foreground pointer-coarse:size-11\"\n title=\"More\"\n aria-label=\"More\"\n >\n <MoreHorizontal className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {menuItems}\n {menuItems && <DropdownMenuSeparator />}\n <DropdownMenuCheckboxItem\n checked={captions}\n onCheckedChange={(on) => usePresentation.getState().setCaptions(!!on)}\n >\n Show source labels\n </DropdownMenuCheckboxItem>\n </DropdownMenuContent>\n </DropdownMenu>\n {presenting ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"destructive\"\n className=\"ml-0.5 h-8 rounded-full\"\n onClick={() => usePresentation.getState().setPresenting(false)}\n title=\"Stop presenting (the presentation window closes)\"\n data-testid=\"presentation-stop-button\"\n >\n <Square className=\"size-3 fill-current\" />\n Stop presenting\n </Button>\n ) : (\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"ml-0.5 h-8 rounded-full\"\n onClick={() => {\n setSelection(null)\n onPresent()\n }}\n title=\"Present: the slides alone, in a window to share or put full screen\"\n data-testid=\"presentation-present-button\"\n >\n <Play className=\"size-3.5\" />\n Present\n </Button>\n )}\n </div>\n\n <ClipTray start={trayStart} />\n <Notice />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * The presentation editor: its slides one under the other, scrolled like a PDF's pages, each one\n * edited where it is \u2014 clips dragged, resized and reshaped on it, its title and points typed on\n * it. Hovering a slide shows its actions (layout, move up or down, duplicate, delete); between two\n * slides, + inserts one. One small floating bar holds the rest (Design, the host's Save and other\n * actions, Present, \"\u2026\"), so no header takes room from the slides. The clips collected for the\n * presentation wait in the tray along the bottom, to be dragged onto any slide.\n *\n * The same editor shows wherever the host mounts it (the viewer's tab, or its own window).\n */\n\nimport { memo, useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport {\n ArrowDown,\n ArrowUp,\n Circle,\n Copy,\n Minus,\n MoreHorizontal,\n Play,\n Plus,\n RectangleHorizontal,\n Shapes,\n Square,\n Trash2,\n Type,\n} from 'lucide-react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n cn,\n} from '@arbidocs/react/ui'\nimport { ClipTray } from './ClipTray'\nimport { DesignMenu } from './DesignMenu'\nimport { LayoutMenu } from './LayoutMenu'\nimport { Notice } from './Notice'\nimport { PresentationStage } from './PresentationStage'\nimport type { SlideTextEditing } from './SlideDecor'\nimport { usePresentation } from './store'\nimport { newBox, type BoxShape } from './boxes'\nimport { accentColor, isLightBackground } from './theme'\nimport { usePresenterKeys } from './usePresenterKeys'\n\nexport interface SlidesEditorProps {\n /** Present: the host opens the shareable window. */\n onPresent: () => void\n /** In the floating bar, before Present (e.g. Save, pop out). */\n actions?: ReactNode\n /** At the top of the \"\u2026\" menu (e.g. New from template, Save as, Export). */\n menuItems?: ReactNode\n /** First in the clip tray (e.g. Generate image). */\n trayStart?: ReactNode\n onChooseLogo?: () => void\n onEditImage?: (clipId: string) => void\n onCropImage?: (clipId: string) => void\n}\n\ninterface Selection {\n index: number\n clipId: string\n}\n\nconst SlideCard = memo(function SlideCard({\n index,\n count,\n current,\n selected,\n onSelect,\n onEditImage,\n onCropImage,\n}: {\n index: number\n count: number\n current: boolean\n selected: string | null\n onSelect: (index: number, clipId: string | null) => void\n onEditImage?: (clipId: string) => void\n onCropImage?: (clipId: string) => void\n}) {\n const empty = usePresentation((s) => {\n const slide = s.slides[index]\n return (\n !!slide &&\n !slide.items.length &&\n !slide.boxes.length &&\n !slide.points.trim() &&\n slide.layout !== 'title' &&\n slide.layout !== 'section'\n )\n })\n const notes = usePresentation((s) => s.slides[index]?.notes ?? '')\n const textEditing = useMemo<SlideTextEditing>(\n () => ({\n onTitle: (title) => usePresentation.getState().setSlideCaption(title, index),\n onPoints: (points) => usePresentation.getState().setSlidePoints(points, index),\n }),\n [index]\n )\n const select = useCallback((clipId: string | null) => onSelect(index, clipId), [onSelect, index])\n const actions = usePresentation.getState()\n return (\n <div className=\"group relative flex gap-2\" data-testid={`slide-${index}`}>\n {/* The gutter: the slide's number, and its actions on hover (always on a touch screen's\n current slide), clear of the slide itself. */}\n <div className=\"flex w-9 shrink-0 flex-col items-center gap-0.5\">\n <span\n className={cn(\n 'py-1 text-xs tabular-nums',\n current ? 'font-semibold text-foreground' : 'text-muted-foreground'\n )}\n >\n {index + 1}\n </span>\n <div\n className={cn(\n 'flex flex-col items-center gap-0.5 transition-opacity',\n 'opacity-0 focus-within:opacity-100 group-hover:opacity-100',\n current && 'pointer-coarse:opacity-100'\n )}\n data-testid={`slide-actions-${index}`}\n >\n <LayoutMenu index={index} compact />\n {/* The slide's other actions, behind \"\u2026\": moving it, copying it, deleting it. */}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-7 text-muted-foreground hover:text-foreground pointer-coarse:size-11\"\n title=\"Slide actions\"\n aria-label={`Slide ${index + 1} actions`}\n data-testid={`slide-menu-${index}`}\n >\n <MoreHorizontal className=\"size-3.5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem\n disabled={index === 0}\n onSelect={() => actions.moveSlide(index, index - 1)}\n >\n <ArrowUp className=\"size-4\" />\n <span className=\"ml-2\">Move up</span>\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={index === count - 1}\n onSelect={() => actions.moveSlide(index, index + 1)}\n >\n <ArrowDown className=\"size-4\" />\n <span className=\"ml-2\">Move down</span>\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={() => actions.duplicateSlide(index)}>\n <Copy className=\"size-4\" />\n <span className=\"ml-2\">Duplicate</span>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-destructive\"\n onSelect={() => actions.removeSlide(index)}\n >\n <Trash2 className=\"size-4\" />\n <span className=\"ml-2\">Delete slide</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n <div\n className={cn(\n 'relative aspect-video min-w-0 flex-1 rounded-sm',\n current && 'ring-1 ring-primary/40 ring-offset-4 ring-offset-transparent'\n )}\n >\n <PresentationStage\n editable\n slideIndex={index}\n selected={selected}\n onSelect={select}\n onEditImage={onEditImage}\n onCropImage={onCropImage}\n textEditing={textEditing}\n overlay={\n empty && (\n <div className=\"pointer-events-none absolute inset-x-0 bottom-[8%] flex justify-center\">\n <span className=\"rounded-full bg-black/40 px-3 py-1 text-[1.4cqw] text-white/70\">\n Drag a clip here from the tray\n </span>\n </div>\n )\n }\n />\n {(notes || current) && (\n <textarea\n value={notes}\n rows={notes.split('\\n').length}\n placeholder=\"Speaker notes\"\n onChange={(event) =>\n usePresentation.getState().setSlideNotes(event.target.value, index)\n }\n className=\"mt-2 block w-full resize-none rounded-sm border-0 bg-transparent px-1 py-0.5 text-xs text-muted-foreground placeholder:text-muted-foreground/50 focus:bg-background focus:text-foreground focus:outline-none focus:ring-1 focus:ring-ring\"\n aria-label={`Speaker notes for slide ${index + 1}`}\n data-testid={`slide-notes-${index}`}\n />\n )}\n </div>\n </div>\n )\n})\n\nconst INSERTS: { shape: BoxShape; label: string; icon: ReactNode }[] = [\n { shape: 'text', label: 'Text box', icon: <Type className=\"size-4\" /> },\n { shape: 'rect', label: 'Rectangle', icon: <Square className=\"size-4\" /> },\n {\n shape: 'roundRect',\n label: 'Rounded rectangle',\n icon: <RectangleHorizontal className=\"size-4\" />,\n },\n { shape: 'ellipse', label: 'Ellipse', icon: <Circle className=\"size-4\" /> },\n { shape: 'line', label: 'Line', icon: <Minus className=\"size-4\" /> },\n]\n\n/** Insert a text box or a shape on the current slide; it is selected, ready to type in. */\nfunction InsertMenu({ onInserted }: { onInserted: (index: number, boxId: string) => void }) {\n const insert = (shape: BoxShape) => {\n const state = usePresentation.getState()\n const slide = state.slides[state.current]\n const box = newBox(shape, accentColor(state.theme.accent))\n // On a light slide (an imported deck's), text is dark.\n const dark = isLightBackground(slide?.background)\n const { id, ...rest } = dark\n ? {\n ...box,\n paragraphs: box.paragraphs.map((p) => ({\n ...p,\n runs: p.runs.map((r) => ({ color: '#1f2328', ...r })),\n })),\n }\n : box\n void id\n onInserted(state.current, state.addBox(rest))\n }\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-8 text-muted-foreground hover:text-foreground pointer-coarse:size-11\"\n title=\"Insert a text box or shape\"\n aria-label=\"Insert\"\n data-testid=\"slides-insert\"\n >\n <Shapes className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {INSERTS.map(({ shape, label, icon }) => (\n <DropdownMenuItem\n key={shape}\n onSelect={() => insert(shape)}\n data-testid={`slides-insert-${shape}`}\n >\n {icon}\n <span className=\"ml-2\">{label}</span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/** Between two slides (and after the last): + inserts a slide there, shown on hover. */\nfunction InsertSlide({ after, always }: { after: number; always?: boolean }) {\n return (\n <div className=\"group/insert relative flex h-8 items-center justify-center pl-11\">\n <button\n type=\"button\"\n onClick={() => usePresentation.getState().addSlide(undefined, after)}\n className={cn(\n 'flex items-center gap-1 rounded-full border border-dashed border-border bg-background px-2.5 py-0.5 text-xs text-muted-foreground transition-opacity hover:border-solid hover:text-foreground focus-visible:opacity-100',\n !always && 'opacity-0 group-hover/insert:opacity-100 pointer-coarse:opacity-100'\n )}\n aria-label={`New slide after slide ${after + 1}`}\n data-testid={`slide-insert-${after}`}\n >\n <Plus className=\"size-3\" />\n Slide\n </button>\n </div>\n )\n}\n\nexport function SlidesEditor({\n onPresent,\n actions,\n menuItems,\n trayStart,\n onChooseLogo,\n onEditImage,\n onCropImage,\n}: SlidesEditorProps) {\n const ids = usePresentation((s) => s.slides.map((slide) => slide.id).join(' '))\n const slideIds = useMemo(() => ids.split(' '), [ids])\n const current = usePresentation((s) => s.current)\n const captions = usePresentation((s) => s.captions)\n // Presenting in the presentation's own window: Present becomes Stop presenting.\n const presenting = usePresentation((s) => s.presenting)\n const [selection, setSelection] = useState<Selection | null>(null)\n const [view, setView] = useState<Window | null>(null)\n const list = useRef<HTMLDivElement>(null)\n const rootRef = useCallback((node: HTMLDivElement | null) => {\n if (node) setView(node.ownerDocument.defaultView)\n }, [])\n\n const onSelect = useCallback((index: number, clipId: string | null) => {\n setSelection(clipId ? { index, clipId } : null)\n }, [])\n const keySelection = useMemo(\n () => ({ clipId: selection?.clipId ?? null, clear: () => setSelection(null) }),\n [selection]\n )\n usePresenterKeys(view, false, keySelection)\n\n // A slide added, duplicated or moved becomes the current one: brought into view.\n useEffect(() => {\n const card = list.current?.querySelector(`[data-testid=\"slide-${current}\"]`)\n card?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })\n }, [current, slideIds.length])\n\n return (\n <div\n ref={rootRef}\n className=\"relative flex h-full min-h-0 flex-col bg-muted/30\"\n data-testid=\"slides-editor\"\n >\n <div ref={list} className=\"min-h-0 flex-1 overflow-y-auto\">\n <div className=\"mx-auto flex max-w-[1100px] flex-col pb-6 pl-2 pr-6 pt-14\">\n {slideIds.map((id, index) => (\n <div key={id}>\n <SlideCard\n index={index}\n count={slideIds.length}\n current={index === current}\n selected={selection?.index === index ? selection.clipId : null}\n onSelect={onSelect}\n onEditImage={onEditImage}\n onCropImage={onCropImage}\n />\n <InsertSlide after={index} always={index === slideIds.length - 1} />\n </div>\n ))}\n </div>\n </div>\n\n {/* The rest, in one small floating bar: no header row. */}\n <div\n className=\"absolute right-4 top-2.5 z-40 flex items-center gap-0.5 rounded-full border border-border bg-background p-1 shadow-md\"\n data-testid=\"slides-toolbar\"\n >\n <DesignMenu onChooseLogo={onChooseLogo} />\n <InsertMenu onInserted={(index, boxId) => setSelection({ index, clipId: boxId })} />\n {actions}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-8 text-muted-foreground hover:text-foreground pointer-coarse:size-11\"\n title=\"More\"\n aria-label=\"More\"\n >\n <MoreHorizontal className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {menuItems}\n {menuItems && <DropdownMenuSeparator />}\n <DropdownMenuCheckboxItem\n checked={captions}\n onCheckedChange={(on) => usePresentation.getState().setCaptions(!!on)}\n >\n Show source labels\n </DropdownMenuCheckboxItem>\n </DropdownMenuContent>\n </DropdownMenu>\n {presenting ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"destructive\"\n className=\"ml-0.5 h-8 rounded-full\"\n onClick={() => usePresentation.getState().setPresenting(false)}\n title=\"Stop presenting (the presentation window closes)\"\n data-testid=\"presentation-stop-button\"\n >\n <Square className=\"size-3 fill-current\" />\n Stop presenting\n </Button>\n ) : (\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"ml-0.5 h-8 rounded-full\"\n onClick={() => {\n setSelection(null)\n onPresent()\n }}\n title=\"Present: the slides alone, in a window to share or put full screen\"\n data-testid=\"presentation-present-button\"\n >\n <Play className=\"size-3.5\" />\n Present\n </Button>\n )}\n </div>\n\n <ClipTray start={trayStart} />\n <Notice />\n </div>\n )\n}\n"],
5
+ "mappings": ";AA8GQ,cAiCM,YAjCN;AAnGR,SAAS,MAAM,aAAa,WAAW,SAAS,QAAQ,gBAAgC;AACxF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,yBAAyB;AAElC,SAAS,uBAAuB;AAChC,SAAS,cAA6B;AACtC,SAAS,aAAa,yBAAyB;AAC/C,SAAS,wBAAwB;AAqBjC,MAAM,YAAY,KAAK,SAASA,WAAU;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,QAAM,QAAQ,gBAAgB,CAAC,MAAM;AACnC,UAAM,QAAQ,EAAE,OAAO,KAAK;AAC5B,WACE,CAAC,CAAC,SACF,CAAC,MAAM,MAAM,UACb,CAAC,MAAM,MAAM,UACb,CAAC,MAAM,OAAO,KAAK,KACnB,MAAM,WAAW,WACjB,MAAM,WAAW;AAAA,EAErB,CAAC;AACD,QAAM,QAAQ,gBAAgB,CAAC,MAAM,EAAE,OAAO,KAAK,GAAG,SAAS,EAAE;AACjE,QAAM,cAAc;AAAA,IAClB,OAAO;AAAA,MACL,SAAS,CAAC,UAAU,gBAAgB,SAAS,EAAE,gBAAgB,OAAO,KAAK;AAAA,MAC3E,UAAU,CAAC,WAAW,gBAAgB,SAAS,EAAE,eAAe,QAAQ,KAAK;AAAA,IAC/E;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AACA,QAAM,SAAS,YAAY,CAAC,WAA0B,SAAS,OAAO,MAAM,GAAG,CAAC,UAAU,KAAK,CAAC;AAChG,QAAM,UAAU,gBAAgB,SAAS;AACzC,SACE,qBAAC,SAAI,WAAU,6BAA4B,eAAa,SAAS,KAAK,IAGpE;AAAA,yBAAC,SAAI,WAAU,mDACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,UAAU,kCAAkC;AAAA,UAC9C;AAAA,UAEC,kBAAQ;AAAA;AAAA,MACX;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA,WAAW;AAAA,UACb;AAAA,UACA,eAAa,iBAAiB,KAAK;AAAA,UAEnC;AAAA,gCAAC,cAAW,OAAc,SAAO,MAAC;AAAA,YAElC,qBAAC,gBACC;AAAA,kCAAC,uBAAoB,SAAO,MAC1B;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,MAAK;AAAA,kBACL,SAAQ;AAAA,kBACR,WAAU;AAAA,kBACV,OAAM;AAAA,kBACN,cAAY,SAAS,QAAQ,CAAC;AAAA,kBAC9B,eAAa,cAAc,KAAK;AAAA,kBAEhC,8BAAC,kBAAe,WAAU,YAAW;AAAA;AAAA,cACvC,GACF;AAAA,cACA,qBAAC,uBAAoB,OAAM,SACzB;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,UAAU,UAAU;AAAA,oBACpB,UAAU,MAAM,QAAQ,UAAU,OAAO,QAAQ,CAAC;AAAA,oBAElD;AAAA,0CAAC,WAAQ,WAAU,UAAS;AAAA,sBAC5B,oBAAC,UAAK,WAAU,QAAO,qBAAO;AAAA;AAAA;AAAA,gBAChC;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,UAAU,UAAU,QAAQ;AAAA,oBAC5B,UAAU,MAAM,QAAQ,UAAU,OAAO,QAAQ,CAAC;AAAA,oBAElD;AAAA,0CAAC,aAAU,WAAU,UAAS;AAAA,sBAC9B,oBAAC,UAAK,WAAU,QAAO,uBAAS;AAAA;AAAA;AAAA,gBAClC;AAAA,gBACA,qBAAC,oBAAiB,UAAU,MAAM,QAAQ,eAAe,KAAK,GAC5D;AAAA,sCAAC,QAAK,WAAU,UAAS;AAAA,kBACzB,oBAAC,UAAK,WAAU,QAAO,uBAAS;AAAA,mBAClC;AAAA,gBACA,oBAAC,yBAAsB;AAAA,gBACvB;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAU;AAAA,oBACV,UAAU,MAAM,QAAQ,YAAY,KAAK;AAAA,oBAEzC;AAAA,0CAAC,UAAO,WAAU,UAAS;AAAA,sBAC3B,oBAAC,UAAK,WAAU,QAAO,0BAAY;AAAA;AAAA;AAAA,gBACrC;AAAA,iBACF;AAAA,eACF;AAAA;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,WAAW;AAAA,QACb;AAAA,QAEA;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,UAAQ;AAAA,cACR,YAAY;AAAA,cACZ;AAAA,cACA,UAAU;AAAA,cACV;AAAA,cACA;AAAA,cACA;AAAA,cACA,SACE,SACE,oBAAC,SAAI,WAAU,0EACb,8BAAC,UAAK,WAAU,kEAAiE,4CAEjF,GACF;AAAA;AAAA,UAGN;AAAA,WACE,SAAS,YACT;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,MAAM,MAAM,MAAM,IAAI,EAAE;AAAA,cACxB,aAAY;AAAA,cACZ,UAAU,CAAC,UACT,gBAAgB,SAAS,EAAE,cAAc,MAAM,OAAO,OAAO,KAAK;AAAA,cAEpE,WAAU;AAAA,cACV,cAAY,2BAA2B,QAAQ,CAAC;AAAA,cAChD,eAAa,eAAe,KAAK;AAAA;AAAA,UACnC;AAAA;AAAA;AAAA,IAEJ;AAAA,KACF;AAEJ,CAAC;AAED,MAAM,UAAiE;AAAA,EACrE,EAAE,OAAO,QAAQ,OAAO,YAAY,MAAM,oBAAC,QAAK,WAAU,UAAS,EAAG;AAAA,EACtE,EAAE,OAAO,QAAQ,OAAO,aAAa,MAAM,oBAAC,UAAO,WAAU,UAAS,EAAG;AAAA,EACzE;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM,oBAAC,uBAAoB,WAAU,UAAS;AAAA,EAChD;AAAA,EACA,EAAE,OAAO,WAAW,OAAO,WAAW,MAAM,oBAAC,UAAO,WAAU,UAAS,EAAG;AAAA,EAC1E,EAAE,OAAO,QAAQ,OAAO,QAAQ,MAAM,oBAAC,SAAM,WAAU,UAAS,EAAG;AACrE;AAGA,SAAS,WAAW,EAAE,WAAW,GAA2D;AAC1F,QAAM,SAAS,CAAC,UAAoB;AAClC,UAAM,QAAQ,gBAAgB,SAAS;AACvC,UAAM,QAAQ,MAAM,OAAO,MAAM,OAAO;AACxC,UAAM,MAAM,OAAO,OAAO,YAAY,MAAM,MAAM,MAAM,CAAC;AAEzD,UAAM,OAAO,kBAAkB,OAAO,UAAU;AAChD,UAAM,EAAE,IAAI,GAAG,KAAK,IAAI,OACpB;AAAA,MACE,GAAG;AAAA,MACH,YAAY,IAAI,WAAW,IAAI,CAAC,OAAO;AAAA,QACrC,GAAG;AAAA,QACH,MAAM,EAAE,KAAK,IAAI,CAAC,OAAO,EAAE,OAAO,WAAW,GAAG,EAAE,EAAE;AAAA,MACtD,EAAE;AAAA,IACJ,IACA;AACJ,SAAK;AACL,eAAW,MAAM,SAAS,MAAM,OAAO,IAAI,CAAC;AAAA,EAC9C;AACA,SACE,qBAAC,gBACC;AAAA,wBAAC,uBAAoB,SAAO,MAC1B;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,WAAU;AAAA,QACV,OAAM;AAAA,QACN,cAAW;AAAA,QACX,eAAY;AAAA,QAEZ,8BAAC,UAAO,WAAU,UAAS;AAAA;AAAA,IAC7B,GACF;AAAA,IACA,oBAAC,uBAAoB,OAAM,OACxB,kBAAQ,IAAI,CAAC,EAAE,OAAO,OAAO,KAAK,MACjC;AAAA,MAAC;AAAA;AAAA,QAEC,UAAU,MAAM,OAAO,KAAK;AAAA,QAC5B,eAAa,iBAAiB,KAAK;AAAA,QAElC;AAAA;AAAA,UACD,oBAAC,UAAK,WAAU,QAAQ,iBAAM;AAAA;AAAA;AAAA,MALzB;AAAA,IAMP,CACD,GACH;AAAA,KACF;AAEJ;AAGA,SAAS,YAAY,EAAE,OAAO,OAAO,GAAwC;AAC3E,SACE,oBAAC,SAAI,WAAU,oEACb;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS,MAAM,gBAAgB,SAAS,EAAE,SAAS,QAAW,KAAK;AAAA,MACnE,WAAW;AAAA,QACT;AAAA,QACA,CAAC,UAAU;AAAA,MACb;AAAA,MACA,cAAY,yBAAyB,QAAQ,CAAC;AAAA,MAC9C,eAAa,gBAAgB,KAAK;AAAA,MAElC;AAAA,4BAAC,QAAK,WAAU,UAAS;AAAA,QAAE;AAAA;AAAA;AAAA,EAE7B,GACF;AAEJ;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,MAAM,gBAAgB,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,UAAU,MAAM,EAAE,EAAE,KAAK,GAAG,CAAC;AAC9E,QAAM,WAAW,QAAQ,MAAM,IAAI,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC;AACpD,QAAM,UAAU,gBAAgB,CAAC,MAAM,EAAE,OAAO;AAChD,QAAM,WAAW,gBAAgB,CAAC,MAAM,EAAE,QAAQ;AAElD,QAAM,aAAa,gBAAgB,CAAC,MAAM,EAAE,UAAU;AACtD,QAAM,CAAC,WAAW,YAAY,IAAI,SAA2B,IAAI;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,IAAI;AACpD,QAAM,OAAO,OAAuB,IAAI;AACxC,QAAM,UAAU,YAAY,CAAC,SAAgC;AAC3D,QAAI,KAAM,SAAQ,KAAK,cAAc,WAAW;AAAA,EAClD,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,YAAY,CAAC,OAAe,WAA0B;AACrE,iBAAa,SAAS,EAAE,OAAO,OAAO,IAAI,IAAI;AAAA,EAChD,GAAG,CAAC,CAAC;AACL,QAAM,eAAe;AAAA,IACnB,OAAO,EAAE,QAAQ,WAAW,UAAU,MAAM,OAAO,MAAM,aAAa,IAAI,EAAE;AAAA,IAC5E,CAAC,SAAS;AAAA,EACZ;AACA,mBAAiB,MAAM,OAAO,YAAY;AAG1C,YAAU,MAAM;AACd,UAAM,OAAO,KAAK,SAAS,cAAc,uBAAuB,OAAO,IAAI;AAC3E,UAAM,iBAAiB,EAAE,OAAO,WAAW,UAAU,SAAS,CAAC;AAAA,EACjE,GAAG,CAAC,SAAS,SAAS,MAAM,CAAC;AAE7B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAU;AAAA,MACV,eAAY;AAAA,MAEZ;AAAA,4BAAC,SAAI,KAAK,MAAM,WAAU,kCACxB,8BAAC,SAAI,WAAU,6DACZ,mBAAS,IAAI,CAAC,IAAI,UACjB,qBAAC,SACC;AAAA;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA,OAAO,SAAS;AAAA,cAChB,SAAS,UAAU;AAAA,cACnB,UAAU,WAAW,UAAU,QAAQ,UAAU,SAAS;AAAA,cAC1D;AAAA,cACA;AAAA,cACA;AAAA;AAAA,UACF;AAAA,UACA,oBAAC,eAAY,OAAO,OAAO,QAAQ,UAAU,SAAS,SAAS,GAAG;AAAA,aAV1D,EAWV,CACD,GACH,GACF;AAAA,QAGA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAY;AAAA,YAEZ;AAAA,kCAAC,cAAW,cAA4B;AAAA,cACxC,oBAAC,cAAW,YAAY,CAAC,OAAO,UAAU,aAAa,EAAE,OAAO,QAAQ,MAAM,CAAC,GAAG;AAAA,cACjF;AAAA,cACD,qBAAC,gBACC;AAAA,oCAAC,uBAAoB,SAAO,MAC1B;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,MAAK;AAAA,oBACL,SAAQ;AAAA,oBACR,WAAU;AAAA,oBACV,OAAM;AAAA,oBACN,cAAW;AAAA,oBAEX,8BAAC,kBAAe,WAAU,UAAS;AAAA;AAAA,gBACrC,GACF;AAAA,gBACA,qBAAC,uBAAoB,OAAM,OACxB;AAAA;AAAA,kBACA,aAAa,oBAAC,yBAAsB;AAAA,kBACrC;AAAA,oBAAC;AAAA;AAAA,sBACC,SAAS;AAAA,sBACT,iBAAiB,CAAC,OAAO,gBAAgB,SAAS,EAAE,YAAY,CAAC,CAAC,EAAE;AAAA,sBACrE;AAAA;AAAA,kBAED;AAAA,mBACF;AAAA,iBACF;AAAA,cACC,aACC;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,MAAK;AAAA,kBACL,SAAQ;AAAA,kBACR,WAAU;AAAA,kBACV,SAAS,MAAM,gBAAgB,SAAS,EAAE,cAAc,KAAK;AAAA,kBAC7D,OAAM;AAAA,kBACN,eAAY;AAAA,kBAEZ;AAAA,wCAAC,UAAO,WAAU,uBAAsB;AAAA,oBAAE;AAAA;AAAA;AAAA,cAE5C,IAEA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,MAAK;AAAA,kBACL,WAAU;AAAA,kBACV,SAAS,MAAM;AACb,iCAAa,IAAI;AACjB,8BAAU;AAAA,kBACZ;AAAA,kBACA,OAAM;AAAA,kBACN,eAAY;AAAA,kBAEZ;AAAA,wCAAC,QAAK,WAAU,YAAW;AAAA,oBAAE;AAAA;AAAA;AAAA,cAE/B;AAAA;AAAA;AAAA,QAEJ;AAAA,QAEA,oBAAC,YAAS,OAAO,WAAW;AAAA,QAC5B,oBAAC,UAAO;AAAA;AAAA;AAAA,EACV;AAEJ;",
6
6
  "names": ["SlideCard"]
7
7
  }
@@ -3,7 +3,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { memo, useRef, useState } from "react";
4
4
  import { cn } from "@arbidocs/react/ui";
5
5
  import { ClipImage } from "./ClipImage.js";
6
- import { ClipViewControls } from "./ClipViewControls.js";
7
6
  import {
8
7
  frameHeight,
9
8
  moveFrame,
@@ -36,7 +35,6 @@ const StageItem = memo(function StageItem2({
36
35
  }) {
37
36
  const element = useRef(null);
38
37
  const gesture = useRef(null);
39
- const [dragging, setDragging] = useState(false);
40
38
  const [reshape, setReshape] = useState(null);
41
39
  const shown = reshape ? { ...clip, ...reshape.shape } : clip;
42
40
  const box = reshape?.frame ?? frame;
@@ -86,7 +84,6 @@ const StageItem = memo(function StageItem2({
86
84
  const g = gesture.current;
87
85
  if (!g) return;
88
86
  if (!g.next) {
89
- setDragging(true);
90
87
  host.dragging(true);
91
88
  if (element.current) element.current.style.willChange = "transform";
92
89
  }
@@ -117,7 +114,6 @@ const StageItem = memo(function StageItem2({
117
114
  }
118
115
  if (!g.next) return;
119
116
  setReshape(null);
120
- setDragging(false);
121
117
  host.dragging(false);
122
118
  host.guides([]);
123
119
  host.commit(clip.id, g.next.frame, g.next.shape);
@@ -167,7 +163,6 @@ const StageItem = memo(function StageItem2({
167
163
  children: label
168
164
  }
169
165
  ),
170
- selected && !dragging && /* @__PURE__ */ jsx(ClipViewControls, { clip }),
171
166
  selected && CORNERS.map(({ corner, left, top, cursor }) => /* @__PURE__ */ jsx(
172
167
  "div",
173
168
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/presentation/StageItem.tsx"],
4
- "sourcesContent": ["/**\n * One clip on the stage, and its own gestures: moved by dragging it, resized from a corner (its\n * shape kept), reshaped from a side's middle (more or less of the page that way), snapping to the\n * stage and to the other clips.\n *\n * Built for smooth dragging: while a clip is moved or resized nothing re-renders and nothing is\n * laid out again. Its element is moved by a GPU transform (translate, scale) once per animation\n * frame, and the new frame reaches the presentation once, when the drag ends. Only reshaping, which\n * changes what the clip shows, re-renders the clip itself (and nothing else). The component is\n * memoised: other clips, the slide's text and the other slides are untouched by its drags.\n */\n\nimport { memo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'\nimport { cn } from '@arbidocs/react/ui'\nimport { ClipImage } from './ClipImage'\nimport { ClipViewControls } from './ClipViewControls'\nimport {\n frameHeight,\n moveFrame,\n reshapeFrame,\n resizeFrame,\n type Clip,\n type Corner,\n type Edge,\n type Frame,\n type Guide,\n type Placed,\n} from './model'\nimport { TEXT_COLORS, clipEdgeCss } from './theme'\n\ntype Kind = 'move' | Corner | Edge\n\nconst isEdge = (kind: Kind): kind is Edge =>\n kind === 'n' || kind === 'e' || kind === 's' || kind === 'w'\n\n/** The handle on each side's middle: drag it to show more (or less) of the page that way. */\nconst EDGES: { edge: Edge; left: string; top: string; cursor: string; label: string }[] = [\n { edge: 'n', left: '50%', top: '0%', cursor: 'ns-resize', label: 'top' },\n { edge: 'e', left: '100%', top: '50%', cursor: 'ew-resize', label: 'right' },\n { edge: 's', left: '50%', top: '100%', cursor: 'ns-resize', label: 'bottom' },\n { edge: 'w', left: '0%', top: '50%', cursor: 'ew-resize', label: 'left' },\n]\n\nconst CORNERS: { corner: Corner; left: string; top: string; cursor: string }[] = [\n { corner: 'nw', left: '0%', top: '0%', cursor: 'nwse-resize' },\n { corner: 'ne', left: '100%', top: '0%', cursor: 'nesw-resize' },\n { corner: 'sw', left: '0%', top: '100%', cursor: 'nesw-resize' },\n { corner: 'se', left: '100%', top: '100%', cursor: 'nwse-resize' },\n]\n\nconst EDGE_CSS = clipEdgeCss()\n\n/** What a stage gives its clips (stable across renders: the clips are memoised). */\nexport interface StageItemHost {\n /** The stage's box on screen, as a gesture begins. */\n stageBox(): DOMRect | null\n /** The other clips of the slide, as a gesture begins: what this one snaps to. */\n others(clipId: string): Placed[]\n select(clipId: string): void\n /** The snap lines to draw while a clip is dragged (empty: none). */\n guides(guides: Guide[]): void\n /** A drag is on (the stage hides the selected clip's floating actions meanwhile). */\n dragging(on: boolean): void\n /** The drag's result, once it ends. */\n commit(clipId: string, frame: Frame, shape?: Pick<Clip, 'rect' | 'aspect'>): void\n}\n\nexport interface StageItemProps {\n clip: Clip\n frame: Frame\n editable: boolean\n selected: boolean\n dim: boolean\n /** Its source label, under it (empty: none). */\n label: string\n host: StageItemHost\n}\n\ninterface Gesture {\n kind: Kind\n pointer: { x: number; y: number }\n frame: Frame\n shape: Pick<Clip, 'rect' | 'aspect'>\n stage: DOMRect\n others: Placed[]\n /** The latest result, applied at the next animation frame and committed at the end. */\n next: { frame: Frame; shape?: Pick<Clip, 'rect' | 'aspect'>; guides: Guide[] } | null\n request: number | null\n guidesKey: string\n}\n\nexport const StageItem = memo(function StageItem({\n clip,\n frame,\n editable,\n selected,\n dim,\n label,\n host,\n}: StageItemProps) {\n const element = useRef<HTMLDivElement>(null)\n const gesture = useRef<Gesture | null>(null)\n const [dragging, setDragging] = useState(false)\n // Being reshaped: drawn as it will be (the one case that re-renders while dragging).\n const [reshape, setReshape] = useState<{\n frame: Frame\n shape: Pick<Clip, 'rect' | 'aspect'>\n } | null>(null)\n\n const shown = reshape ? { ...clip, ...reshape.shape } : clip\n const box = reshape?.frame ?? frame\n\n const view = () => element.current?.ownerDocument.defaultView ?? window\n\n const apply = () => {\n const g = gesture.current\n const el = element.current\n if (!g) return\n g.request = null\n const next = g.next\n if (!next || !el) return\n if (next.shape) {\n setReshape({ frame: next.frame, shape: next.shape })\n } else {\n // Moved or resized: a transform from where the drag began (top left fixed as the origin).\n const dx = (next.frame.x - g.frame.x) * g.stage.width\n const dy = (next.frame.y - g.frame.y) * g.stage.height\n const scale = next.frame.w / g.frame.w\n el.style.transform = `translate3d(${dx}px, ${dy}px, 0) scale(${scale})`\n }\n const key = next.guides.map((guide) => `${guide.axis}${guide.at}`).join()\n if (key !== g.guidesKey) {\n g.guidesKey = key\n host.guides(next.guides)\n }\n }\n\n const start = (event: ReactPointerEvent, kind: Kind) => {\n if (!editable || event.button !== 0) return\n const stage = host.stageBox()\n if (!stage) return\n event.preventDefault()\n event.stopPropagation()\n host.select(clip.id)\n event.currentTarget.setPointerCapture(event.pointerId)\n gesture.current = {\n kind,\n pointer: { x: event.clientX, y: event.clientY },\n frame,\n shape: { rect: clip.rect, aspect: clip.aspect },\n stage,\n others: host.others(clip.id),\n next: null,\n request: null,\n guidesKey: '',\n }\n }\n\n const move = (event: ReactPointerEvent) => {\n const g = gesture.current\n if (!g) return\n if (!g.next) {\n // The first move: the drag is on (handles and actions hide; the layer is promoted).\n setDragging(true)\n host.dragging(true)\n if (element.current) element.current.style.willChange = 'transform'\n }\n const dx = (event.clientX - g.pointer.x) / g.stage.width\n const dy = (event.clientY - g.pointer.y) / g.stage.height\n // Alt (Option) places freely, without snapping.\n const free = event.altKey\n if (isEdge(g.kind)) {\n const result = reshapeFrame(g.frame, g.shape, g.kind, dx, dy, g.others, free)\n g.next = {\n frame: result.frame,\n shape: { rect: result.rect, aspect: result.aspect },\n guides: result.guides,\n }\n } else {\n g.next =\n g.kind === 'move'\n ? moveFrame(g.frame, g.shape.aspect, dx, dy, g.others, free)\n : resizeFrame(g.frame, g.shape.aspect, g.kind, dx, dy, g.others, free)\n }\n g.request ??= view().requestAnimationFrame(apply)\n }\n\n const end = () => {\n const g = gesture.current\n gesture.current = null\n if (!g) return\n if (g.request !== null) view().cancelAnimationFrame(g.request)\n const el = element.current\n if (el) {\n el.style.transform = ''\n el.style.willChange = ''\n }\n if (!g.next) return // a click: the clip is selected, nothing moved\n setReshape(null)\n setDragging(false)\n host.dragging(false)\n host.guides([])\n host.commit(clip.id, g.next.frame, g.next.shape)\n }\n\n return (\n <div\n ref={element}\n className={cn(\n 'absolute origin-top-left',\n // Lifted off the background (a picture that is part of the slide's design is not).\n !clip.plain && 'rounded-[0.6cqw] shadow-[0_0.5cqw_1.6cqw_rgba(0,0,0,0.5)]',\n editable && 'cursor-move touch-none',\n selected && 'outline outline-2 outline-offset-2 outline-primary',\n dim && 'opacity-30'\n )}\n style={{\n left: `${box.x * 100}%`,\n top: `${box.y * 100}%`,\n width: `${box.w * 100}%`,\n height: `${frameHeight(box, shown.aspect) * 100}%`,\n }}\n onPointerDown={(event) => start(event, 'move')}\n onPointerMove={move}\n onPointerUp={end}\n onPointerCancel={end}\n data-testid={`stage-item-${clip.id}`}\n >\n {/* Softly rounded, with a faint light edge: crisp on any background. */}\n <div className={cn('absolute inset-0 overflow-hidden', !clip.plain && 'rounded-[0.6cqw]')}>\n <ClipImage clip={shown} live={!editable} />\n {/* A page's text fades out at the clip's edge rather than being sliced. */}\n {clip.kind === 'pdf' && !clip.plain && (\n <div\n className=\"pointer-events-none absolute inset-0\"\n style={{ background: EDGE_CSS }}\n data-testid=\"stage-clip-edge\"\n />\n )}\n {!clip.plain && (\n <div className=\"pointer-events-none absolute inset-0 rounded-[0.6cqw] ring-1 ring-inset ring-white/15\" />\n )}\n </div>\n {label && !clip.plain && (\n // Under the clip, in the room the layout keeps (CAPTION_SPACE): never over it.\n <div\n className=\"pointer-events-none absolute left-0 top-full mt-[0.5cqw] max-w-full truncate text-[1.7cqw] leading-tight\"\n style={{ color: TEXT_COLORS.muted }}\n >\n {label}\n </div>\n )}\n {/* Scroll and zoom what it shows, on a layer over it (not while it is dragged). */}\n {selected && !dragging && <ClipViewControls clip={clip} />}\n {/* A handle on each corner (a small dot in a finger-sized target), and a bar on each side's\n middle; their moves bubble to the clip's handlers while the gesture lasts. */}\n {selected &&\n CORNERS.map(({ corner, left, top, cursor }) => (\n <div\n key={corner}\n className=\"absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11\"\n style={{ left, top, cursor, touchAction: 'none' }}\n onPointerDown={(event) => start(event, corner)}\n aria-label={`Resize from the ${corner} corner`}\n data-testid={`stage-item-resize-${corner}`}\n >\n <span className=\"pointer-events-none size-3 rounded-full border-2 border-primary bg-background\" />\n </div>\n ))}\n {selected &&\n EDGES.map(({ edge, left, top, cursor, label: side }) => (\n <div\n key={edge}\n className=\"absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11\"\n style={{ left, top, cursor, touchAction: 'none' }}\n onPointerDown={(event) => start(event, edge)}\n aria-label={`Show more or less at the ${side}`}\n title=\"Drag to show more or less of the page\"\n data-testid={`stage-item-reshape-${edge}`}\n >\n <span\n className={cn(\n 'pointer-events-none rounded-full border-2 border-primary bg-background',\n edge === 'n' || edge === 's' ? 'h-2 w-4' : 'h-4 w-2'\n )}\n />\n </div>\n ))}\n </div>\n )\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * One clip on the stage, and its own gestures: moved by dragging it, resized from a corner (its\n * shape kept), reshaped from a side's middle (more or less of the page that way), snapping to the\n * stage and to the other clips.\n *\n * Built for smooth dragging: while a clip is moved or resized nothing re-renders and nothing is\n * laid out again. Its element is moved by a GPU transform (translate, scale) once per animation\n * frame, and the new frame reaches the presentation once, when the drag ends. Only reshaping, which\n * changes what the clip shows, re-renders the clip itself (and nothing else). The component is\n * memoised: other clips, the slide's text and the other slides are untouched by its drags.\n */\n\nimport { memo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'\nimport { cn } from '@arbidocs/react/ui'\nimport { ClipImage } from './ClipImage'\nimport {\n frameHeight,\n moveFrame,\n reshapeFrame,\n resizeFrame,\n type Clip,\n type Corner,\n type Edge,\n type Frame,\n type Guide,\n type Placed,\n} from './model'\nimport { TEXT_COLORS, clipEdgeCss } from './theme'\n\ntype Kind = 'move' | Corner | Edge\n\nconst isEdge = (kind: Kind): kind is Edge =>\n kind === 'n' || kind === 'e' || kind === 's' || kind === 'w'\n\n/** The handle on each side's middle: drag it to show more (or less) of the page that way. */\nconst EDGES: { edge: Edge; left: string; top: string; cursor: string; label: string }[] = [\n { edge: 'n', left: '50%', top: '0%', cursor: 'ns-resize', label: 'top' },\n { edge: 'e', left: '100%', top: '50%', cursor: 'ew-resize', label: 'right' },\n { edge: 's', left: '50%', top: '100%', cursor: 'ns-resize', label: 'bottom' },\n { edge: 'w', left: '0%', top: '50%', cursor: 'ew-resize', label: 'left' },\n]\n\nconst CORNERS: { corner: Corner; left: string; top: string; cursor: string }[] = [\n { corner: 'nw', left: '0%', top: '0%', cursor: 'nwse-resize' },\n { corner: 'ne', left: '100%', top: '0%', cursor: 'nesw-resize' },\n { corner: 'sw', left: '0%', top: '100%', cursor: 'nesw-resize' },\n { corner: 'se', left: '100%', top: '100%', cursor: 'nwse-resize' },\n]\n\nconst EDGE_CSS = clipEdgeCss()\n\n/** What a stage gives its clips (stable across renders: the clips are memoised). */\nexport interface StageItemHost {\n /** The stage's box on screen, as a gesture begins. */\n stageBox(): DOMRect | null\n /** The other clips of the slide, as a gesture begins: what this one snaps to. */\n others(clipId: string): Placed[]\n select(clipId: string): void\n /** The snap lines to draw while a clip is dragged (empty: none). */\n guides(guides: Guide[]): void\n /** A drag is on (the stage hides the selected clip's floating actions meanwhile). */\n dragging(on: boolean): void\n /** The drag's result, once it ends. */\n commit(clipId: string, frame: Frame, shape?: Pick<Clip, 'rect' | 'aspect'>): void\n}\n\nexport interface StageItemProps {\n clip: Clip\n frame: Frame\n editable: boolean\n selected: boolean\n dim: boolean\n /** Its source label, under it (empty: none). */\n label: string\n host: StageItemHost\n}\n\ninterface Gesture {\n kind: Kind\n pointer: { x: number; y: number }\n frame: Frame\n shape: Pick<Clip, 'rect' | 'aspect'>\n stage: DOMRect\n others: Placed[]\n /** The latest result, applied at the next animation frame and committed at the end. */\n next: { frame: Frame; shape?: Pick<Clip, 'rect' | 'aspect'>; guides: Guide[] } | null\n request: number | null\n guidesKey: string\n}\n\nexport const StageItem = memo(function StageItem({\n clip,\n frame,\n editable,\n selected,\n dim,\n label,\n host,\n}: StageItemProps) {\n const element = useRef<HTMLDivElement>(null)\n const gesture = useRef<Gesture | null>(null)\n // Being reshaped: drawn as it will be (the one case that re-renders while dragging).\n const [reshape, setReshape] = useState<{\n frame: Frame\n shape: Pick<Clip, 'rect' | 'aspect'>\n } | null>(null)\n\n const shown = reshape ? { ...clip, ...reshape.shape } : clip\n const box = reshape?.frame ?? frame\n\n const view = () => element.current?.ownerDocument.defaultView ?? window\n\n const apply = () => {\n const g = gesture.current\n const el = element.current\n if (!g) return\n g.request = null\n const next = g.next\n if (!next || !el) return\n if (next.shape) {\n setReshape({ frame: next.frame, shape: next.shape })\n } else {\n // Moved or resized: a transform from where the drag began (top left fixed as the origin).\n const dx = (next.frame.x - g.frame.x) * g.stage.width\n const dy = (next.frame.y - g.frame.y) * g.stage.height\n const scale = next.frame.w / g.frame.w\n el.style.transform = `translate3d(${dx}px, ${dy}px, 0) scale(${scale})`\n }\n const key = next.guides.map((guide) => `${guide.axis}${guide.at}`).join()\n if (key !== g.guidesKey) {\n g.guidesKey = key\n host.guides(next.guides)\n }\n }\n\n const start = (event: ReactPointerEvent, kind: Kind) => {\n if (!editable || event.button !== 0) return\n const stage = host.stageBox()\n if (!stage) return\n event.preventDefault()\n event.stopPropagation()\n host.select(clip.id)\n event.currentTarget.setPointerCapture(event.pointerId)\n gesture.current = {\n kind,\n pointer: { x: event.clientX, y: event.clientY },\n frame,\n shape: { rect: clip.rect, aspect: clip.aspect },\n stage,\n others: host.others(clip.id),\n next: null,\n request: null,\n guidesKey: '',\n }\n }\n\n const move = (event: ReactPointerEvent) => {\n const g = gesture.current\n if (!g) return\n if (!g.next) {\n // The first move: the drag is on (handles and actions hide; the layer is promoted).\n host.dragging(true)\n if (element.current) element.current.style.willChange = 'transform'\n }\n const dx = (event.clientX - g.pointer.x) / g.stage.width\n const dy = (event.clientY - g.pointer.y) / g.stage.height\n // Alt (Option) places freely, without snapping.\n const free = event.altKey\n if (isEdge(g.kind)) {\n const result = reshapeFrame(g.frame, g.shape, g.kind, dx, dy, g.others, free)\n g.next = {\n frame: result.frame,\n shape: { rect: result.rect, aspect: result.aspect },\n guides: result.guides,\n }\n } else {\n g.next =\n g.kind === 'move'\n ? moveFrame(g.frame, g.shape.aspect, dx, dy, g.others, free)\n : resizeFrame(g.frame, g.shape.aspect, g.kind, dx, dy, g.others, free)\n }\n g.request ??= view().requestAnimationFrame(apply)\n }\n\n const end = () => {\n const g = gesture.current\n gesture.current = null\n if (!g) return\n if (g.request !== null) view().cancelAnimationFrame(g.request)\n const el = element.current\n if (el) {\n el.style.transform = ''\n el.style.willChange = ''\n }\n if (!g.next) return // a click: the clip is selected, nothing moved\n setReshape(null)\n host.dragging(false)\n host.guides([])\n host.commit(clip.id, g.next.frame, g.next.shape)\n }\n\n return (\n <div\n ref={element}\n className={cn(\n 'absolute origin-top-left',\n // Lifted off the background (a picture that is part of the slide's design is not).\n !clip.plain && 'rounded-[0.6cqw] shadow-[0_0.5cqw_1.6cqw_rgba(0,0,0,0.5)]',\n editable && 'cursor-move touch-none',\n selected && 'outline outline-2 outline-offset-2 outline-primary',\n dim && 'opacity-30'\n )}\n style={{\n left: `${box.x * 100}%`,\n top: `${box.y * 100}%`,\n width: `${box.w * 100}%`,\n height: `${frameHeight(box, shown.aspect) * 100}%`,\n }}\n onPointerDown={(event) => start(event, 'move')}\n onPointerMove={move}\n onPointerUp={end}\n onPointerCancel={end}\n data-testid={`stage-item-${clip.id}`}\n >\n {/* Softly rounded, with a faint light edge: crisp on any background. */}\n <div className={cn('absolute inset-0 overflow-hidden', !clip.plain && 'rounded-[0.6cqw]')}>\n <ClipImage clip={shown} live={!editable} />\n {/* A page's text fades out at the clip's edge rather than being sliced. */}\n {clip.kind === 'pdf' && !clip.plain && (\n <div\n className=\"pointer-events-none absolute inset-0\"\n style={{ background: EDGE_CSS }}\n data-testid=\"stage-clip-edge\"\n />\n )}\n {!clip.plain && (\n <div className=\"pointer-events-none absolute inset-0 rounded-[0.6cqw] ring-1 ring-inset ring-white/15\" />\n )}\n </div>\n {label && !clip.plain && (\n // Under the clip, in the room the layout keeps (CAPTION_SPACE): never over it.\n <div\n className=\"pointer-events-none absolute left-0 top-full mt-[0.5cqw] max-w-full truncate text-[1.7cqw] leading-tight\"\n style={{ color: TEXT_COLORS.muted }}\n >\n {label}\n </div>\n )}\n {/* A handle on each corner (a small dot in a finger-sized target), and a bar on each side's\n middle; their moves bubble to the clip's handlers while the gesture lasts. */}\n {selected &&\n CORNERS.map(({ corner, left, top, cursor }) => (\n <div\n key={corner}\n className=\"absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11\"\n style={{ left, top, cursor, touchAction: 'none' }}\n onPointerDown={(event) => start(event, corner)}\n aria-label={`Resize from the ${corner} corner`}\n data-testid={`stage-item-resize-${corner}`}\n >\n <span className=\"pointer-events-none size-3 rounded-full border-2 border-primary bg-background\" />\n </div>\n ))}\n {selected &&\n EDGES.map(({ edge, left, top, cursor, label: side }) => (\n <div\n key={edge}\n className=\"absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11\"\n style={{ left, top, cursor, touchAction: 'none' }}\n onPointerDown={(event) => start(event, edge)}\n aria-label={`Show more or less at the ${side}`}\n title=\"Drag to show more or less of the page\"\n data-testid={`stage-item-reshape-${edge}`}\n >\n <span\n className={cn(\n 'pointer-events-none rounded-full border-2 border-primary bg-background',\n edge === 'n' || edge === 's' ? 'h-2 w-4' : 'h-4 w-2'\n )}\n />\n </div>\n ))}\n </div>\n )\n})\n"],
5
+ "mappings": ";AAiOM,SACE,KADF;AArNN,SAAS,MAAM,QAAQ,gBAAwD;AAC/E,SAAS,UAAU;AACnB,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAOK;AACP,SAAS,aAAa,mBAAmB;AAIzC,MAAM,SAAS,CAAC,SACd,SAAS,OAAO,SAAS,OAAO,SAAS,OAAO,SAAS;AAG3D,MAAM,QAAoF;AAAA,EACxF,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM,QAAQ,aAAa,OAAO,MAAM;AAAA,EACvE,EAAE,MAAM,KAAK,MAAM,QAAQ,KAAK,OAAO,QAAQ,aAAa,OAAO,QAAQ;AAAA,EAC3E,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,QAAQ,QAAQ,aAAa,OAAO,SAAS;AAAA,EAC5E,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,OAAO,QAAQ,aAAa,OAAO,OAAO;AAC1E;AAEA,MAAM,UAA2E;AAAA,EAC/E,EAAE,QAAQ,MAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,cAAc;AAAA,EAC7D,EAAE,QAAQ,MAAM,MAAM,QAAQ,KAAK,MAAM,QAAQ,cAAc;AAAA,EAC/D,EAAE,QAAQ,MAAM,MAAM,MAAM,KAAK,QAAQ,QAAQ,cAAc;AAAA,EAC/D,EAAE,QAAQ,MAAM,MAAM,QAAQ,KAAK,QAAQ,QAAQ,cAAc;AACnE;AAEA,MAAM,WAAW,YAAY;AAyCtB,MAAM,YAAY,KAAK,SAASA,WAAU;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmB;AACjB,QAAM,UAAU,OAAuB,IAAI;AAC3C,QAAM,UAAU,OAAuB,IAAI;AAE3C,QAAM,CAAC,SAAS,UAAU,IAAI,SAGpB,IAAI;AAEd,QAAM,QAAQ,UAAU,EAAE,GAAG,MAAM,GAAG,QAAQ,MAAM,IAAI;AACxD,QAAM,MAAM,SAAS,SAAS;AAE9B,QAAM,OAAO,MAAM,QAAQ,SAAS,cAAc,eAAe;AAEjE,QAAM,QAAQ,MAAM;AAClB,UAAM,IAAI,QAAQ;AAClB,UAAM,KAAK,QAAQ;AACnB,QAAI,CAAC,EAAG;AACR,MAAE,UAAU;AACZ,UAAM,OAAO,EAAE;AACf,QAAI,CAAC,QAAQ,CAAC,GAAI;AAClB,QAAI,KAAK,OAAO;AACd,iBAAW,EAAE,OAAO,KAAK,OAAO,OAAO,KAAK,MAAM,CAAC;AAAA,IACrD,OAAO;AAEL,YAAM,MAAM,KAAK,MAAM,IAAI,EAAE,MAAM,KAAK,EAAE,MAAM;AAChD,YAAM,MAAM,KAAK,MAAM,IAAI,EAAE,MAAM,KAAK,EAAE,MAAM;AAChD,YAAM,QAAQ,KAAK,MAAM,IAAI,EAAE,MAAM;AACrC,SAAG,MAAM,YAAY,eAAe,EAAE,OAAO,EAAE,gBAAgB,KAAK;AAAA,IACtE;AACA,UAAM,MAAM,KAAK,OAAO,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,GAAG,MAAM,EAAE,EAAE,EAAE,KAAK;AACxE,QAAI,QAAQ,EAAE,WAAW;AACvB,QAAE,YAAY;AACd,WAAK,OAAO,KAAK,MAAM;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,QAAQ,CAAC,OAA0B,SAAe;AACtD,QAAI,CAAC,YAAY,MAAM,WAAW,EAAG;AACrC,UAAM,QAAQ,KAAK,SAAS;AAC5B,QAAI,CAAC,MAAO;AACZ,UAAM,eAAe;AACrB,UAAM,gBAAgB;AACtB,SAAK,OAAO,KAAK,EAAE;AACnB,UAAM,cAAc,kBAAkB,MAAM,SAAS;AACrD,YAAQ,UAAU;AAAA,MAChB;AAAA,MACA,SAAS,EAAE,GAAG,MAAM,SAAS,GAAG,MAAM,QAAQ;AAAA,MAC9C;AAAA,MACA,OAAO,EAAE,MAAM,KAAK,MAAM,QAAQ,KAAK,OAAO;AAAA,MAC9C;AAAA,MACA,QAAQ,KAAK,OAAO,KAAK,EAAE;AAAA,MAC3B,MAAM;AAAA,MACN,SAAS;AAAA,MACT,WAAW;AAAA,IACb;AAAA,EACF;AAEA,QAAM,OAAO,CAAC,UAA6B;AACzC,UAAM,IAAI,QAAQ;AAClB,QAAI,CAAC,EAAG;AACR,QAAI,CAAC,EAAE,MAAM;AAEX,WAAK,SAAS,IAAI;AAClB,UAAI,QAAQ,QAAS,SAAQ,QAAQ,MAAM,aAAa;AAAA,IAC1D;AACA,UAAM,MAAM,MAAM,UAAU,EAAE,QAAQ,KAAK,EAAE,MAAM;AACnD,UAAM,MAAM,MAAM,UAAU,EAAE,QAAQ,KAAK,EAAE,MAAM;AAEnD,UAAM,OAAO,MAAM;AACnB,QAAI,OAAO,EAAE,IAAI,GAAG;AAClB,YAAM,SAAS,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,QAAQ,IAAI;AAC5E,QAAE,OAAO;AAAA,QACP,OAAO,OAAO;AAAA,QACd,OAAO,EAAE,MAAM,OAAO,MAAM,QAAQ,OAAO,OAAO;AAAA,QAClD,QAAQ,OAAO;AAAA,MACjB;AAAA,IACF,OAAO;AACL,QAAE,OACA,EAAE,SAAS,SACP,UAAU,EAAE,OAAO,EAAE,MAAM,QAAQ,IAAI,IAAI,EAAE,QAAQ,IAAI,IACzD,YAAY,EAAE,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,IAAI,IAAI,EAAE,QAAQ,IAAI;AAAA,IAC3E;AACA,MAAE,YAAY,KAAK,EAAE,sBAAsB,KAAK;AAAA,EAClD;AAEA,QAAM,MAAM,MAAM;AAChB,UAAM,IAAI,QAAQ;AAClB,YAAQ,UAAU;AAClB,QAAI,CAAC,EAAG;AACR,QAAI,EAAE,YAAY,KAAM,MAAK,EAAE,qBAAqB,EAAE,OAAO;AAC7D,UAAM,KAAK,QAAQ;AACnB,QAAI,IAAI;AACN,SAAG,MAAM,YAAY;AACrB,SAAG,MAAM,aAAa;AAAA,IACxB;AACA,QAAI,CAAC,EAAE,KAAM;AACb,eAAW,IAAI;AACf,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,CAAC,CAAC;AACd,SAAK,OAAO,KAAK,IAAI,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK;AAAA,EACjD;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW;AAAA,QACT;AAAA;AAAA,QAEA,CAAC,KAAK,SAAS;AAAA,QACf,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,OAAO;AAAA,MACT;AAAA,MACA,OAAO;AAAA,QACL,MAAM,GAAG,IAAI,IAAI,GAAG;AAAA,QACpB,KAAK,GAAG,IAAI,IAAI,GAAG;AAAA,QACnB,OAAO,GAAG,IAAI,IAAI,GAAG;AAAA,QACrB,QAAQ,GAAG,YAAY,KAAK,MAAM,MAAM,IAAI,GAAG;AAAA,MACjD;AAAA,MACA,eAAe,CAAC,UAAU,MAAM,OAAO,MAAM;AAAA,MAC7C,eAAe;AAAA,MACf,aAAa;AAAA,MACb,iBAAiB;AAAA,MACjB,eAAa,cAAc,KAAK,EAAE;AAAA,MAGlC;AAAA,6BAAC,SAAI,WAAW,GAAG,oCAAoC,CAAC,KAAK,SAAS,kBAAkB,GACtF;AAAA,8BAAC,aAAU,MAAM,OAAO,MAAM,CAAC,UAAU;AAAA,UAExC,KAAK,SAAS,SAAS,CAAC,KAAK,SAC5B;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,EAAE,YAAY,SAAS;AAAA,cAC9B,eAAY;AAAA;AAAA,UACd;AAAA,UAED,CAAC,KAAK,SACL,oBAAC,SAAI,WAAU,yFAAwF;AAAA,WAE3G;AAAA,QACC,SAAS,CAAC,KAAK;AAAA,QAEd;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,OAAO,YAAY,MAAM;AAAA,YAEjC;AAAA;AAAA,QACH;AAAA,QAID,YACC,QAAQ,IAAI,CAAC,EAAE,QAAQ,MAAM,KAAK,OAAO,MACvC;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YACV,OAAO,EAAE,MAAM,KAAK,QAAQ,aAAa,OAAO;AAAA,YAChD,eAAe,CAAC,UAAU,MAAM,OAAO,MAAM;AAAA,YAC7C,cAAY,mBAAmB,MAAM;AAAA,YACrC,eAAa,qBAAqB,MAAM;AAAA,YAExC,8BAAC,UAAK,WAAU,iFAAgF;AAAA;AAAA,UAP3F;AAAA,QAQP,CACD;AAAA,QACF,YACC,MAAM,IAAI,CAAC,EAAE,MAAM,MAAM,KAAK,QAAQ,OAAO,KAAK,MAChD;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YACV,OAAO,EAAE,MAAM,KAAK,QAAQ,aAAa,OAAO;AAAA,YAChD,eAAe,CAAC,UAAU,MAAM,OAAO,IAAI;AAAA,YAC3C,cAAY,4BAA4B,IAAI;AAAA,YAC5C,OAAM;AAAA,YACN,eAAa,sBAAsB,IAAI;AAAA,YAEvC;AAAA,cAAC;AAAA;AAAA,gBACC,WAAW;AAAA,kBACT;AAAA,kBACA,SAAS,OAAO,SAAS,MAAM,YAAY;AAAA,gBAC7C;AAAA;AAAA,YACF;AAAA;AAAA,UAbK;AAAA,QAcP,CACD;AAAA;AAAA;AAAA,EACL;AAEJ,CAAC;",
6
6
  "names": ["StageItem"]
7
7
  }