@hunterchen/canvas 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -0
- package/dist/components/canvas/canvas.d.ts +3 -1
- package/dist/components/canvas/canvas.d.ts.map +1 -1
- package/dist/components/canvas/canvas.js +6 -4
- package/dist/components/canvas/canvas.js.map +1 -1
- package/dist/components/canvas/draggable.d.ts +1 -0
- package/dist/components/canvas/draggable.d.ts.map +1 -1
- package/dist/components/canvas/draggable.js +3 -3
- package/dist/components/canvas/draggable.js.map +1 -1
- package/dist/components/canvas/toolbar.d.ts +3 -1
- package/dist/components/canvas/toolbar.d.ts.map +1 -1
- package/dist/components/canvas/toolbar.js +67 -9
- package/dist/components/canvas/toolbar.js.map +1 -1
- package/dist/index.d.ts +19 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -15
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/index.d.ts +43 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/canvas/canvas.tsx +40 -25
- package/src/components/canvas/draggable.tsx +4 -2
- package/src/components/canvas/toolbar.tsx +129 -16
- package/src/index.ts +46 -28
- package/src/types/index.ts +59 -0
package/dist/styles.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--canvas-beige:#f7f1e5;--canvas-coral:#ffb5a7;--canvas-lilac:#d9c8e6;--canvas-salmon:#ffa585;--canvas-heavy:#3c204c;--canvas-emphasis:#513b7a;--canvas-active:#8f57ad;--canvas-tinted:#c9a7db;--canvas-medium:#776780;--canvas-light:#c3b8cb;--canvas-faint-lilac:#f5f2f7;--canvas-offwhite:#fdfcfd;--canvas-highlight:#f5f2f7;--canvas-border-light:0 0% 89%}.container{width:100%;margin-right:auto;margin-left:auto;padding-right:2rem;padding-left:2rem}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1600px){.container{max-width:1600px}}@media (min-width:2000px){.container{max-width:2000px}}@media (min-width:3000px){.container{max-width:3000px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.-top-10{top:-2.5rem}.bottom-12{bottom:3rem}.bottom-24{bottom:6rem}.left-1\/2{left:50%}.left-4{left:1rem}.top-1\/2{top:50%}.top-24{top:6rem}.top-6{top:1.5rem}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.z-\[1000\]{z-index:1000}.m-auto{margin:auto}.mb-4{margin-bottom:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-5{height:1.25rem}.h-auto{height:auto}.h-full{height:100%}.w-5{width:1.25rem}.w-full{width:100%}.flex-shrink-0{flex-shrink:0}.grow{flex-grow:1}.origin-center{transform-origin:center}.origin-top-left{transform-origin:top left}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.overflow-hidden{overflow:hidden}.whitespace-nowrap{white-space:nowrap}.rounded-\[10px\]{border-radius:10px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border,.border-\[1px\]{border-width:1px}.border-border{border-color:hsl(var(--border))}.bg-\[\#EEE2FB\]{--tw-bg-opacity:1;background-color:rgb(238 226 251/var(--tw-bg-opacity,1))}.bg-canvas-highlight{background-color:var(--canvas-highlight)}.bg-canvas-offwhite{background-color:var(--canvas-offwhite)}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.bg-none{background-image:none}.from-black\/10{--tw-gradient-from:rgba(0,0,0,.1) var(--tw-gradient-from-position);--tw-gradient-to:transparent var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-2{padding:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-\[1px\]{padding-left:1px;padding-right:1px}.py-1{padding-top:.25rem;padding-bottom:.25rem}.pb-\[2\.5px\]{padding-bottom:2.5px}.pt-\[1px\]{padding-top:1px}.text-center{text-align:center}.font-canvas-figtree{font-family:var(--font-figtree)}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-canvas-emphasis{color:var(--canvas-emphasis)}.text-canvas-heavy{color:var(--canvas-heavy)}.text-canvas-light{color:var(--canvas-light)}.text-canvas-medium{color:var(--canvas-medium)}.text-neutral-500{--tw-text-opacity:1;color:rgb(115 115 115/var(--tw-text-opacity,1))}.text-neutral-600{--tw-text-opacity:1;color:rgb(82 82 82/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-\[0_20px_40px_rgba\(103\2c 86\2c 86\2c 0\.15\)\]{--tw-shadow:0 20px 40px hsla(0,9%,37%,.15);--tw-shadow-colored:0 20px 40px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_6px_12px_rgba\(0\2c 0\2c 0\2c 0\.10\)\]{--tw-shadow:0 6px 12px rgba(0,0,0,.1);--tw-shadow-colored:0 6px 12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.blur{--tw-blur:blur(8px)}.blur,.drop-shadow{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.duration-200{animation-duration:.2s}.running{animation-play-state:running}@media (min-width:640px){.sm\:top-4{top:1rem}}@media (min-width:768px){.md\:bottom-4{bottom:1rem}.md\:inline{display:inline}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--canvas-beige:#f7f1e5;--canvas-coral:#ffb5a7;--canvas-lilac:#d9c8e6;--canvas-salmon:#ffa585;--canvas-heavy:#3c204c;--canvas-emphasis:#513b7a;--canvas-active:#8f57ad;--canvas-tinted:#c9a7db;--canvas-medium:#776780;--canvas-light:#c3b8cb;--canvas-faint-lilac:#f5f2f7;--canvas-offwhite:#fdfcfd;--canvas-highlight:#f5f2f7;--canvas-border-light:0 0% 89%}.container{width:100%;margin-right:auto;margin-left:auto;padding-right:2rem;padding-left:2rem}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1600px){.container{max-width:1600px}}@media (min-width:2000px){.container{max-width:2000px}}@media (min-width:3000px){.container{max-width:3000px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.-top-10{top:-2.5rem}.bottom-12{bottom:3rem}.bottom-24{bottom:6rem}.bottom-6{bottom:1.5rem}.left-1\/2{left:50%}.left-4{left:1rem}.right-4{right:1rem}.top-1\/2{top:50%}.top-24{top:6rem}.top-6{top:1.5rem}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.z-\[1000\]{z-index:1000}.m-auto{margin:auto}.mb-4{margin-bottom:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.h-5{height:1.25rem}.h-auto{height:auto}.h-full{height:100%}.w-5{width:1.25rem}.w-full{width:100%}.flex-shrink-0{flex-shrink:0}.grow{flex-grow:1}.origin-center{transform-origin:center}.origin-top-left{transform-origin:top left}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.overflow-hidden{overflow:hidden}.whitespace-nowrap{white-space:nowrap}.rounded-\[10px\]{border-radius:10px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border,.border-\[1px\]{border-width:1px}.border-border{border-color:hsl(var(--border))}.bg-\[\#EEE2FB\]{--tw-bg-opacity:1;background-color:rgb(238 226 251/var(--tw-bg-opacity,1))}.bg-canvas-highlight{background-color:var(--canvas-highlight)}.bg-canvas-offwhite{background-color:var(--canvas-offwhite)}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.bg-none{background-image:none}.from-black\/10{--tw-gradient-from:rgba(0,0,0,.1) var(--tw-gradient-from-position);--tw-gradient-to:transparent var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-2{padding:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-\[1px\]{padding-left:1px;padding-right:1px}.py-1{padding-top:.25rem;padding-bottom:.25rem}.pb-\[2\.5px\]{padding-bottom:2.5px}.pt-\[1px\]{padding-top:1px}.text-center{text-align:center}.font-canvas-figtree{font-family:var(--font-figtree)}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-canvas-emphasis{color:var(--canvas-emphasis)}.text-canvas-heavy{color:var(--canvas-heavy)}.text-canvas-light{color:var(--canvas-light)}.text-canvas-medium{color:var(--canvas-medium)}.text-neutral-500{--tw-text-opacity:1;color:rgb(115 115 115/var(--tw-text-opacity,1))}.text-neutral-600{--tw-text-opacity:1;color:rgb(82 82 82/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.shadow-\[0_20px_40px_rgba\(103\2c 86\2c 86\2c 0\.15\)\]{--tw-shadow:0 20px 40px hsla(0,9%,37%,.15);--tw-shadow-colored:0 20px 40px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_6px_12px_rgba\(0\2c 0\2c 0\2c 0\.10\)\]{--tw-shadow:0 6px 12px rgba(0,0,0,.1);--tw-shadow-colored:0 6px 12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.blur{--tw-blur:blur(8px)}.blur,.drop-shadow{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.duration-200{animation-duration:.2s}.running{animation-play-state:running}@media (min-width:640px){.sm\:bottom-4{bottom:1rem}.sm\:top-4{top:1rem}}@media (min-width:768px){.md\:bottom-4{bottom:1rem}.md\:inline{display:inline}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -37,4 +37,47 @@ export interface NavItem {
|
|
|
37
37
|
/** If true, clicking this section triggers the reset/home behavior */
|
|
38
38
|
isHome?: boolean;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Preset positions for the toolbar
|
|
42
|
+
*/
|
|
43
|
+
export type ToolbarPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
44
|
+
/**
|
|
45
|
+
* What to display in the toolbar
|
|
46
|
+
*/
|
|
47
|
+
export type ToolbarDisplayMode = 'coordinates' | 'scale' | 'both';
|
|
48
|
+
/**
|
|
49
|
+
* Configuration options for the canvas toolbar
|
|
50
|
+
*/
|
|
51
|
+
export interface ToolbarConfig {
|
|
52
|
+
/** Hide the toolbar entirely. Default: false */
|
|
53
|
+
hidden?: boolean;
|
|
54
|
+
/** What to show: 'coordinates', 'scale', or 'both'. Default: 'both' */
|
|
55
|
+
display?: ToolbarDisplayMode;
|
|
56
|
+
/** Preset position. Default: 'top-left' */
|
|
57
|
+
position?: ToolbarPosition;
|
|
58
|
+
/** Disable auto-hide when at home position. Default: false */
|
|
59
|
+
disableAutoHide?: boolean;
|
|
60
|
+
/** Additional className for the container */
|
|
61
|
+
className?: string;
|
|
62
|
+
/** Additional className for the coordinates text */
|
|
63
|
+
coordinatesClassName?: string;
|
|
64
|
+
/** Additional className for the scale text */
|
|
65
|
+
scaleClassName?: string;
|
|
66
|
+
/** Additional className for the separator */
|
|
67
|
+
separatorClassName?: string;
|
|
68
|
+
/** Inline styles for the container */
|
|
69
|
+
style?: React.CSSProperties;
|
|
70
|
+
/** Inline styles for the coordinates */
|
|
71
|
+
coordinatesStyle?: React.CSSProperties;
|
|
72
|
+
/** Inline styles for the scale */
|
|
73
|
+
scaleStyle?: React.CSSProperties;
|
|
74
|
+
/** Custom separator between coordinates and scale. Default: ' | ' */
|
|
75
|
+
separator?: string;
|
|
76
|
+
/** Gap around the separator in pixels or CSS value. Default: undefined (uses inline spacing) */
|
|
77
|
+
separatorGap?: number | string;
|
|
78
|
+
/** Format for coordinates. Default: '(x, y)' */
|
|
79
|
+
coordinatesFormat?: (x: number, y: number) => string;
|
|
80
|
+
/** Format for scale. Default: '1.00x' */
|
|
81
|
+
scaleFormat?: (scale: number) => string;
|
|
82
|
+
}
|
|
40
83
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAEnC;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,iCAAiC;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,iCAAiC;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAEnC;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,iCAAiC;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,iCAAiC;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;AAExF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,aAAa;IAE5B,gDAAgD;IAChD,MAAM,CAAC,EAAE,OAAO,CAAC;IAGjB,uEAAuE;IACvE,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAG7B,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,eAAe,CAAC;IAG3B,8DAA8D;IAC9D,eAAe,CAAC,EAAE,OAAO,CAAC;IAG1B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,8CAA8C;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAG5B,sCAAsC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACvC,kCAAkC;IAClC,UAAU,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAGjC,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACrD,yCAAyC;IACzC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACzC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterchen/canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A React-based canvas library for creating pannable, zoomable, and interactive canvas experiences.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"framer-motion": "^11.0.0 || ^12.0.0",
|
|
49
|
-
"react": "^
|
|
50
|
-
"react-dom": "^
|
|
49
|
+
"react": "^19.0.0",
|
|
50
|
+
"react-dom": "^19.0.0"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"clsx": "^2.1.1",
|
|
@@ -39,7 +39,12 @@ import {
|
|
|
39
39
|
import useWindowDimensions from "../../hooks/useWindowDimensions";
|
|
40
40
|
import Navbar from "./navbar";
|
|
41
41
|
import Toolbar from "./toolbar";
|
|
42
|
-
import type {
|
|
42
|
+
import type {
|
|
43
|
+
CanvasSection,
|
|
44
|
+
NavItem,
|
|
45
|
+
SectionCoordinates,
|
|
46
|
+
ToolbarConfig,
|
|
47
|
+
} from "../../types";
|
|
43
48
|
import { CanvasWrapper } from "./wrapper";
|
|
44
49
|
import { usePerformanceMode } from "../../hooks/usePerformanceMode";
|
|
45
50
|
import type { ReactNode } from "react";
|
|
@@ -74,12 +79,16 @@ interface Props {
|
|
|
74
79
|
canvasBackground?: ReactNode;
|
|
75
80
|
/** Custom wrapper/intro background. If not provided, uses introBackgroundGradient. */
|
|
76
81
|
wrapperBackground?: ReactNode;
|
|
82
|
+
|
|
83
|
+
// ============== Toolbar Customization ==============
|
|
84
|
+
/** Toolbar customization options */
|
|
85
|
+
toolbarConfig?: ToolbarConfig;
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
const stopAllMotion = (
|
|
80
89
|
x: MotionValue<number>,
|
|
81
90
|
y: MotionValue<number>,
|
|
82
|
-
scale: MotionValue<number
|
|
91
|
+
scale: MotionValue<number>
|
|
83
92
|
) => {
|
|
84
93
|
x.stop();
|
|
85
94
|
y.stop();
|
|
@@ -99,6 +108,7 @@ const Canvas: FC<Props> = ({
|
|
|
99
108
|
blurTransition,
|
|
100
109
|
canvasBackground,
|
|
101
110
|
wrapperBackground,
|
|
111
|
+
toolbarConfig,
|
|
102
112
|
}) => {
|
|
103
113
|
const { height: windowHeight, width: windowWidth } = useWindowDimensions();
|
|
104
114
|
|
|
@@ -130,7 +140,7 @@ const Canvas: FC<Props> = ({
|
|
|
130
140
|
|
|
131
141
|
const initialBoxWidth = useMemo(
|
|
132
142
|
() => calcInitialBoxWidth(windowWidth, windowHeight),
|
|
133
|
-
[windowWidth, windowHeight]
|
|
143
|
+
[windowWidth, windowHeight]
|
|
134
144
|
);
|
|
135
145
|
|
|
136
146
|
// somewhere near the middle-ish
|
|
@@ -145,7 +155,7 @@ const Canvas: FC<Props> = ({
|
|
|
145
155
|
coords: homeCoordinates,
|
|
146
156
|
targetZoom: 1,
|
|
147
157
|
}),
|
|
148
|
-
[homeCoordinates, windowWidth, windowHeight]
|
|
158
|
+
[homeCoordinates, windowWidth, windowHeight]
|
|
149
159
|
);
|
|
150
160
|
|
|
151
161
|
const onResetViewAndItems = useCallback(
|
|
@@ -157,7 +167,7 @@ const Canvas: FC<Props> = ({
|
|
|
157
167
|
if (onComplete) onComplete();
|
|
158
168
|
});
|
|
159
169
|
},
|
|
160
|
-
[offsetHomeCoordinates, x, y, scale]
|
|
170
|
+
[offsetHomeCoordinates, x, y, scale]
|
|
161
171
|
);
|
|
162
172
|
|
|
163
173
|
// Shared intro progress (0->1) driven by CanvasWrapper
|
|
@@ -167,7 +177,7 @@ const Canvas: FC<Props> = ({
|
|
|
167
177
|
const stage1Targets = useMemo(() => {
|
|
168
178
|
const finalScale = Math.max(
|
|
169
179
|
(windowWidth || 0) / canvasWidth,
|
|
170
|
-
(windowHeight || 0) / canvasHeight
|
|
180
|
+
(windowHeight || 0) / canvasHeight
|
|
171
181
|
);
|
|
172
182
|
const endX = (windowWidth - canvasWidth * finalScale) / 2;
|
|
173
183
|
const endY = (windowHeight - canvasHeight * finalScale) / 2;
|
|
@@ -178,7 +188,7 @@ const Canvas: FC<Props> = ({
|
|
|
178
188
|
const derivedScale = useTransform(
|
|
179
189
|
introProgress,
|
|
180
190
|
[0, 1],
|
|
181
|
-
[initialBoxWidth, stage1Targets.finalScale]
|
|
191
|
+
[initialBoxWidth, stage1Targets.finalScale]
|
|
182
192
|
);
|
|
183
193
|
const derivedX = useTransform(introProgress, [0, 1], [0, stage1Targets.endX]);
|
|
184
194
|
const derivedY = useTransform(introProgress, [0, 1], [0, stage1Targets.endY]);
|
|
@@ -240,11 +250,11 @@ const Canvas: FC<Props> = ({
|
|
|
240
250
|
node.addEventListener("wheel", wheelWrapper, { passive: false });
|
|
241
251
|
}
|
|
242
252
|
},
|
|
243
|
-
[wheelWrapper]
|
|
253
|
+
[wheelWrapper]
|
|
244
254
|
);
|
|
245
255
|
|
|
246
256
|
const activePointersRef = useRef<Map<number, PointerEvent<HTMLDivElement>>>(
|
|
247
|
-
new Map()
|
|
257
|
+
new Map()
|
|
248
258
|
);
|
|
249
259
|
const initialPinchStateRef = useRef<{
|
|
250
260
|
distance: number;
|
|
@@ -258,7 +268,7 @@ const Canvas: FC<Props> = ({
|
|
|
258
268
|
offset: Point,
|
|
259
269
|
viewportRef: React.RefObject<HTMLDivElement | null>,
|
|
260
270
|
onComplete?: () => void,
|
|
261
|
-
zoom?: number
|
|
271
|
+
zoom?: number
|
|
262
272
|
): void => {
|
|
263
273
|
if (!viewportRef.current) return;
|
|
264
274
|
setIsSceneMoving(true);
|
|
@@ -281,13 +291,13 @@ const Canvas: FC<Props> = ({
|
|
|
281
291
|
x,
|
|
282
292
|
y,
|
|
283
293
|
scale,
|
|
284
|
-
zoom
|
|
294
|
+
zoom
|
|
285
295
|
).then(() => {
|
|
286
296
|
setIsSceneMoving(false);
|
|
287
297
|
if (onComplete) onComplete();
|
|
288
298
|
});
|
|
289
299
|
},
|
|
290
|
-
[sceneWidth, sceneHeight, x, y, scale]
|
|
300
|
+
[sceneWidth, sceneHeight, x, y, scale]
|
|
291
301
|
);
|
|
292
302
|
|
|
293
303
|
// Guarded stop that ignores attempts during intro animations
|
|
@@ -340,7 +350,7 @@ const Canvas: FC<Props> = ({
|
|
|
340
350
|
viewportRef,
|
|
341
351
|
animationStage,
|
|
342
352
|
stopAllSceneMotion,
|
|
343
|
-
]
|
|
353
|
+
]
|
|
344
354
|
);
|
|
345
355
|
|
|
346
356
|
const handlePointerMove = useCallback(
|
|
@@ -366,11 +376,11 @@ const Canvas: FC<Props> = ({
|
|
|
366
376
|
|
|
367
377
|
const newX = Math.min(
|
|
368
378
|
Math.max(initialPanOffsetOnDrag.x + deltaX, minPanX),
|
|
369
|
-
maxPanX
|
|
379
|
+
maxPanX
|
|
370
380
|
);
|
|
371
381
|
const newY = Math.min(
|
|
372
382
|
Math.max(initialPanOffsetOnDrag.y + deltaY, minPanY),
|
|
373
|
-
maxPanY
|
|
383
|
+
maxPanY
|
|
374
384
|
);
|
|
375
385
|
x.set(newX);
|
|
376
386
|
y.set(newY);
|
|
@@ -399,7 +409,7 @@ const Canvas: FC<Props> = ({
|
|
|
399
409
|
(window.innerWidth / canvasWidth) * ZOOM_BOUND, // Ensure zoom is at least the width of the canvas
|
|
400
410
|
(window.innerHeight / canvasHeight) * ZOOM_BOUND, // Ensure zoom is at least the height of the canvas
|
|
401
411
|
Math.min(newZoom, 10),
|
|
402
|
-
MIN_ZOOM
|
|
412
|
+
MIN_ZOOM // Ensure zoom is not less than MIN_ZOOM
|
|
403
413
|
);
|
|
404
414
|
|
|
405
415
|
const mx = currentMidpoint.x;
|
|
@@ -441,7 +451,7 @@ const Canvas: FC<Props> = ({
|
|
|
441
451
|
MIN_ZOOM,
|
|
442
452
|
animationStage,
|
|
443
453
|
stopAllSceneMotion,
|
|
444
|
-
]
|
|
454
|
+
]
|
|
445
455
|
);
|
|
446
456
|
|
|
447
457
|
const handlePointerUpOrCancel = useCallback(
|
|
@@ -478,7 +488,7 @@ const Canvas: FC<Props> = ({
|
|
|
478
488
|
setInitialPanOffsetOnDrag({ x: x.get(), y: y.get() });
|
|
479
489
|
}
|
|
480
490
|
},
|
|
481
|
-
[x, y, isPanning, animationStage, stopAllSceneMotion]
|
|
491
|
+
[x, y, isPanning, animationStage, stopAllSceneMotion]
|
|
482
492
|
);
|
|
483
493
|
|
|
484
494
|
const handleWheelZoom = useCallback(
|
|
@@ -504,11 +514,11 @@ const Canvas: FC<Props> = ({
|
|
|
504
514
|
const nextZoom = Math.max(
|
|
505
515
|
Math.min(
|
|
506
516
|
currentZoom * (1 - event.deltaY * ZOOM_SENSITIVITY),
|
|
507
|
-
MAX_ZOOM
|
|
517
|
+
MAX_ZOOM
|
|
508
518
|
),
|
|
509
519
|
MIN_ZOOM,
|
|
510
520
|
(window.innerWidth / canvasWidth) * ZOOM_BOUND, // Ensure zoom is at least the width of the canvas
|
|
511
|
-
(window.innerHeight / canvasHeight) * ZOOM_BOUND
|
|
521
|
+
(window.innerHeight / canvasHeight) * ZOOM_BOUND // Ensure zoom is at least the height of the canvas
|
|
512
522
|
);
|
|
513
523
|
|
|
514
524
|
const rect = viewportRef.current?.getBoundingClientRect();
|
|
@@ -567,7 +577,7 @@ const Canvas: FC<Props> = ({
|
|
|
567
577
|
windowHeight,
|
|
568
578
|
animationStage,
|
|
569
579
|
stopAllSceneMotion,
|
|
570
|
-
]
|
|
580
|
+
]
|
|
571
581
|
);
|
|
572
582
|
|
|
573
583
|
// Keep the wheel handler ref pointing to the latest implementation
|
|
@@ -579,7 +589,7 @@ const Canvas: FC<Props> = ({
|
|
|
579
589
|
(
|
|
580
590
|
offset: { x: number; y: number },
|
|
581
591
|
onComplete?: () => void,
|
|
582
|
-
zoom?: number
|
|
592
|
+
zoom?: number
|
|
583
593
|
) => {
|
|
584
594
|
panToOffset(
|
|
585
595
|
{
|
|
@@ -588,10 +598,10 @@ const Canvas: FC<Props> = ({
|
|
|
588
598
|
},
|
|
589
599
|
viewportRef,
|
|
590
600
|
onComplete,
|
|
591
|
-
zoom
|
|
601
|
+
zoom
|
|
592
602
|
);
|
|
593
603
|
},
|
|
594
|
-
[panToOffset, viewportRef]
|
|
604
|
+
[panToOffset, viewportRef]
|
|
595
605
|
);
|
|
596
606
|
|
|
597
607
|
return (
|
|
@@ -620,7 +630,12 @@ const Canvas: FC<Props> = ({
|
|
|
620
630
|
>
|
|
621
631
|
{animationStage >= 2 && (
|
|
622
632
|
<>
|
|
623
|
-
|
|
633
|
+
{!toolbarConfig?.hidden && (
|
|
634
|
+
<Toolbar
|
|
635
|
+
homeCoordinates={offsetHomeCoordinates}
|
|
636
|
+
config={toolbarConfig}
|
|
637
|
+
/>
|
|
638
|
+
)}
|
|
624
639
|
{hasNavbar && navItems ? (
|
|
625
640
|
<Navbar
|
|
626
641
|
panToOffset={handlePanToOffset}
|
|
@@ -137,6 +137,7 @@ export interface DraggableImageProps extends DraggableProps {
|
|
|
137
137
|
width?: string | number;
|
|
138
138
|
height?: string | number;
|
|
139
139
|
scale?: number;
|
|
140
|
+
hoverScale?: number;
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
function drawImageToCanvas(img: HTMLImageElement, canvas: HTMLCanvasElement) {
|
|
@@ -202,6 +203,7 @@ export function DraggableImage(props: DraggableImageProps) {
|
|
|
202
203
|
animate,
|
|
203
204
|
className,
|
|
204
205
|
scale,
|
|
206
|
+
hoverScale,
|
|
205
207
|
...restProps
|
|
206
208
|
} = props;
|
|
207
209
|
const imgRef = useRef<HTMLImageElement>(null);
|
|
@@ -265,7 +267,7 @@ export function DraggableImage(props: DraggableImageProps) {
|
|
|
265
267
|
updateCursor(isOpaque, false, imgRef.current);
|
|
266
268
|
};
|
|
267
269
|
|
|
268
|
-
const
|
|
270
|
+
const hoverScaleValue = isOpaque ? (hoverScale ?? (scale ?? 1)) : (scale ?? 1);
|
|
269
271
|
|
|
270
272
|
return (
|
|
271
273
|
<Draggable
|
|
@@ -285,7 +287,7 @@ export function DraggableImage(props: DraggableImageProps) {
|
|
|
285
287
|
height={height}
|
|
286
288
|
animate={animate}
|
|
287
289
|
draggable="false"
|
|
288
|
-
whileHover={{ scale:
|
|
290
|
+
whileHover={{ scale: hoverScaleValue }}
|
|
289
291
|
style={{
|
|
290
292
|
scale: scale ?? 1,
|
|
291
293
|
pointerEvents: isOpaque ? "auto" : "none",
|
|
@@ -1,19 +1,56 @@
|
|
|
1
1
|
import { type Point, useTransform, motion } from "framer-motion";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useEffect, useState, useMemo } from "react";
|
|
3
3
|
import { useCanvasContext } from "../../contexts/CanvasContext";
|
|
4
4
|
import {
|
|
5
5
|
TOOLBAR_OPACITY_POS_EPS,
|
|
6
6
|
TOOLBAR_OPACITY_SCALE_EPS,
|
|
7
7
|
} from "../../lib/constants";
|
|
8
|
+
import { cn } from "../../lib/utils";
|
|
9
|
+
import type { ToolbarConfig, ToolbarPosition } from "../../types";
|
|
8
10
|
|
|
9
11
|
type ToolbarProps = {
|
|
10
12
|
homeCoordinates?: Point;
|
|
13
|
+
config?: ToolbarConfig;
|
|
11
14
|
};
|
|
12
15
|
|
|
13
|
-
const
|
|
16
|
+
const positionStyles: Record<ToolbarPosition, string> = {
|
|
17
|
+
"top-left": "left-4 top-6 sm:top-4",
|
|
18
|
+
"top-right": "right-4 top-6 sm:top-4",
|
|
19
|
+
"bottom-left": "left-4 bottom-6 sm:bottom-4",
|
|
20
|
+
"bottom-right": "right-4 bottom-6 sm:bottom-4",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const Toolbar = ({
|
|
24
|
+
homeCoordinates = { x: 0, y: 0 },
|
|
25
|
+
config = {},
|
|
26
|
+
}: ToolbarProps) => {
|
|
14
27
|
const { x, y, scale } = useCanvasContext();
|
|
15
28
|
const [hasMounted, setHasMounted] = useState(false);
|
|
16
29
|
|
|
30
|
+
const {
|
|
31
|
+
display = "both",
|
|
32
|
+
position = "top-left",
|
|
33
|
+
disableAutoHide = false,
|
|
34
|
+
className,
|
|
35
|
+
coordinatesClassName,
|
|
36
|
+
scaleClassName,
|
|
37
|
+
separatorClassName,
|
|
38
|
+
style,
|
|
39
|
+
coordinatesStyle,
|
|
40
|
+
scaleStyle,
|
|
41
|
+
separator = "|",
|
|
42
|
+
separatorGap,
|
|
43
|
+
coordinatesFormat,
|
|
44
|
+
scaleFormat,
|
|
45
|
+
} = config;
|
|
46
|
+
|
|
47
|
+
const separatorStyle: React.CSSProperties | undefined = separatorGap
|
|
48
|
+
? {
|
|
49
|
+
marginInline:
|
|
50
|
+
typeof separatorGap === "number" ? `${separatorGap}px` : separatorGap,
|
|
51
|
+
}
|
|
52
|
+
: undefined;
|
|
53
|
+
|
|
17
54
|
useEffect(() => {
|
|
18
55
|
setHasMounted(true);
|
|
19
56
|
}, []);
|
|
@@ -21,44 +58,120 @@ const Toolbar = ({ homeCoordinates = { x: 0, y: 0 } }: ToolbarProps) => {
|
|
|
21
58
|
// numeric MotionValues
|
|
22
59
|
const rawDx = useTransform(
|
|
23
60
|
[x, scale],
|
|
24
|
-
([lx, ls]) => -((lx as number) / (ls as number)) + homeCoordinates.x
|
|
61
|
+
([lx, ls]) => -((lx as number) / (ls as number)) + homeCoordinates.x
|
|
25
62
|
);
|
|
26
63
|
const rawDy = useTransform(
|
|
27
64
|
[y, scale],
|
|
28
|
-
([ly, ls]) => -((ly as number) / (ls as number)) + homeCoordinates.y
|
|
65
|
+
([ly, ls]) => -((ly as number) / (ls as number)) + homeCoordinates.y
|
|
29
66
|
);
|
|
30
67
|
|
|
31
|
-
// formatted MotionValues
|
|
68
|
+
// formatted MotionValues for default display
|
|
32
69
|
const displayX = useTransform(rawDx, (v) => Math.round(v).toString());
|
|
33
70
|
const displayY = useTransform(rawDy, (v) => Math.round(v).toString());
|
|
34
71
|
const displayScale = useTransform(scale, (v) => v.toFixed(2));
|
|
35
72
|
|
|
36
|
-
|
|
37
|
-
|
|
73
|
+
// For custom formatters, we need to use state to track values
|
|
74
|
+
const [currentX, setCurrentX] = useState(0);
|
|
75
|
+
const [currentY, setCurrentY] = useState(0);
|
|
76
|
+
const [currentScale, setCurrentScale] = useState(1);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const unsubX = rawDx.on("change", (v) => setCurrentX(Math.round(v)));
|
|
80
|
+
const unsubY = rawDy.on("change", (v) => setCurrentY(Math.round(v)));
|
|
81
|
+
const unsubScale = scale.on("change", (v) => setCurrentScale(v));
|
|
82
|
+
return () => {
|
|
83
|
+
unsubX();
|
|
84
|
+
unsubY();
|
|
85
|
+
unsubScale();
|
|
86
|
+
};
|
|
87
|
+
}, [rawDx, rawDy, scale]);
|
|
88
|
+
|
|
89
|
+
const opacity = useTransform([rawDx, rawDy, scale], ([dx, dy, ls]) => {
|
|
90
|
+
if (disableAutoHide) return 1;
|
|
91
|
+
return Math.abs(dx as number) < TOOLBAR_OPACITY_POS_EPS &&
|
|
38
92
|
Math.abs(dy as number) < TOOLBAR_OPACITY_POS_EPS &&
|
|
39
93
|
Math.abs((ls as number) - 1) < TOOLBAR_OPACITY_SCALE_EPS
|
|
40
94
|
? 0
|
|
41
|
-
: 1
|
|
42
|
-
);
|
|
95
|
+
: 1;
|
|
96
|
+
});
|
|
43
97
|
|
|
44
98
|
const handlePointerDown = (e: React.PointerEvent) => e.stopPropagation();
|
|
45
99
|
|
|
100
|
+
const showCoordinates = display === "coordinates" || display === "both";
|
|
101
|
+
const showScale = display === "scale" || display === "both";
|
|
102
|
+
const showSeparator = display === "both";
|
|
103
|
+
|
|
104
|
+
// Compute formatted values
|
|
105
|
+
const formattedCoordinates = useMemo(() => {
|
|
106
|
+
if (coordinatesFormat) {
|
|
107
|
+
return coordinatesFormat(currentX, currentY);
|
|
108
|
+
}
|
|
109
|
+
return null; // Will use motion spans for default
|
|
110
|
+
}, [coordinatesFormat, currentX, currentY]);
|
|
111
|
+
|
|
112
|
+
const formattedScale = useMemo(() => {
|
|
113
|
+
if (scaleFormat) {
|
|
114
|
+
return scaleFormat(currentScale);
|
|
115
|
+
}
|
|
116
|
+
return null; // Will use motion span for default
|
|
117
|
+
}, [scaleFormat, currentScale]);
|
|
118
|
+
|
|
119
|
+
// Placeholder content for SSR/initial render
|
|
120
|
+
const placeholderContent = useMemo(() => {
|
|
121
|
+
const parts: string[] = [];
|
|
122
|
+
if (showCoordinates) parts.push("(0, 0)");
|
|
123
|
+
if (showSeparator) parts.push(separator);
|
|
124
|
+
if (showScale) parts.push("1.00x");
|
|
125
|
+
return parts.join("");
|
|
126
|
+
}, [showCoordinates, showScale, showSeparator, separator]);
|
|
127
|
+
|
|
46
128
|
return (
|
|
47
129
|
<motion.div
|
|
48
|
-
className=
|
|
130
|
+
className={cn(
|
|
131
|
+
"absolute z-[1000] cursor-default select-none rounded-[10px] border border-border bg-canvas-offwhite p-2 font-mono text-xs text-canvas-heavy shadow-[0_6px_12px_rgba(0,0,0,0.10)] md:text-sm",
|
|
132
|
+
positionStyles[position],
|
|
133
|
+
className
|
|
134
|
+
)}
|
|
49
135
|
onPointerDown={handlePointerDown}
|
|
50
136
|
data-toolbar-button
|
|
51
|
-
style={{ opacity }}
|
|
137
|
+
style={{ opacity, ...style }}
|
|
52
138
|
>
|
|
53
139
|
{hasMounted ? (
|
|
54
140
|
<>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
141
|
+
{showCoordinates && (
|
|
142
|
+
<span className={coordinatesClassName} style={coordinatesStyle}>
|
|
143
|
+
{formattedCoordinates !== null ? (
|
|
144
|
+
formattedCoordinates
|
|
145
|
+
) : (
|
|
146
|
+
<>
|
|
147
|
+
(<motion.span>{displayX}</motion.span>,{" "}
|
|
148
|
+
<motion.span>{displayY}</motion.span>)
|
|
149
|
+
</>
|
|
150
|
+
)}
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
{showSeparator && (
|
|
154
|
+
<span
|
|
155
|
+
className={cn("text-canvas-light", separatorClassName)}
|
|
156
|
+
style={separatorStyle}
|
|
157
|
+
>
|
|
158
|
+
{separator}
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
{showScale && (
|
|
162
|
+
<span className={scaleClassName} style={scaleStyle}>
|
|
163
|
+
{formattedScale !== null ? (
|
|
164
|
+
formattedScale
|
|
165
|
+
) : (
|
|
166
|
+
<>
|
|
167
|
+
<motion.span>{displayScale}</motion.span>x
|
|
168
|
+
</>
|
|
169
|
+
)}
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
59
172
|
</>
|
|
60
173
|
) : (
|
|
61
|
-
<span style={{ opacity: 0 }}>
|
|
174
|
+
<span style={{ opacity: 0 }}>{placeholderContent}</span>
|
|
62
175
|
)}
|
|
63
176
|
</motion.div>
|
|
64
177
|
);
|