@namroodinc/liquid-editorial 0.1.7 → 0.1.9
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/AGENTS.md +2 -0
- package/README.md +1 -1
- package/UPGRADING.md +50 -0
- package/dist/chunks/Board-BLhB5_xx.cjs +2 -0
- package/dist/chunks/Board-BLhB5_xx.cjs.map +1 -0
- package/dist/chunks/Board-BTFSzn6c.js +239 -0
- package/dist/chunks/Board-BTFSzn6c.js.map +1 -0
- package/dist/chunks/EntryEditor-CcyYtH74.js +191 -0
- package/dist/chunks/EntryEditor-CcyYtH74.js.map +1 -0
- package/dist/chunks/EntryEditor-DIpc1iPF.cjs +2 -0
- package/dist/chunks/EntryEditor-DIpc1iPF.cjs.map +1 -0
- package/dist/chunks/{RichTextEditor-DC_0oN1s.cjs → RichTextEditor-BxtlbjXj.cjs} +2 -2
- package/dist/chunks/RichTextEditor-BxtlbjXj.cjs.map +1 -0
- package/dist/chunks/{RichTextEditor-CSpjsL1z.js → RichTextEditor-Dnx4i7bQ.js} +104 -93
- package/dist/chunks/RichTextEditor-Dnx4i7bQ.js.map +1 -0
- package/dist/chunks/mergeRefs-Bc3qS0Tt.cjs +2 -0
- package/dist/chunks/mergeRefs-Bc3qS0Tt.cjs.map +1 -0
- package/dist/chunks/mergeRefs-DGM402mE.js +10 -0
- package/dist/chunks/mergeRefs-DGM402mE.js.map +1 -0
- package/dist/components/AssetCard/index.cjs +2 -0
- package/dist/components/AssetCard/index.cjs.map +1 -0
- package/dist/components/AssetCard/index.d.cts +41 -0
- package/dist/components/AssetCard/index.d.cts.map +1 -0
- package/dist/components/AssetCard/index.d.ts +41 -0
- package/dist/components/AssetCard/index.d.ts.map +1 -0
- package/dist/components/AssetCard/index.js +159 -0
- package/dist/components/AssetCard/index.js.map +1 -0
- package/dist/components/AssetCard/style.css +204 -0
- package/dist/components/AssetGrid/index.cjs +1 -1
- package/dist/components/AssetGrid/index.cjs.map +1 -1
- package/dist/components/AssetGrid/index.d.cts +5 -30
- package/dist/components/AssetGrid/index.d.cts.map +1 -1
- package/dist/components/AssetGrid/index.d.ts +5 -30
- package/dist/components/AssetGrid/index.d.ts.map +1 -1
- package/dist/components/AssetGrid/index.js +20 -107
- package/dist/components/AssetGrid/index.js.map +1 -1
- package/dist/components/AssetGrid/style.css +0 -161
- package/dist/components/Board/index.cjs +1 -2
- package/dist/components/Board/index.d.cts.map +1 -1
- package/dist/components/Board/index.d.ts.map +1 -1
- package/dist/components/Board/index.js +1 -231
- package/dist/components/Board/keyboard.d.cts +7 -0
- package/dist/components/Board/keyboard.d.cts.map +1 -0
- package/dist/components/Board/keyboard.d.ts +7 -0
- package/dist/components/Board/keyboard.d.ts.map +1 -0
- package/dist/components/Board/keyboard.test.d.cts +2 -0
- package/dist/components/Board/keyboard.test.d.cts.map +1 -0
- package/dist/components/Board/keyboard.test.d.ts +2 -0
- package/dist/components/Board/keyboard.test.d.ts.map +1 -0
- package/dist/components/Board/moves.d.cts +5 -0
- package/dist/components/Board/moves.d.cts.map +1 -0
- package/dist/components/Board/moves.d.ts +5 -0
- package/dist/components/Board/moves.d.ts.map +1 -0
- package/dist/components/Board/moves.test.d.cts +2 -0
- package/dist/components/Board/moves.test.d.cts.map +1 -0
- package/dist/components/Board/moves.test.d.ts +2 -0
- package/dist/components/Board/moves.test.d.ts.map +1 -0
- package/dist/components/Button/index.cjs +1 -1
- package/dist/components/Button/index.cjs.map +1 -1
- package/dist/components/Button/index.js +1 -1
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Button/style.css +0 -16
- package/dist/components/DatePicker/index.cjs +1 -1
- package/dist/components/DatePicker/index.cjs.map +1 -1
- package/dist/components/DatePicker/index.d.cts.map +1 -1
- package/dist/components/DatePicker/index.d.ts.map +1 -1
- package/dist/components/DatePicker/index.js +39 -36
- package/dist/components/DatePicker/index.js.map +1 -1
- package/dist/components/Drawer/index.cjs +1 -1
- package/dist/components/Drawer/index.cjs.map +1 -1
- package/dist/components/Drawer/index.d.cts.map +1 -1
- package/dist/components/Drawer/index.d.ts.map +1 -1
- package/dist/components/Drawer/index.js +69 -62
- package/dist/components/Drawer/index.js.map +1 -1
- package/dist/components/EntryEditor/index.cjs +1 -1
- package/dist/components/EntryEditor/index.d.cts.map +1 -1
- package/dist/components/EntryEditor/index.d.ts.map +1 -1
- package/dist/components/EntryEditor/index.js +1 -1
- package/dist/components/EntryEditor/model.d.cts.map +1 -1
- package/dist/components/EntryEditor/model.d.ts.map +1 -1
- package/dist/components/Icon/style.css +18 -0
- package/dist/components/ImageCropper/index.cjs +1 -1
- package/dist/components/ImageCropper/index.cjs.map +1 -1
- package/dist/components/ImageCropper/index.d.cts +16 -1
- package/dist/components/ImageCropper/index.d.cts.map +1 -1
- package/dist/components/ImageCropper/index.d.ts +16 -1
- package/dist/components/ImageCropper/index.d.ts.map +1 -1
- package/dist/components/ImageCropper/index.js +143 -107
- package/dist/components/ImageCropper/index.js.map +1 -1
- package/dist/components/Pagination/index.cjs +1 -1
- package/dist/components/Pagination/index.cjs.map +1 -1
- package/dist/components/Pagination/index.js +1 -1
- package/dist/components/Pagination/index.js.map +1 -1
- package/dist/components/Pagination/style.css +0 -1
- package/dist/components/RichTextEditor/index.cjs +1 -1
- package/dist/components/RichTextEditor/index.d.cts.map +1 -1
- package/dist/components/RichTextEditor/index.d.ts.map +1 -1
- package/dist/components/RichTextEditor/index.js +1 -1
- package/dist/components/Select/index.cjs +1 -1
- package/dist/components/Select/index.cjs.map +1 -1
- package/dist/components/Select/index.d.cts.map +1 -1
- package/dist/components/Select/index.d.ts.map +1 -1
- package/dist/components/Select/index.js +32 -18
- package/dist/components/Select/index.js.map +1 -1
- package/dist/components/SortableList/index.cjs +1 -1
- package/dist/components/SortableList/index.cjs.map +1 -1
- package/dist/components/SortableList/index.d.cts.map +1 -1
- package/dist/components/SortableList/index.d.ts.map +1 -1
- package/dist/components/SortableList/index.js +49 -50
- package/dist/components/SortableList/index.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -33
- package/dist/internal/mergeRefs.d.cts +4 -0
- package/dist/internal/mergeRefs.d.cts.map +1 -0
- package/dist/internal/mergeRefs.d.ts +4 -0
- package/dist/internal/mergeRefs.d.ts.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/test/dragEvent.d.cts +3 -0
- package/dist/test/dragEvent.d.cts.map +1 -0
- package/dist/test/dragEvent.d.ts +3 -0
- package/dist/test/dragEvent.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/chunks/EntryEditor-C32lKKnV.cjs +0 -2
- package/dist/chunks/EntryEditor-C32lKKnV.cjs.map +0 -1
- package/dist/chunks/EntryEditor-xVuiqtFo.js +0 -181
- package/dist/chunks/EntryEditor-xVuiqtFo.js.map +0 -1
- package/dist/chunks/RichTextEditor-CSpjsL1z.js.map +0 -1
- package/dist/chunks/RichTextEditor-DC_0oN1s.cjs.map +0 -1
- package/dist/components/Board/index.cjs.map +0 -1
- package/dist/components/Board/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Drawer","forwardRef","rest","panelRef","useRef","scrimRef","closeRef","onCloseRef","onClose","titleId","useId","descriptionId","previousOpen","setPreviousOpen","useState","isOpen","exiting","setExiting","variant","useEffect","durations","duration","value","timeout","previous","focusOptions","selector","onKeyDown","event","focusable","first","last","overflow","portalContainer","createPortal","node","side","ref","description","size","className","title","Button","closeLabel","footer"],"sources":["../../../src/components/Drawer/index.tsx"],"sourcesContent":["import { forwardRef, useEffect, useId, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Button } from \"../Button\";\nimport \"./style.css\";\n\nexport type DrawerSide = \"end\" | \"start\" | \"bottom\";\nexport type DrawerSize = \"md\" | \"wide\";\n\nexport interface DrawerProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n isOpen: boolean;\n /** Escape, the scrim and the close button all call this. */\n onClose: () => void;\n title: ReactNode;\n description?: string;\n /** Which edge the panel is anchored to. */\n side?: DrawerSide;\n size?: DrawerSize;\n /** Navigation uses a compact, square panel and animates its exit. */\n variant?: \"default\" | \"navigation\";\n /** Accessible name of the dismiss button. */\n closeLabel?: string;\n /**\n * Defaults to document.body, covering the viewport. A navigation drawer\n * portaled into a positioned element, such as an app shell, stays inside it.\n */\n portalContainer?: HTMLElement | null;\n /** Actions, right-aligned. The confirming action goes last. */\n footer?: ReactNode;\n children?: ReactNode;\n}\n\n/**\n * A side panel for work that belongs beside the page rather than on top of it\n * — editing one record, a filter set, a revision history. Same frame, rules\n * and footer band as `Modal`; focus is trapped while open.\n */\nexport const Drawer = forwardRef<HTMLDivElement, DrawerProps>(function Drawer(\n {\n isOpen,\n onClose,\n title,\n description,\n side = \"end\",\n size = \"md\",\n variant = \"default\",\n closeLabel = \"Close\",\n portalContainer,\n footer,\n className,\n children,\n ...rest\n },\n ref,\n) {\n const panelRef = useRef<HTMLDivElement | null>(null);\n const scrimRef = useRef<HTMLDivElement | null>(null);\n const closeRef = useRef<HTMLButtonElement | null>(null);\n const onCloseRef = useRef(onClose);\n const titleId = useId();\n const descriptionId = useId();\n const [previousOpen, setPreviousOpen] = useState(isOpen);\n const [exiting, setExiting] = useState(false);\n\n if (previousOpen !== isOpen) {\n setPreviousOpen(isOpen);\n setExiting(!isOpen && variant === \"navigation\");\n }\n\n useEffect(() => {\n if (!exiting || isOpen) return;\n // Read the actual transition, including token overrides and reduced motion.\n const durations = getComputedStyle(scrimRef.current!).transitionDuration.split(\",\");\n const duration = Math.max(0, ...durations.map((value) =>\n parseFloat(value) * (value.trim().endsWith(\"ms\") ? 1 : 1000) || 0,\n ));\n const timeout = window.setTimeout(() => setExiting(false), duration);\n return () => window.clearTimeout(timeout);\n }, [exiting, isOpen]);\n\n useEffect(() => {\n onCloseRef.current = onClose;\n }, [onClose]);\n\n useEffect(() => {\n if (!isOpen) return;\n\n const previous = document.activeElement as HTMLElement | null;\n // Navigation starts off-screen; focusing it must not scroll the shell with it.\n const focusOptions = variant === \"navigation\" ? { preventScroll: true } : undefined;\n const selector = \":is(button, [href], input, select, textarea, [tabindex]):not(:disabled):not([tabindex='-1'])\";\n const initialFocus = variant === \"navigation\" ? closeRef.current : panelRef.current?.querySelector<HTMLElement>(selector);\n (initialFocus ?? panelRef.current)?.focus(focusOptions);\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n // A popup inside the panel (Menu, Select, DateField) claims its Escape.\n if (event.defaultPrevented) return;\n event.preventDefault();\n onCloseRef.current();\n return;\n }\n if (event.key !== \"Tab\") return;\n const focusable = Array.from(\n panelRef.current?.querySelectorAll<HTMLElement>(\n selector,\n ) ?? [],\n );\n if (focusable.length === 0) return;\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n if (event.shiftKey && (document.activeElement === first || !panelRef.current?.contains(document.activeElement))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (document.activeElement === last || !panelRef.current?.contains(document.activeElement))) {\n event.preventDefault();\n first.focus();\n }\n };\n\n // On window, so document-level popup handlers inside the panel run first.\n window.addEventListener(\"keydown\", onKeyDown);\n const overflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n return () => {\n window.removeEventListener(\"keydown\", onKeyDown);\n document.body.style.overflow = overflow;\n previous?.focus(focusOptions);\n };\n }, [isOpen, variant, portalContainer]);\n\n if ((!isOpen && !exiting) || typeof document === \"undefined\") return null;\n\n return createPortal(\n <div\n ref={(node) => {\n scrimRef.current = node;\n if (node) node.inert = !isOpen;\n }}\n className=\"ds-drawer-scrim\"\n data-side={side}\n data-variant={variant}\n data-contained={portalContainer ? \"\" : undefined}\n data-state={isOpen ? \"open\" : \"closed\"}\n aria-hidden={!isOpen || undefined}\n onTransitionEnd={(event) => {\n if (event.target === event.currentTarget && event.propertyName === \"opacity\" && !isOpen) setExiting(false);\n }}\n onMouseDown={(event) => isOpen && event.target === event.currentTarget && onCloseRef.current()}\n >\n <div\n {...rest}\n ref={(node) => {\n panelRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={rest[\"aria-label\"] ? undefined : (rest[\"aria-labelledby\"] ?? titleId)}\n aria-describedby={description ? descriptionId : undefined}\n tabIndex={-1}\n data-side={side}\n data-size={size}\n data-variant={variant}\n className={[\"ds-drawer\", className].filter(Boolean).join(\" \")}\n >\n <header className=\"ds-drawer__head\">\n <div className=\"ds-drawer__titles\">\n <h2 id={titleId} className=\"ds-drawer__title\">\n {title}\n </h2>\n {description && (\n <p id={descriptionId} className=\"ds-drawer__description\">\n {description}\n </p>\n )}\n </div>\n <Button\n ref={closeRef}\n variant=\"simple\"\n size={variant === \"navigation\" ? \"lg\" : \"sm\"}\n isIconOnly\n iconStart=\"x\"\n className={variant === \"navigation\" ? \"ds-drawer__close\" : undefined}\n aria-label={closeLabel}\n onClick={() => onCloseRef.current()}\n />\n </header>\n <div className=\"ds-drawer__body\">{children}</div>\n {footer && <footer className=\"ds-drawer__footer\">{footer}</footer>}\n </div>\n </div>,\n portalContainer ?? document.body,\n );\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["useDrawerExit","previousOpen","setPreviousOpen","useState","isOpen","exiting","setExiting","animated","useEffect","durations","scrimRef","duration","value","timeout","Drawer","forwardRef","rest","panelRef","useRef","closeRef","onCloseRef","onClose","titleId","useId","descriptionId","variant","previous","focusOptions","selector","onKeyDown","event","focusable","first","last","overflow","portalContainer","createPortal","node","side","mergeRefs","ref","description","size","className","title","Button","closeLabel","footer"],"sources":["../../../src/components/Drawer/index.tsx"],"sourcesContent":["import { forwardRef, useEffect, useId, useRef, useState, type HTMLAttributes, type ReactNode, type RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Button } from \"../Button\";\nimport { mergeRefs } from \"../../internal/mergeRefs\";\nimport \"./style.css\";\n\nexport type DrawerSide = \"end\" | \"start\" | \"bottom\";\nexport type DrawerSize = \"md\" | \"wide\";\n\nexport interface DrawerProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n isOpen: boolean;\n /** Escape, the scrim and the close button all call this. */\n onClose: () => void;\n title: ReactNode;\n description?: string;\n /** Which edge the panel is anchored to. */\n side?: DrawerSide;\n size?: DrawerSize;\n /** Navigation uses a compact, square panel and animates its exit. */\n variant?: \"default\" | \"navigation\";\n /** Accessible name of the dismiss button. */\n closeLabel?: string;\n /**\n * Defaults to document.body, covering the viewport. A navigation drawer\n * portaled into a positioned element, such as an app shell, stays inside it.\n */\n portalContainer?: HTMLElement | null;\n /** Actions, right-aligned. The confirming action goes last. */\n footer?: ReactNode;\n children?: ReactNode;\n}\n\nfunction useDrawerExit(isOpen: boolean, animated: boolean, scrimRef: RefObject<HTMLDivElement | null>) {\n const [previousOpen, setPreviousOpen] = useState(isOpen);\n const [exiting, setExiting] = useState(false);\n\n if (previousOpen !== isOpen) {\n setPreviousOpen(isOpen);\n setExiting(!isOpen && animated);\n }\n\n useEffect(() => {\n if (!exiting || isOpen) return;\n // Read the actual transition, including token overrides and reduced motion.\n const durations = getComputedStyle(scrimRef.current!).transitionDuration.split(\",\");\n const duration = Math.max(0, ...durations.map((value) =>\n parseFloat(value) * (value.trim().endsWith(\"ms\") ? 1 : 1000) || 0,\n ));\n const timeout = window.setTimeout(() => setExiting(false), duration);\n return () => window.clearTimeout(timeout);\n }, [exiting, isOpen, scrimRef]);\n\n return [exiting, setExiting] as const;\n}\n\n/**\n * A side panel for work that belongs beside the page rather than on top of it\n * — editing one record, a filter set, a revision history. Same frame, rules\n * and footer band as `Modal`; focus is trapped while open.\n */\nexport const Drawer = forwardRef<HTMLDivElement, DrawerProps>(function Drawer(\n {\n isOpen,\n onClose,\n title,\n description,\n side = \"end\",\n size = \"md\",\n variant = \"default\",\n closeLabel = \"Close\",\n portalContainer,\n footer,\n className,\n children,\n ...rest\n },\n ref,\n) {\n const panelRef = useRef<HTMLDivElement | null>(null);\n const scrimRef = useRef<HTMLDivElement | null>(null);\n const closeRef = useRef<HTMLButtonElement | null>(null);\n const onCloseRef = useRef(onClose);\n const titleId = useId();\n const descriptionId = useId();\n const [exiting, setExiting] = useDrawerExit(isOpen, variant === \"navigation\", scrimRef);\n\n useEffect(() => {\n onCloseRef.current = onClose;\n }, [onClose]);\n\n useEffect(() => {\n if (!isOpen) return;\n\n const previous = document.activeElement as HTMLElement | null;\n // Navigation starts off-screen; focusing it must not scroll the shell with it.\n const focusOptions = variant === \"navigation\" ? { preventScroll: true } : undefined;\n const selector = \":is(button, [href], input, select, textarea, [tabindex]):not(:disabled):not([tabindex='-1'])\";\n const initialFocus = variant === \"navigation\" ? closeRef.current : panelRef.current?.querySelector<HTMLElement>(selector);\n (initialFocus ?? panelRef.current)?.focus(focusOptions);\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n // A popup inside the panel (Menu, Select, DateField) claims its Escape.\n if (event.defaultPrevented) return;\n event.preventDefault();\n onCloseRef.current();\n return;\n }\n if (event.key !== \"Tab\") return;\n const focusable = Array.from(\n panelRef.current?.querySelectorAll<HTMLElement>(\n selector,\n ) ?? [],\n );\n if (focusable.length === 0) return;\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n if (event.shiftKey && (document.activeElement === first || !panelRef.current?.contains(document.activeElement))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (document.activeElement === last || !panelRef.current?.contains(document.activeElement))) {\n event.preventDefault();\n first.focus();\n }\n };\n\n // On window, so document-level popup handlers inside the panel run first.\n window.addEventListener(\"keydown\", onKeyDown);\n const overflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n return () => {\n window.removeEventListener(\"keydown\", onKeyDown);\n document.body.style.overflow = overflow;\n previous?.focus(focusOptions);\n };\n }, [isOpen, variant, portalContainer]);\n\n if ((!isOpen && !exiting) || typeof document === \"undefined\") return null;\n\n return createPortal(\n <div\n ref={(node) => {\n scrimRef.current = node;\n if (node) node.inert = !isOpen;\n }}\n className=\"ds-drawer-scrim\"\n data-side={side}\n data-variant={variant}\n data-contained={portalContainer ? \"\" : undefined}\n data-state={isOpen ? \"open\" : \"closed\"}\n aria-hidden={!isOpen || undefined}\n onTransitionEnd={(event) => {\n if (event.target === event.currentTarget && event.propertyName === \"opacity\" && !isOpen) setExiting(false);\n }}\n onMouseDown={(event) => isOpen && event.target === event.currentTarget && onCloseRef.current()}\n >\n <div\n {...rest}\n ref={mergeRefs(panelRef, ref)}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={rest[\"aria-label\"] ? undefined : (rest[\"aria-labelledby\"] ?? titleId)}\n aria-describedby={description ? descriptionId : undefined}\n tabIndex={-1}\n data-side={side}\n data-size={size}\n data-variant={variant}\n className={[\"ds-drawer\", className].filter(Boolean).join(\" \")}\n >\n <header className=\"ds-drawer__head\">\n <div className=\"ds-drawer__titles\">\n <h2 id={titleId} className=\"ds-drawer__title\">\n {title}\n </h2>\n {description && (\n <p id={descriptionId} className=\"ds-drawer__description\">\n {description}\n </p>\n )}\n </div>\n <Button\n ref={closeRef}\n variant=\"simple\"\n size={variant === \"navigation\" ? \"lg\" : \"sm\"}\n isIconOnly\n iconStart=\"x\"\n className={variant === \"navigation\" ? \"ds-drawer__close\" : undefined}\n aria-label={closeLabel}\n onClick={() => onCloseRef.current()}\n />\n </header>\n <div className=\"ds-drawer__body\">{children}</div>\n {footer && <footer className=\"ds-drawer__footer\">{footer}</footer>}\n </div>\n </div>,\n portalContainer ?? document.body,\n );\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require("../Field/index.cjs"),require("../TextField/index.cjs"),require("../EntryPicker/index.cjs"),require("../NumberField/index.cjs"),require("../Switch/index.cjs");const e=require("../../chunks/EntryEditor-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require("../Field/index.cjs"),require("../TextField/index.cjs"),require("../EntryPicker/index.cjs"),require("../NumberField/index.cjs"),require("../Switch/index.cjs");const e=require("../../chunks/EntryEditor-DIpc1iPF.cjs");exports.EntryEditor=e.t,exports.validateEntryValues=e.n;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/EntryEditor/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/EntryEditor/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAO7D,OAAO,EAA4D,KAAK,YAAY,EAAE,KAAK,WAAW,EAAwB,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAChK,OAAO,aAAa,CAAC;AAErB,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEjI,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,UAAU,CAAC;IACzG,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1C,sEAAsE;IACtE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AA6DD,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,YAAiB,EACjB,aAAa,EACb,WAAgB,EAChB,MAAW,EACX,UAAkB,EAClB,aAAqB,EACrB,SAAS,EACT,GAAG,IAAI,EACR,EAAE,gBAAgB,+BAgClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/EntryEditor/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/EntryEditor/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAO7D,OAAO,EAA4D,KAAK,YAAY,EAAE,KAAK,WAAW,EAAwB,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAChK,OAAO,aAAa,CAAC;AAErB,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEjI,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,UAAU,CAAC;IACzG,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1C,sEAAsE;IACtE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AA6DD,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,YAAiB,EACjB,aAAa,EACb,WAAgB,EAChB,MAAW,EACX,UAAkB,EAClB,aAAqB,EACrB,SAAS,EACT,GAAG,IAAI,EACR,EAAE,gBAAgB,+BAgClB"}
|
|
@@ -3,4 +3,4 @@ import "../TextField/index.js";
|
|
|
3
3
|
import "../EntryPicker/index.js";
|
|
4
4
|
import "../NumberField/index.js";
|
|
5
5
|
import "../Switch/index.js";
|
|
6
|
-
export { t as EntryEditor, n as validateEntryValues } from "../../chunks/EntryEditor-
|
|
6
|
+
export { t as EntryEditor, n as validateEntryValues } from "../../chunks/EntryEditor-CcyYtH74.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.cts","sourceRoot":"","sources":["../../../src/components/EntryEditor/model.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,CAC1C;IAAE,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,GACrG;IAAE,IAAI,EAAE,OAAO,GAAG,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CACtD,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AAC/C,MAAM,WAAW,cAAc;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AACnD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,GAAG,cAAc,GAAG,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;AAC3I,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC,CAAC;AACtE,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAa1D,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOnE;
|
|
1
|
+
{"version":3,"file":"model.d.cts","sourceRoot":"","sources":["../../../src/components/EntryEditor/model.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,CAC1C;IAAE,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,GACrG;IAAE,IAAI,EAAE,OAAO,GAAG,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CACtD,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AAC/C,MAAM,WAAW,cAAc;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AACnD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,GAAG,cAAc,GAAG,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;AAC3I,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC,CAAC;AACtE,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAa1D,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOnE;AAmDD,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CASrG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/components/EntryEditor/model.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,CAC1C;IAAE,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,GACrG;IAAE,IAAI,EAAE,OAAO,GAAG,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CACtD,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AAC/C,MAAM,WAAW,cAAc;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AACnD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,GAAG,cAAc,GAAG,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;AAC3I,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC,CAAC;AACtE,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAa1D,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOnE;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/components/EntryEditor/model.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,CAC1C;IAAE,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,GACrG;IAAE,IAAI,EAAE,OAAO,GAAG,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CACtD,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AAC/C,MAAM,WAAW,cAAc;IAAG,OAAO,EAAE,MAAM,CAAA;CAAE;AACnD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,GAAG,cAAc,GAAG,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;AAC3I,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC,CAAC;AACtE,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAa1D,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOnE;AAmDD,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CASrG"}
|
|
@@ -7,4 +7,22 @@
|
|
|
7
7
|
stroke-linecap: round;
|
|
8
8
|
stroke-linejoin: round;
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
/* Every pending spinner shares one motion. Reduced motion slows it rather
|
|
12
|
+
* than stopping it, so the wait still reads as progress. */
|
|
13
|
+
.ds-icon--spin {
|
|
14
|
+
animation: ds-spin 900ms linear infinite;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@keyframes ds-spin {
|
|
18
|
+
to {
|
|
19
|
+
transform: rotate(360deg);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (prefers-reduced-motion: reduce) {
|
|
24
|
+
.ds-icon--spin {
|
|
25
|
+
animation-duration: 2400ms;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
10
28
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../Button/index.cjs"),t=require("../ButtonGroup/index.cjs"),n=require("../Field/index.cjs"),r=require("../Grid/index.cjs"),i=require("../Slider/index.cjs");let a=require("react"),o=require("react/jsx-runtime");var s=1,c={
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../Button/index.cjs"),t=require("../ButtonGroup/index.cjs"),n=require("../Field/index.cjs"),r=require("../Grid/index.cjs"),i=require("../Slider/index.cjs");let a=require("react"),o=require("react/jsx-runtime");var s=1,c={zoom:1,rotation:0,x:.5,y:.5};function l(e,t,n){let r=[e,t];for(let e=Math.round(n/90)%4;e>0;e--)r=[1-r[1],r[0]];return r}function u(e,t,n,r){let i=n%180!=0,a=i?e.height:e.width,o=i?e.width:e.height;return{width:a,height:o,scale:Math.max(t.width/a,t.height/o)*r}}function d(e){let t=(e,t)=>e!=null&&Number.isFinite(e)?e:t,n=e=>Math.min(1,Math.max(0,t(e,.5))),r=(Math.round(t(e?.rotation,0)/90)*90%360+360)%360,[i,a]=l(n(e?.cx),n(e?.cy),r);return{zoom:t(e?.zoom,1),rotation:r,x:i,y:a}}var f=(0,a.forwardRef)(function({src:f,alt:p=``,aspect:m,maxZoom:h=3,rotatable:g=!0,onChange:_,defaultState:v,className:y,...b},x){let S=(0,a.useRef)(null),C=(0,a.useRef)(null),w=(0,a.useRef)(null),[T,E]=(0,a.useState)({width:0,height:0}),[D,O]=(0,a.useState)(null),[k,A]=(0,a.useState)(()=>d(v)),[j,M]=(0,a.useState)({src:f,aspect:m});(j.src!==f||j.aspect!==m)&&(M({src:f,aspect:m}),A(e=>j.src===f?{...c,rotation:e.rotation}:d(v)));let N=(0,a.useCallback)(e=>{let{naturalWidth:t,naturalHeight:n}=e;t&&n&&O(e=>e?.src===f&&e.width===t&&e.height===n?e:{src:f,width:t,height:n})},[f]);(0,a.useEffect)(()=>{let e=C.current;e?.complete&&N(e)},[N]),(0,a.useLayoutEffect)(()=>{let e=S.current;if(!e)return;let t=()=>{let t=e.clientWidth,n=e.clientHeight;E(e=>e.width===t&&e.height===n?e:{width:t,height:n})};t();let n=new ResizeObserver(t);return n.observe(e),()=>n.disconnect()},[m]);let P=D?.src===f&&T.width>0&&T.height>0?D:null,F=(0,a.useCallback)(e=>{let t=Math.min(Math.max(s,h),Math.max(s,e.zoom)),{x:n,y:r}=e;if(P){let i=u(P,T,e.rotation,t),a=Math.min(.5,T.width/i.scale/i.width/2),o=Math.min(.5,T.height/i.scale/i.height/2);n=Math.min(1-a,Math.max(a,n)),r=Math.min(1-o,Math.max(o,r))}return t===e.zoom&&n===e.x&&r===e.y?e:{...e,zoom:t,x:n,y:r}},[T,h,P]),I=(0,a.useMemo)(()=>F(k),[F,k]);(0,a.useEffect)(()=>{A(I)},[I]);let L=(0,a.useMemo)(()=>P?u(P,T,I.rotation,I.zoom):null,[P,T,I.rotation,I.zoom]),R=L?.scale??0,z=L?(.5-I.x)*L.width*R:0,B=L?(.5-I.y)*L.height*R:0,V=L?Math.max(0,(L.width*R-T.width)/2):0,H=L?Math.max(0,(L.height*R-T.height)/2):0,U=`Drag the picture to reposition it. Nothing is written until you save — ${V===0&&H===0?`at this zoom the whole picture is in frame.`:`the veiled edges are what gets trimmed.`}`;(0,a.useEffect)(()=>{if(!L)return;let e=Math.min(L.width,Math.round(T.width/L.scale)),t=Math.min(L.height,Math.round(T.height/L.scale)),[n,r]=l(I.x,I.y,360-I.rotation);_?.({x:Math.max(0,Math.min(L.width-e,Math.round(I.x*L.width-T.width/L.scale/2))),y:Math.max(0,Math.min(L.height-t,Math.round(I.y*L.height-T.height/L.scale/2))),width:e,height:t},{offsetX:z,offsetY:B,zoom:I.zoom,rotation:I.rotation,cx:n,cy:r})},[L,I,T,z,B]);let W=(e,t)=>A(n=>{if(!P)return n;let{width:r,height:i,scale:a}=u(P,T,n.rotation,n.zoom);return F({...n,x:n.x-e/(r*a),y:n.y-t/(i*a)})}),G=e=>A(t=>({...c,rotation:(t.rotation+e+360)%360})),K=e=>{e.button===0&&(w.current={pointerId:e.pointerId,x:e.clientX,y:e.clientY},e.currentTarget.setPointerCapture(e.pointerId))},q=e=>{let t=w.current;if(!t||t.pointerId!==e.pointerId)return;let n=e.clientX-t.x,r=e.clientY-t.y;w.current={...t,x:e.clientX,y:e.clientY},W(n,r)},J=e=>{w.current?.pointerId===e.pointerId&&(w.current=null,e.currentTarget.releasePointerCapture?.(e.pointerId))};return(0,o.jsxs)(r.Stack,{ref:x,className:[`ds-cropper`,y].filter(Boolean).join(` `),...b,children:[(0,o.jsxs)(`div`,{role:`application`,"aria-label":`Crop area — drag to reposition, arrow keys to nudge`,tabIndex:0,className:`ds-cropper__stage`,style:{"--cropper-aspect":String(m)},onPointerDown:K,onPointerMove:q,onPointerUp:J,onPointerCancel:J,onKeyDown:e=>{let t=e.shiftKey?24:8,n={ArrowLeft:[-t,0],ArrowRight:[t,0],ArrowUp:[0,-t],ArrowDown:[0,t]}[e.key];n&&(e.preventDefault(),W(n[0],n[1]))},children:[(0,o.jsx)(`img`,{ref:C,src:f,alt:p,draggable:!1,className:`ds-cropper__image`,onLoad:e=>N(e.currentTarget),style:{inlineSize:P?P.width*R:void 0,blockSize:P?P.height*R:void 0,transform:`translate(calc(-50% + ${z}px), calc(-50% + ${B}px)) rotate(${I.rotation}deg)`}}),(0,o.jsx)(`div`,{className:`ds-cropper__frame`,ref:S,"aria-hidden":`true`,children:(0,o.jsx)(`div`,{className:`ds-cropper__guides`})})]}),(0,o.jsx)(n.Field,{label:`Zoom`,hint:U,children:n=>(0,o.jsxs)(r.Stack,{direction:`horizontal`,gap:`md`,align:`center`,wrap:!0,children:[(0,o.jsx)(r.Stack,{style:{flex:`1 1 50%`,minInlineSize:0},children:(0,o.jsx)(i.Slider,{...n,min:s,max:Math.max(s,h),step:.01,value:I.zoom,suffix:`×`,onValueChange:e=>A(t=>F({...t,zoom:e}))})}),(0,o.jsxs)(t.ButtonGroup,{"aria-label":`Crop actions`,children:[g&&(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(e.Button,{size:`sm`,iconStart:`rotate-ccw`,"aria-label":`Rotate left`,onClick:()=>G(-90)}),(0,o.jsx)(e.Button,{size:`sm`,iconStart:`rotate-cw`,"aria-label":`Rotate right`,onClick:()=>G(90)})]}),(0,o.jsx)(e.Button,{size:`sm`,onClick:()=>A(c),children:`Reset`})]})]})})]})});async function p(e,t,{outputWidth:n,rotation:r=0,type:i=`image/png`,quality:a=.92}={}){let o=new Image;o.crossOrigin=`anonymous`,o.src=e,await o.decode();let s=r%180!=0,c=document.createElement(`canvas`);c.width=s?o.naturalHeight:o.naturalWidth,c.height=s?o.naturalWidth:o.naturalHeight;let l=c.getContext(`2d`);if(!l)throw Error(`Canvas 2D context unavailable`);l.translate(c.width/2,c.height/2),l.rotate(r*Math.PI/180),l.drawImage(o,-o.naturalWidth/2,-o.naturalHeight/2);let u=n??t.width,d=Math.round(t.height/t.width*u),f=document.createElement(`canvas`);f.width=u,f.height=d;let p=f.getContext(`2d`);if(!p)throw Error(`Canvas 2D context unavailable`);return p.drawImage(c,t.x,t.y,t.width,t.height,0,0,u,d),new Promise((e,t)=>{f.toBlob(n=>n?e(n):t(Error(`Could not encode the crop`)),i,a)})}exports.ImageCropper=f,exports.cropToBlob=p;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["forwardRef","useRef","useState","useCallback","useMemo","Stack","Field","Slider","ButtonGroup","_Fragment","Button","require_components_Button_index","require_components_ButtonGroup_index","require_components_Field_index","require_components_Grid_index","require_components_Slider_index","react","react_jsx_runtime","MIN_ZOOM","REST","ImageCropper","rest","frameRef","frame","setFrame","natural","setNatural","state","setState","dragRef","imageRef","prev","aspect","turn","by","adopt","img","src","quarter","view","baseScale","node","measure","observer","clamp","zoom","maxZoom","next","nextScale","limitX","limitY","offsetX","offsetY","bounded","scale","maxOffsetX","maxOffsetY","hint","width","height","onChange","onPointerDown","event","onPointerMove","drag","dx","dy","endDrag","nudge","className","step","move","props","rotatable","cropToBlob","image","rotation","turned","turnedContext","outputWidth","crop","canvas","context","blob","resolve","reject","type","quality"],"sources":["../../../src/components/ImageCropper/index.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type PointerEvent as ReactPointerEvent,\n} from \"react\";\nimport { Button } from \"../Button\";\nimport { ButtonGroup } from \"../ButtonGroup\";\nimport { Field } from \"../Field\";\nimport { Stack } from \"../Grid\";\nimport { Slider } from \"../Slider\";\nimport \"./style.css\";\n\nexport interface CropRect {\n /** Left edge of the crop, in the (rotated) image's own pixels. */\n x: number;\n /** Top edge of the crop, in the (rotated) image's own pixels. */\n y: number;\n width: number;\n height: number;\n}\n\nexport interface CropState {\n /** Pan, in stage pixels, from the centre. */\n offsetX: number;\n offsetY: number;\n /** 1 is \"cover the crop window exactly\". */\n zoom: number;\n /** Quarter turns applied to the picture, clockwise: 0, 90, 180 or 270. */\n rotation: number;\n}\n\nexport interface ImageCropperProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\"\n> {\n src: string;\n /** Describes the picture for people who cannot see the stage. */\n alt?: string;\n /** Required output shape, width / height. Must be positive and finite. */\n aspect: number;\n /** Highest allowed zoom. Past 3 the result is mush on most uploads. */\n maxZoom?: number;\n /** Offer the quarter-turn controls. */\n rotatable?: boolean;\n /** Fires on every pan, zoom, turn and reset, with the crop in image pixels. */\n onChange?: (crop: CropRect, state: CropState) => void;\n}\n\nconst MIN_ZOOM = 1;\nconst REST: CropState = { offsetX: 0, offsetY: 0, zoom: 1, rotation: 0 };\n\n/**\n * Pan-and-zoom cropping over a fixed frame. The frame never moves — the\n * picture does — and the parts that fall outside it stay visible under a\n * veil, so the reader can see what they are trimming away.\n * Drag with a pointer, or focus the stage and use the arrow keys.\n */\nexport const ImageCropper = forwardRef<HTMLDivElement, ImageCropperProps>(\n function ImageCropper(\n {\n src,\n alt = \"\",\n aspect,\n maxZoom = 3,\n rotatable = true,\n onChange,\n className,\n ...rest\n },\n ref,\n ) {\n const frameRef = useRef<HTMLDivElement | null>(null);\n const [frame, setFrame] = useState({ width: 0, height: 0 });\n const [natural, setNatural] = useState({ width: 0, height: 0 });\n const [state, setState] = useState<CropState>(REST);\n const dragRef = useRef<{ pointerId: number; x: number; y: number } | null>(\n null,\n );\n const imageRef = useRef<HTMLImageElement | null>(null);\n\n // The prop stays the source of truth when the caller drives the shape.\n useEffect(() => {\n setState((prev) => ({ ...REST, rotation: prev.rotation }));\n }, [aspect]);\n\n const turn = (by: number) =>\n setState((prev) => ({\n ...REST,\n rotation: (prev.rotation + by + 360) % 360,\n }));\n\n /** Take the picture's own pixels and start from a centred, unzoomed view. */\n const adopt = useCallback((img: HTMLImageElement) => {\n if (!img.naturalWidth) return;\n setNatural({ width: img.naturalWidth, height: img.naturalHeight });\n setState(REST);\n }, []);\n\n // A cached or server-rendered image can already be decoded before React\n // attaches onLoad, and then that event never fires.\n useEffect(() => {\n const img = imageRef.current;\n if (img?.complete) adopt(img);\n }, [adopt, src]);\n\n // After a quarter turn the picture presents its other side to the frame.\n const quarter = state.rotation % 180 !== 0;\n const view = {\n width: quarter ? natural.height : natural.width,\n height: quarter ? natural.width : natural.height,\n };\n\n // The scale at which the image exactly covers the frame; zoom multiplies it.\n const baseScale =\n view.width && frame.width\n ? Math.max(frame.width / view.width, frame.height / view.height)\n : 0;\n\n useLayoutEffect(() => {\n const node = frameRef.current;\n if (!node) return;\n const measure = () =>\n setFrame({ width: node.clientWidth, height: node.clientHeight });\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(node);\n return () => observer.disconnect();\n }, [aspect]);\n\n const clamp = useCallback(\n (next: CropState): CropState => {\n const zoom = Math.min(\n Math.max(MIN_ZOOM, maxZoom),\n Math.max(MIN_ZOOM, next.zoom),\n );\n const nextScale = baseScale * zoom;\n const limitX = nextScale\n ? Math.max(0, (view.width * nextScale - frame.width) / 2)\n : 0;\n const limitY = nextScale\n ? Math.max(0, (view.height * nextScale - frame.height) / 2)\n : 0;\n const offsetX = Math.min(limitX, Math.max(-limitX, next.offsetX));\n const offsetY = Math.min(limitY, Math.max(-limitY, next.offsetY));\n if (\n zoom === next.zoom &&\n offsetX === next.offsetX &&\n offsetY === next.offsetY\n )\n return next;\n return { ...next, zoom, offsetX, offsetY };\n },\n [baseScale, maxZoom, view.height, view.width, frame.height, frame.width],\n );\n\n // Keep the rendered transform bounded when the frame or zoom limit changes too.\n const bounded = useMemo(() => clamp(state), [clamp, state]);\n useEffect(() => {\n setState(bounded);\n }, [bounded]);\n const scale = baseScale * bounded.zoom;\n const maxOffsetX = scale\n ? Math.max(0, (view.width * scale - frame.width) / 2)\n : 0;\n const maxOffsetY = scale\n ? Math.max(0, (view.height * scale - frame.height) / 2)\n : 0;\n const hint = `Drag the picture to reposition it. Nothing is written until you save — ${\n maxOffsetX === 0 && maxOffsetY === 0\n ? \"at this zoom the whole picture is in frame.\"\n : \"the veiled edges are what gets trimmed.\"\n }`;\n\n useEffect(() => {\n if (!scale || !frame.width) return;\n const width = Math.min(view.width, Math.round(frame.width / scale));\n const height = Math.min(view.height, Math.round(frame.height / scale));\n onChange?.(\n {\n x: Math.max(\n 0,\n Math.min(\n view.width - width,\n Math.round(\n view.width / 2 -\n bounded.offsetX / scale -\n frame.width / scale / 2,\n ),\n ),\n ),\n y: Math.max(\n 0,\n Math.min(\n view.height - height,\n Math.round(\n view.height / 2 -\n bounded.offsetY / scale -\n frame.height / scale / 2,\n ),\n ),\n ),\n width,\n height,\n },\n bounded,\n );\n // onChange is intentionally not a dependency: callers pass inline handlers.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [scale, frame.width, frame.height, view.width, view.height, bounded]);\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return;\n dragRef.current = {\n pointerId: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n };\n event.currentTarget.setPointerCapture(event.pointerId);\n };\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== event.pointerId) return;\n const dx = event.clientX - drag.x;\n const dy = event.clientY - drag.y;\n dragRef.current = { ...drag, x: event.clientX, y: event.clientY };\n setState((prev) =>\n clamp({\n ...prev,\n offsetX: prev.offsetX + dx,\n offsetY: prev.offsetY + dy,\n }),\n );\n };\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (dragRef.current?.pointerId !== event.pointerId) return;\n dragRef.current = null;\n event.currentTarget.releasePointerCapture?.(event.pointerId);\n };\n\n const nudge = (dx: number, dy: number) =>\n setState((prev) =>\n clamp({\n ...prev,\n offsetX: prev.offsetX + dx,\n offsetY: prev.offsetY + dy,\n }),\n );\n\n return (\n <Stack\n ref={ref}\n className={[\"ds-cropper\", className].filter(Boolean).join(\" \")}\n {...rest}\n >\n <div\n role=\"application\"\n aria-label=\"Crop area — drag to reposition, arrow keys to nudge\"\n tabIndex={0}\n className=\"ds-cropper__stage\"\n style={{ \"--cropper-aspect\": String(aspect) } as CSSProperties}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={(event) => {\n const step = event.shiftKey ? 24 : 8;\n const moves: Record<string, [number, number]> = {\n ArrowLeft: [-step, 0],\n ArrowRight: [step, 0],\n ArrowUp: [0, -step],\n ArrowDown: [0, step],\n };\n const move = moves[event.key];\n if (!move) return;\n event.preventDefault();\n nudge(move[0], move[1]);\n }}\n >\n <img\n ref={imageRef}\n src={src}\n alt={alt}\n draggable={false}\n className=\"ds-cropper__image\"\n onLoad={(event) => adopt(event.currentTarget)}\n style={{\n inlineSize: natural.width * scale || undefined,\n blockSize: natural.height * scale || undefined,\n transform: `translate(calc(-50% + ${bounded.offsetX}px), calc(-50% + ${bounded.offsetY}px)) rotate(${bounded.rotation}deg)`,\n }}\n />\n {/* The window: everything outside it is veiled, not hidden. */}\n <div className=\"ds-cropper__frame\" ref={frameRef} aria-hidden=\"true\">\n <div className=\"ds-cropper__guides\" />\n </div>\n </div>\n\n <Field label=\"Zoom\" hint={hint}>\n {(props) => (\n <Stack direction=\"horizontal\" gap=\"md\" align=\"center\" wrap>\n <Stack style={{ flex: \"1 1 50%\", minInlineSize: 0 }}>\n <Slider\n {...props}\n min={MIN_ZOOM}\n max={Math.max(MIN_ZOOM, maxZoom)}\n step={0.01}\n value={bounded.zoom}\n suffix=\"×\"\n onValueChange={(zoom) =>\n setState((prev) => clamp({ ...prev, zoom }))\n }\n />\n </Stack>\n <ButtonGroup aria-label=\"Crop actions\">\n {rotatable && (\n <>\n <Button\n size=\"sm\"\n iconStart=\"rotate-ccw\"\n aria-label=\"Rotate left\"\n onClick={() => turn(-90)}\n />\n <Button\n size=\"sm\"\n iconStart=\"rotate-cw\"\n aria-label=\"Rotate right\"\n onClick={() => turn(90)}\n />\n </>\n )}\n <Button size=\"sm\" onClick={() => setState(REST)}>\n Reset\n </Button>\n </ButtonGroup>\n </Stack>\n )}\n </Field>\n </Stack>\n );\n },\n);\n\nexport interface CropToBlobOptions {\n /** Width of the output in pixels; the height follows the crop's aspect. */\n outputWidth?: number;\n /** Quarter turns applied before cropping — pass `state.rotation`. */\n rotation?: number;\n type?: \"image/png\" | \"image/jpeg\" | \"image/webp\";\n quality?: number;\n}\n\n/**\n * Renders a `CropRect` to an image blob in the browser. Same-origin or\n * CORS-enabled sources only — a tainted canvas cannot be exported.\n */\nexport async function cropToBlob(\n src: string,\n crop: CropRect,\n {\n outputWidth,\n rotation = 0,\n type = \"image/png\",\n quality = 0.92,\n }: CropToBlobOptions = {},\n): Promise<Blob> {\n const image = new Image();\n image.crossOrigin = \"anonymous\";\n image.src = src;\n await image.decode();\n\n // The crop is expressed against the turned picture, so turn it first.\n const quarter = rotation % 180 !== 0;\n const turned = document.createElement(\"canvas\");\n turned.width = quarter ? image.naturalHeight : image.naturalWidth;\n turned.height = quarter ? image.naturalWidth : image.naturalHeight;\n const turnedContext = turned.getContext(\"2d\");\n if (!turnedContext) throw new Error(\"Canvas 2D context unavailable\");\n turnedContext.translate(turned.width / 2, turned.height / 2);\n turnedContext.rotate((rotation * Math.PI) / 180);\n turnedContext.drawImage(\n image,\n -image.naturalWidth / 2,\n -image.naturalHeight / 2,\n );\n\n const width = outputWidth ?? crop.width;\n const height = Math.round((crop.height / crop.width) * width);\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\");\n if (!context) throw new Error(\"Canvas 2D context unavailable\");\n context.drawImage(\n turned,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n 0,\n 0,\n width,\n height,\n );\n\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) =>\n blob ? resolve(blob) : reject(new Error(\"Could not encode the crop\")),\n type,\n quality,\n );\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["forwardRef","useRef","useState","useCallback","useMemo","Stack","Field","Slider","ButtonGroup","_Fragment","Button","require_components_Button_index","require_components_ButtonGroup_index","require_components_Field_index","require_components_Grid_index","require_components_Slider_index","react","react_jsx_runtime","MIN_ZOOM","CENTRED","turnPoint","point","x","y","turns","degrees","project","quarter","rotation","width","picture","height","frame","zoom","seed","finite","value","fallback","fraction","saved","ImageCropper","rest","frameRef","imageRef","dragRef","setFrame","natural","setNatural","framing","setFraming","defaultState","shown","setShown","src","aspect","prev","adopt","img","node","measure","observer","clamp","maxZoom","next","view","halfX","halfY","bounded","scale","offsetX","offsetY","maxOffsetX","maxOffsetY","hint","cx","cy","onChange","pan","at","dx","dy","turn","by","onPointerDown","event","onPointerMove","drag","endDrag","className","step","move","props","rotatable","cropToBlob","image","turned","turnedContext","outputWidth","crop","canvas","context","blob","resolve","reject","type","quality"],"sources":["../../../src/components/ImageCropper/index.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type PointerEvent as ReactPointerEvent,\n} from \"react\";\nimport { Button } from \"../Button\";\nimport { ButtonGroup } from \"../ButtonGroup\";\nimport { Field } from \"../Field\";\nimport { Stack } from \"../Grid\";\nimport { Slider } from \"../Slider\";\nimport \"./style.css\";\n\nexport interface CropRect {\n /** Left edge of the crop, in the (rotated) image's own pixels. */\n x: number;\n /** Top edge of the crop, in the (rotated) image's own pixels. */\n y: number;\n width: number;\n height: number;\n}\n\nexport interface CropState {\n /** Pan, in stage pixels, from the centre. Follows the stage size, so save `cx`/`cy`. */\n offsetX: number;\n offsetY: number;\n /** 1 is \"cover the crop window exactly\". */\n zoom: number;\n /** Quarter turns applied to the picture, clockwise: 0, 90, 180 or 270. */\n rotation: number;\n /** Crop centre as fractions of the unrotated source image. */\n cx: number;\n cy: number;\n}\n\n/** A saved selection. Every emitted `CropState` is one, so it can be passed straight back. */\nexport interface ImageCropperDefaultState {\n /** Zoom at initialization; clamped to the supported range. */\n zoom: number;\n /** Crop centre as fractions of the unrotated source image. */\n cx: number;\n cy: number;\n /** Clockwise quarter turns, snapped to 0, 90, 180 or 270. Defaults to 0. */\n rotation?: number;\n}\n\nexport interface ImageCropperProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\"\n> {\n src: string;\n /** Uncontrolled initial selection. Applied again whenever `src` changes. */\n defaultState?: ImageCropperDefaultState;\n /** Describes the picture for people who cannot see the stage. */\n alt?: string;\n /** Required output shape, width / height. Must be positive and finite. */\n aspect: number;\n /** Highest allowed zoom. Past 3 the result is mush on most uploads. */\n maxZoom?: number;\n /** Offer the quarter-turn controls. */\n rotatable?: boolean;\n /** Fires on every pan, zoom, turn and reset, with the crop in image pixels. */\n onChange?: (crop: CropRect, state: CropState) => void;\n}\n\ninterface Size {\n width: number;\n height: number;\n}\n\n/** The selection as the engine keeps it, independent of the stage's size. */\ninterface Framing {\n zoom: number;\n rotation: number;\n /** Frame centre as fractions of the picture as turned. */\n x: number;\n y: number;\n}\n\nconst MIN_ZOOM = 1;\nconst CENTRED: Framing = { zoom: 1, rotation: 0, x: 0.5, y: 0.5 };\n\n/** Carries a point, in fractions of a picture, through clockwise quarter turns of it. */\nfunction turnPoint(x: number, y: number, degrees: number): [number, number] {\n let point: [number, number] = [x, y];\n for (let turns = Math.round(degrees / 90) % 4; turns > 0; turns--) {\n point = [1 - point[1], point[0]];\n }\n return point;\n}\n\n/** The turned picture's size, and the stage scale at which it covers the frame at this zoom. */\nfunction project(picture: Size, frame: Size, rotation: number, zoom: number) {\n const quarter = rotation % 180 !== 0;\n const width = quarter ? picture.height : picture.width;\n const height = quarter ? picture.width : picture.height;\n return {\n width,\n height,\n scale: Math.max(frame.width / width, frame.height / height) * zoom,\n };\n}\n\n/** Reads a saved selection, falling back to the centre for anything unusable. */\nfunction seed(saved: ImageCropperDefaultState | undefined): Framing {\n const finite = (value: number | undefined, fallback: number) =>\n value != null && Number.isFinite(value) ? value : fallback;\n const fraction = (value: number | undefined) =>\n Math.min(1, Math.max(0, finite(value, 0.5)));\n const rotation =\n (((Math.round(finite(saved?.rotation, 0) / 90) * 90) % 360) + 360) % 360;\n const [x, y] = turnPoint(fraction(saved?.cx), fraction(saved?.cy), rotation);\n return { zoom: finite(saved?.zoom, 1), rotation, x, y };\n}\n\n/**\n * Pan-and-zoom cropping over a fixed frame. The frame never moves — the\n * picture does — and the parts that fall outside it stay visible under a\n * veil, so the reader can see what they are trimming away.\n * Drag with a pointer, or focus the stage and use the arrow keys.\n */\nexport const ImageCropper = forwardRef<HTMLDivElement, ImageCropperProps>(\n function ImageCropper(\n {\n src,\n alt = \"\",\n aspect,\n maxZoom = 3,\n rotatable = true,\n onChange,\n defaultState,\n className,\n ...rest\n },\n ref,\n ) {\n const frameRef = useRef<HTMLDivElement | null>(null);\n const imageRef = useRef<HTMLImageElement | null>(null);\n const dragRef = useRef<{ pointerId: number; x: number; y: number } | null>(\n null,\n );\n const [frame, setFrame] = useState<Size>({ width: 0, height: 0 });\n const [natural, setNatural] = useState<(Size & { src: string }) | null>(\n null,\n );\n const [framing, setFraming] = useState(() => seed(defaultState));\n const [shown, setShown] = useState({ src, aspect });\n\n // A new source starts again from the saved selection; a new shape starts\n // centred and keeps the turn. Adjusting while rendering means no commit\n // pairs the previous selection with the new picture or frame.\n if (shown.src !== src || shown.aspect !== aspect) {\n setShown({ src, aspect });\n setFraming((prev) =>\n shown.src !== src\n ? seed(defaultState)\n : { ...CENTRED, rotation: prev.rotation },\n );\n }\n\n /** Take the picture's own pixels. Repeated load events for it change nothing. */\n const adopt = useCallback(\n (img: HTMLImageElement) => {\n const { naturalWidth: width, naturalHeight: height } = img;\n if (!width || !height) return;\n setNatural((prev) =>\n prev?.src === src && prev.width === width && prev.height === height\n ? prev\n : { src, width, height },\n );\n },\n [src],\n );\n\n // A cached or server-rendered image can already be decoded before React\n // attaches onLoad, and then that event never fires.\n useEffect(() => {\n const img = imageRef.current;\n if (img?.complete) adopt(img);\n }, [adopt]);\n\n useLayoutEffect(() => {\n const node = frameRef.current;\n if (!node) return;\n const measure = () => {\n const width = node.clientWidth;\n const height = node.clientHeight;\n setFrame((prev) =>\n prev.width === width && prev.height === height\n ? prev\n : { width, height },\n );\n };\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(node);\n return () => observer.disconnect();\n }, [aspect]);\n\n // Nothing is measured or reported until this source has loaded into a sized frame.\n const picture =\n natural?.src === src && frame.width > 0 && frame.height > 0\n ? natural\n : null;\n\n const clamp = useCallback(\n (next: Framing): Framing => {\n const zoom = Math.min(\n Math.max(MIN_ZOOM, maxZoom),\n Math.max(MIN_ZOOM, next.zoom),\n );\n let { x, y } = next;\n if (picture) {\n // The frame's half-width and half-height, as shares of the picture.\n const view = project(picture, frame, next.rotation, zoom);\n const halfX = Math.min(0.5, frame.width / view.scale / view.width / 2);\n const halfY = Math.min(0.5, frame.height / view.scale / view.height / 2);\n x = Math.min(1 - halfX, Math.max(halfX, x));\n y = Math.min(1 - halfY, Math.max(halfY, y));\n }\n if (zoom === next.zoom && x === next.x && y === next.y) return next;\n return { ...next, zoom, x, y };\n },\n [frame, maxZoom, picture],\n );\n\n // Keep the rendered transform bounded when the frame or zoom limit changes too.\n const bounded = useMemo(() => clamp(framing), [clamp, framing]);\n useEffect(() => {\n setFraming(bounded);\n }, [bounded]);\n\n const view = useMemo(\n () =>\n picture\n ? project(picture, frame, bounded.rotation, bounded.zoom)\n : null,\n [picture, frame, bounded.rotation, bounded.zoom],\n );\n const scale = view?.scale ?? 0;\n const offsetX = view ? (0.5 - bounded.x) * view.width * scale : 0;\n const offsetY = view ? (0.5 - bounded.y) * view.height * scale : 0;\n const maxOffsetX = view\n ? Math.max(0, (view.width * scale - frame.width) / 2)\n : 0;\n const maxOffsetY = view\n ? Math.max(0, (view.height * scale - frame.height) / 2)\n : 0;\n const hint = `Drag the picture to reposition it. Nothing is written until you save — ${\n maxOffsetX === 0 && maxOffsetY === 0\n ? \"at this zoom the whole picture is in frame.\"\n : \"the veiled edges are what gets trimmed.\"\n }`;\n\n // A passive effect, so a parent that re-renders on every report never\n // delays the frame that paints a drag.\n useEffect(() => {\n if (!view) return;\n const width = Math.min(view.width, Math.round(frame.width / view.scale));\n const height = Math.min(\n view.height,\n Math.round(frame.height / view.scale),\n );\n const [cx, cy] = turnPoint(bounded.x, bounded.y, 360 - bounded.rotation);\n onChange?.(\n {\n x: Math.max(\n 0,\n Math.min(\n view.width - width,\n Math.round(\n bounded.x * view.width - frame.width / view.scale / 2,\n ),\n ),\n ),\n y: Math.max(\n 0,\n Math.min(\n view.height - height,\n Math.round(\n bounded.y * view.height - frame.height / view.scale / 2,\n ),\n ),\n ),\n width,\n height,\n },\n {\n offsetX,\n offsetY,\n zoom: bounded.zoom,\n rotation: bounded.rotation,\n cx,\n cy,\n },\n );\n // onChange is intentionally not a dependency: callers pass inline handlers.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [view, bounded, frame, offsetX, offsetY]);\n\n /** Move the picture by stage pixels; the frame centre travels the other way. */\n const pan = (dx: number, dy: number) =>\n setFraming((prev) => {\n if (!picture) return prev;\n const { width, height, scale: at } = project(\n picture,\n frame,\n prev.rotation,\n prev.zoom,\n );\n return clamp({\n ...prev,\n x: prev.x - dx / (width * at),\n y: prev.y - dy / (height * at),\n });\n });\n\n const turn = (by: number) =>\n setFraming((prev) => ({\n ...CENTRED,\n rotation: (prev.rotation + by + 360) % 360,\n }));\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return;\n dragRef.current = {\n pointerId: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n };\n event.currentTarget.setPointerCapture(event.pointerId);\n };\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== event.pointerId) return;\n const dx = event.clientX - drag.x;\n const dy = event.clientY - drag.y;\n dragRef.current = { ...drag, x: event.clientX, y: event.clientY };\n pan(dx, dy);\n };\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (dragRef.current?.pointerId !== event.pointerId) return;\n dragRef.current = null;\n event.currentTarget.releasePointerCapture?.(event.pointerId);\n };\n\n return (\n <Stack\n ref={ref}\n className={[\"ds-cropper\", className].filter(Boolean).join(\" \")}\n {...rest}\n >\n <div\n role=\"application\"\n aria-label=\"Crop area — drag to reposition, arrow keys to nudge\"\n tabIndex={0}\n className=\"ds-cropper__stage\"\n style={{ \"--cropper-aspect\": String(aspect) } as CSSProperties}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={(event) => {\n const step = event.shiftKey ? 24 : 8;\n const moves: Record<string, [number, number]> = {\n ArrowLeft: [-step, 0],\n ArrowRight: [step, 0],\n ArrowUp: [0, -step],\n ArrowDown: [0, step],\n };\n const move = moves[event.key];\n if (!move) return;\n event.preventDefault();\n pan(move[0], move[1]);\n }}\n >\n <img\n ref={imageRef}\n src={src}\n alt={alt}\n draggable={false}\n className=\"ds-cropper__image\"\n onLoad={(event) => adopt(event.currentTarget)}\n style={{\n inlineSize: picture ? picture.width * scale : undefined,\n blockSize: picture ? picture.height * scale : undefined,\n transform: `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px)) rotate(${bounded.rotation}deg)`,\n }}\n />\n {/* The window: everything outside it is veiled, not hidden. */}\n <div className=\"ds-cropper__frame\" ref={frameRef} aria-hidden=\"true\">\n <div className=\"ds-cropper__guides\" />\n </div>\n </div>\n\n <Field label=\"Zoom\" hint={hint}>\n {(props) => (\n <Stack direction=\"horizontal\" gap=\"md\" align=\"center\" wrap>\n <Stack style={{ flex: \"1 1 50%\", minInlineSize: 0 }}>\n <Slider\n {...props}\n min={MIN_ZOOM}\n max={Math.max(MIN_ZOOM, maxZoom)}\n step={0.01}\n value={bounded.zoom}\n suffix=\"×\"\n onValueChange={(zoom) =>\n setFraming((prev) => clamp({ ...prev, zoom }))\n }\n />\n </Stack>\n <ButtonGroup aria-label=\"Crop actions\">\n {rotatable && (\n <>\n <Button\n size=\"sm\"\n iconStart=\"rotate-ccw\"\n aria-label=\"Rotate left\"\n onClick={() => turn(-90)}\n />\n <Button\n size=\"sm\"\n iconStart=\"rotate-cw\"\n aria-label=\"Rotate right\"\n onClick={() => turn(90)}\n />\n </>\n )}\n <Button size=\"sm\" onClick={() => setFraming(CENTRED)}>\n Reset\n </Button>\n </ButtonGroup>\n </Stack>\n )}\n </Field>\n </Stack>\n );\n },\n);\n\nexport interface CropToBlobOptions {\n /** Width of the output in pixels; the height follows the crop's aspect. */\n outputWidth?: number;\n /** Quarter turns applied before cropping — pass `state.rotation`. */\n rotation?: number;\n type?: \"image/png\" | \"image/jpeg\" | \"image/webp\";\n quality?: number;\n}\n\n/**\n * Renders a `CropRect` to an image blob in the browser. Same-origin or\n * CORS-enabled sources only — a tainted canvas cannot be exported.\n */\nexport async function cropToBlob(\n src: string,\n crop: CropRect,\n {\n outputWidth,\n rotation = 0,\n type = \"image/png\",\n quality = 0.92,\n }: CropToBlobOptions = {},\n): Promise<Blob> {\n const image = new Image();\n image.crossOrigin = \"anonymous\";\n image.src = src;\n await image.decode();\n\n // The crop is expressed against the turned picture, so turn it first.\n const quarter = rotation % 180 !== 0;\n const turned = document.createElement(\"canvas\");\n turned.width = quarter ? image.naturalHeight : image.naturalWidth;\n turned.height = quarter ? image.naturalWidth : image.naturalHeight;\n const turnedContext = turned.getContext(\"2d\");\n if (!turnedContext) throw new Error(\"Canvas 2D context unavailable\");\n turnedContext.translate(turned.width / 2, turned.height / 2);\n turnedContext.rotate((rotation * Math.PI) / 180);\n turnedContext.drawImage(\n image,\n -image.naturalWidth / 2,\n -image.naturalHeight / 2,\n );\n\n const width = outputWidth ?? crop.width;\n const height = Math.round((crop.height / crop.width) * width);\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\");\n if (!context) throw new Error(\"Canvas 2D context unavailable\");\n context.drawImage(\n turned,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n 0,\n 0,\n width,\n height,\n );\n\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) =>\n blob ? resolve(blob) : reject(new Error(\"Could not encode the crop\")),\n type,\n quality,\n );\n });\n}\n"],"mappings":"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"}
|
|
@@ -8,16 +8,31 @@ export interface CropRect {
|
|
|
8
8
|
height: number;
|
|
9
9
|
}
|
|
10
10
|
export interface CropState {
|
|
11
|
-
/** Pan, in stage pixels, from the centre. */
|
|
11
|
+
/** Pan, in stage pixels, from the centre. Follows the stage size, so save `cx`/`cy`. */
|
|
12
12
|
offsetX: number;
|
|
13
13
|
offsetY: number;
|
|
14
14
|
/** 1 is "cover the crop window exactly". */
|
|
15
15
|
zoom: number;
|
|
16
16
|
/** Quarter turns applied to the picture, clockwise: 0, 90, 180 or 270. */
|
|
17
17
|
rotation: number;
|
|
18
|
+
/** Crop centre as fractions of the unrotated source image. */
|
|
19
|
+
cx: number;
|
|
20
|
+
cy: number;
|
|
21
|
+
}
|
|
22
|
+
/** A saved selection. Every emitted `CropState` is one, so it can be passed straight back. */
|
|
23
|
+
export interface ImageCropperDefaultState {
|
|
24
|
+
/** Zoom at initialization; clamped to the supported range. */
|
|
25
|
+
zoom: number;
|
|
26
|
+
/** Crop centre as fractions of the unrotated source image. */
|
|
27
|
+
cx: number;
|
|
28
|
+
cy: number;
|
|
29
|
+
/** Clockwise quarter turns, snapped to 0, 90, 180 or 270. Defaults to 0. */
|
|
30
|
+
rotation?: number;
|
|
18
31
|
}
|
|
19
32
|
export interface ImageCropperProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
20
33
|
src: string;
|
|
34
|
+
/** Uncontrolled initial selection. Applied again whenever `src` changes. */
|
|
35
|
+
defaultState?: ImageCropperDefaultState;
|
|
21
36
|
/** Describes the picture for people who cannot see the stage. */
|
|
22
37
|
alt?: string;
|
|
23
38
|
/** Required output shape, width / height. Must be positive and finite. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/ImageCropper/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EAEpB,MAAM,OAAO,CAAC;AAMf,OAAO,aAAa,CAAC;AAErB,MAAM,WAAW,QAAQ;IACvB,kEAAkE;IAClE,CAAC,EAAE,MAAM,CAAC;IACV,iEAAiE;IACjE,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/ImageCropper/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EAEpB,MAAM,OAAO,CAAC;AAMf,OAAO,aAAa,CAAC;AAErB,MAAM,WAAW,QAAQ;IACvB,kEAAkE;IAClE,CAAC,EAAE,MAAM,CAAC;IACV,iEAAiE;IACjE,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,8FAA8F;AAC9F,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,YAAY,CAAC,EAAE,wBAAwB,CAAC;IACxC,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvD;AAoDD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,8GA+TxB,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,YAAY,CAAC;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,QAAQ,EACd,EACE,WAAW,EACX,QAAY,EACZ,IAAkB,EAClB,OAAc,GACf,GAAE,iBAAsB,GACxB,OAAO,CAAC,IAAI,CAAC,CAgDf"}
|
|
@@ -8,16 +8,31 @@ export interface CropRect {
|
|
|
8
8
|
height: number;
|
|
9
9
|
}
|
|
10
10
|
export interface CropState {
|
|
11
|
-
/** Pan, in stage pixels, from the centre. */
|
|
11
|
+
/** Pan, in stage pixels, from the centre. Follows the stage size, so save `cx`/`cy`. */
|
|
12
12
|
offsetX: number;
|
|
13
13
|
offsetY: number;
|
|
14
14
|
/** 1 is "cover the crop window exactly". */
|
|
15
15
|
zoom: number;
|
|
16
16
|
/** Quarter turns applied to the picture, clockwise: 0, 90, 180 or 270. */
|
|
17
17
|
rotation: number;
|
|
18
|
+
/** Crop centre as fractions of the unrotated source image. */
|
|
19
|
+
cx: number;
|
|
20
|
+
cy: number;
|
|
21
|
+
}
|
|
22
|
+
/** A saved selection. Every emitted `CropState` is one, so it can be passed straight back. */
|
|
23
|
+
export interface ImageCropperDefaultState {
|
|
24
|
+
/** Zoom at initialization; clamped to the supported range. */
|
|
25
|
+
zoom: number;
|
|
26
|
+
/** Crop centre as fractions of the unrotated source image. */
|
|
27
|
+
cx: number;
|
|
28
|
+
cy: number;
|
|
29
|
+
/** Clockwise quarter turns, snapped to 0, 90, 180 or 270. Defaults to 0. */
|
|
30
|
+
rotation?: number;
|
|
18
31
|
}
|
|
19
32
|
export interface ImageCropperProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
20
33
|
src: string;
|
|
34
|
+
/** Uncontrolled initial selection. Applied again whenever `src` changes. */
|
|
35
|
+
defaultState?: ImageCropperDefaultState;
|
|
21
36
|
/** Describes the picture for people who cannot see the stage. */
|
|
22
37
|
alt?: string;
|
|
23
38
|
/** Required output shape, width / height. Must be positive and finite. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/ImageCropper/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EAEpB,MAAM,OAAO,CAAC;AAMf,OAAO,aAAa,CAAC;AAErB,MAAM,WAAW,QAAQ;IACvB,kEAAkE;IAClE,CAAC,EAAE,MAAM,CAAC;IACV,iEAAiE;IACjE,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/ImageCropper/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EAEpB,MAAM,OAAO,CAAC;AAMf,OAAO,aAAa,CAAC;AAErB,MAAM,WAAW,QAAQ;IACvB,kEAAkE;IAClE,CAAC,EAAE,MAAM,CAAC;IACV,iEAAiE;IACjE,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,8FAA8F;AAC9F,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,cAAc,CAAC,cAAc,CAAC,EAC9B,UAAU,CACX;IACC,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,YAAY,CAAC,EAAE,wBAAwB,CAAC;IACxC,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvD;AAoDD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,8GA+TxB,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,YAAY,CAAC;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,QAAQ,EACd,EACE,WAAW,EACX,QAAY,EACZ,IAAkB,EAClB,OAAc,GACf,GAAE,iBAAsB,GACxB,OAAO,CAAC,IAAI,CAAC,CAgDf"}
|