blocks-dusted 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/package.json +5 -2
  2. package/src/cli.js +39 -3
  3. package/src/commands/shop.js +59 -0
  4. package/src/commands/warehouse.js +73 -0
  5. package/src/installers/patchRenderBlocks.js +1 -1
  6. package/src/registry/validateTemplateManifest.js +2 -1
  7. package/templates/blocks/BannerDD790432/Component.tsx +63 -0
  8. package/templates/blocks/BannerDD790432/config.ts +54 -0
  9. package/templates/blocks/BannerDD790432/manifest.json +80 -0
  10. package/templates/blocks/Button47/Component.tsx +124 -0
  11. package/templates/blocks/Button47/config.ts +75 -0
  12. package/templates/blocks/Button47/link.ts +22 -0
  13. package/templates/blocks/Button47/manifest.json +105 -0
  14. package/templates/blocks/Button47/styles.module.scss +75 -0
  15. package/templates/blocks/DD-Accordion/Component.tsx +55 -36
  16. package/templates/blocks/DD-Accordion/config.ts +71 -59
  17. package/templates/blocks/DD-AnimText/Component.tsx +50 -32
  18. package/templates/blocks/DD-AnimText/config.ts +296 -295
  19. package/templates/blocks/DD-AsideNav/Component.tsx +323 -319
  20. package/templates/blocks/DD-Banner/Component.tsx +54 -51
  21. package/templates/blocks/DD-BookCall/Component.tsx +10 -9
  22. package/templates/blocks/DD-ButtonPop/Component.tsx +118 -117
  23. package/templates/blocks/DD-Card/Component.tsx +4 -4
  24. package/templates/blocks/DD-CardTemplate01/Component.tsx +5 -5
  25. package/templates/blocks/DD-CardsWithVariantV3/Component.tsx +472 -0
  26. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/index.tsx +203 -0
  27. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/styles.ts +87 -0
  28. package/templates/blocks/DD-CardsWithVariantV3/ElectricBorder/useInjectStyle.ts +39 -0
  29. package/templates/blocks/DD-CardsWithVariantV3/config.ts +326 -0
  30. package/templates/blocks/DD-CardsWithVariantV3/manifest.json +110 -0
  31. package/templates/blocks/DD-Carousel-A/Component.tsx +251 -249
  32. package/templates/blocks/DD-Carousel-B/Component.tsx +405 -403
  33. package/templates/blocks/DD-Chip/Component.tsx +14 -14
  34. package/templates/blocks/DD-ComparisonTable/Component.tsx +255 -256
  35. package/templates/blocks/DD-Contact/Component.tsx +433 -434
  36. package/templates/blocks/DD-Counter/Component.tsx +18 -13
  37. package/templates/blocks/DD-DeliCard/Component.tsx +303 -0
  38. package/templates/blocks/DD-DeliCard/config.ts +186 -0
  39. package/templates/blocks/DD-DeliCard/manifest.json +94 -0
  40. package/templates/blocks/DD-FeatCat/Component.tsx +302 -302
  41. package/templates/blocks/DD-FeatProd/CircularGallery.tsx +1179 -0
  42. package/templates/blocks/DD-FeatProd/Component.tsx +484 -0
  43. package/templates/blocks/DD-FeatProd/config.ts +173 -0
  44. package/templates/blocks/DD-FeatProd/manifest.json +93 -0
  45. package/templates/blocks/DD-FeatServ/Component.tsx +607 -0
  46. package/templates/blocks/DD-FeatServ/config.ts +213 -0
  47. package/templates/blocks/DD-FeatServ/manifest.json +113 -0
  48. package/templates/blocks/DD-FeatStrip/Component.tsx +584 -585
  49. package/templates/blocks/DD-FluidBtn/ButtonsEyeCatching.tsx +627 -628
  50. package/templates/blocks/DD-FluidBtn/Component.tsx +46 -43
  51. package/templates/blocks/DD-GooeyInfo/Component.tsx +150 -149
  52. package/templates/blocks/DD-GooeyInfo/config.ts +136 -135
  53. package/templates/blocks/DD-Hero/Component.tsx +16 -40
  54. package/templates/blocks/DD-HeroPlas/Component.tsx +50 -0
  55. package/templates/blocks/DD-HeroPlas/ThreeScene.tsx +109 -0
  56. package/templates/blocks/DD-HeroPlas/config.ts +33 -0
  57. package/templates/blocks/DD-HeroPlas/manifest.json +88 -0
  58. package/templates/blocks/DD-HorizontalScroll/Component.tsx +4 -5
  59. package/templates/blocks/DD-Icon/Component.tsx +46 -81
  60. package/templates/blocks/DD-IconButton/Component.tsx +13 -20
  61. package/templates/blocks/DD-IdTag/Component.tsx +22 -21
  62. package/templates/blocks/DD-IframeMedia/Component.tsx +14 -10
  63. package/templates/blocks/DD-LineAnim/Component.tsx +42 -36
  64. package/templates/blocks/DD-LogoMarq/Component.tsx +38 -48
  65. package/templates/blocks/DD-Lottie/Component.tsx +11 -3
  66. package/templates/blocks/DD-MasonaryMedia/Component.tsx +544 -547
  67. package/templates/blocks/DD-MasonaryMedia/config.ts +389 -389
  68. package/templates/blocks/DD-Masonry/Component.tsx +654 -644
  69. package/templates/blocks/DD-Masonry/config.ts +475 -475
  70. package/templates/blocks/DD-MaterialSwatches/Component.tsx +85 -0
  71. package/templates/blocks/DD-MaterialSwatches/config.ts +232 -0
  72. package/templates/blocks/DD-MaterialSwatches/manifest.json +84 -0
  73. package/templates/blocks/DD-MaterialSwatches/material.ts +107 -0
  74. package/templates/blocks/DD-NineCanvas/Component.tsx +129 -0
  75. package/templates/blocks/DD-NineCanvas/RenderBlocks.tsx +244 -0
  76. package/templates/blocks/DD-NineCanvas/allowedCanvasBlocks.ts +6 -0
  77. package/templates/blocks/DD-NineCanvas/blockComponents.tsx +8 -0
  78. package/templates/blocks/DD-NineCanvas/canvas/CanvasGrid.tsx +316 -0
  79. package/templates/blocks/DD-NineCanvas/canvas/RadialNav.tsx +432 -0
  80. package/templates/blocks/DD-NineCanvas/canvas/canvas-panels.css +889 -0
  81. package/templates/blocks/DD-NineCanvas/canvas/icons/custom-icons.tsx +364 -0
  82. package/templates/blocks/DD-NineCanvas/config.ts +61 -0
  83. package/templates/blocks/DD-NineCanvas/manifest.json +165 -0
  84. package/templates/blocks/DD-NineCanvas/styles.scss +152 -0
  85. package/templates/blocks/DD-NineCanvas/tooltip/get-strict-context.tsx +26 -0
  86. package/templates/blocks/DD-NineCanvas/tooltip/primitives.tsx +178 -0
  87. package/templates/blocks/DD-NineCanvas/tooltip/tooltip.tsx +71 -0
  88. package/templates/blocks/DD-NineCanvas/tooltip/use-controlled-state.tsx +33 -0
  89. package/templates/blocks/DD-NineCanvas/utilities/FadeOnScroll.tsx +68 -0
  90. package/templates/blocks/DD-NineCanvas/utilities/ParallaxWrapper.tsx +157 -0
  91. package/templates/blocks/DD-NineCanvas/utilities/renderCustomCSS.ts +5 -0
  92. package/templates/blocks/DD-NineCanvas/utilities/webglCleanup.ts +27 -0
  93. package/templates/blocks/DD-PayloadAIAssistant/Component.tsx +78 -0
  94. package/templates/blocks/DD-PayloadAIAssistant/DustedBot.tsx +590 -0
  95. package/templates/blocks/DD-PayloadAIAssistant/config.ts +37 -0
  96. package/templates/blocks/DD-PayloadAIAssistant/demo.css +125 -0
  97. package/templates/blocks/DD-PayloadAIAssistant/manifest.json +93 -0
  98. package/templates/blocks/DD-PayloadAIAssistant/source-defaults.ts +17 -0
  99. package/templates/blocks/DD-PayloadAIAssistant/styles.ts +306 -0
  100. package/templates/blocks/DD-Pricing/Component.tsx +368 -372
  101. package/templates/blocks/DD-Pricing/config.ts +215 -215
  102. package/templates/blocks/DD-Process/Component.tsx +147 -149
  103. package/templates/blocks/DD-Progress/Component.tsx +156 -141
  104. package/templates/blocks/DD-ProjHeader/Component.tsx +93 -91
  105. package/templates/blocks/DD-ProjSlider/Component.tsx +1 -1
  106. package/templates/blocks/DD-ProjSlider/config.ts +2 -2
  107. package/templates/blocks/DD-Section/Component.tsx +265 -266
  108. package/templates/blocks/DD-Separator/Component.tsx +66 -66
  109. package/templates/blocks/DD-Services/Component.tsx +176 -176
  110. package/templates/blocks/DD-ShowcaseGrid/Component.tsx +306 -307
  111. package/templates/blocks/DD-Slider-A/Component.tsx +237 -237
  112. package/templates/blocks/DD-StackCards/Component.tsx +474 -473
  113. package/templates/blocks/DD-Team/Component.tsx +247 -247
  114. package/templates/blocks/DD-TeamCards/Component.tsx +251 -251
  115. package/templates/blocks/DD-TechStack/Component.tsx +161 -168
  116. package/templates/blocks/DD-TestMarq/Component.tsx +158 -132
  117. package/templates/blocks/DD-Testim/Component.tsx +247 -247
  118. package/templates/blocks/DD-Testim2/Component.tsx +265 -256
  119. package/templates/blocks/DD-Testimonials/Component.tsx +139 -147
  120. package/templates/blocks/DD-TextMarq/Component.tsx +157 -157
  121. package/templates/blocks/DD-Thanks/Component.tsx +501 -500
  122. package/templates/blocks/DD-Work/Component.tsx +315 -315
  123. package/templates/blocks/DD-Work-B/Component.tsx +294 -294
  124. package/templates/blocks/DDHG-CTASection2/Component.tsx +183 -184
  125. package/templates/blocks/DDHG-HighlightArc/Component.tsx +246 -243
  126. package/templates/blocks/DDHG-LogoMarquee/Component.tsx +180 -93
  127. package/templates/blocks/DDHG-LogoMarquee/config.ts +154 -66
  128. package/templates/blocks/DDHG-NavCodeGrid/Component.tsx +225 -213
  129. package/templates/blocks/DDHG-ProcessSec/Component.tsx +38 -39
  130. package/templates/blocks/DDHG-ServiceDetails/Component.tsx +11 -11
  131. package/templates/blocks/DDHG-ServiceDetails/ServiceDetailCard.tsx +30 -47
  132. package/templates/blocks/DDHG-ServiceHero/Component.tsx +10 -10
  133. package/templates/blocks/DDHG-ServiceOverview/Component.tsx +23 -23
  134. package/templates/blocks/DDHG-StatsFAQ/Component.tsx +40 -40
  135. package/templates/blocks/DDHG-TeamCards/Component.tsx +140 -131
  136. package/templates/blocks/Form/Checkbox/index.tsx +45 -0
  137. package/templates/blocks/Form/Component.tsx +163 -0
  138. package/templates/blocks/Form/Country/index.tsx +65 -0
  139. package/templates/blocks/Form/Country/options.ts +982 -0
  140. package/templates/blocks/Form/Email/index.tsx +38 -0
  141. package/templates/blocks/Form/Error/index.tsx +15 -0
  142. package/templates/blocks/Form/Message/index.tsx +13 -0
  143. package/templates/blocks/Form/Number/index.tsx +36 -0
  144. package/templates/blocks/Form/Select/index.tsx +63 -0
  145. package/templates/blocks/Form/State/index.tsx +64 -0
  146. package/templates/blocks/Form/State/options.ts +52 -0
  147. package/templates/blocks/Form/Text/index.tsx +32 -0
  148. package/templates/blocks/Form/Textarea/index.tsx +40 -0
  149. package/templates/blocks/Form/Width/index.tsx +13 -0
  150. package/templates/blocks/Form/config.ts +51 -0
  151. package/templates/blocks/Form/fields.tsx +21 -0
  152. package/templates/blocks/Form/manifest.json +168 -0
  153. package/templates/blocks/MediaBlock/Component.tsx +67 -0
  154. package/templates/blocks/MediaBlock/config.ts +14 -0
  155. package/templates/blocks/MediaBlock/manifest.json +92 -0
  156. package/templates/blocks/TransitionsDustedShowcase/Component.tsx +9 -0
  157. package/templates/blocks/TransitionsDustedShowcase/config.ts +13 -0
  158. package/templates/blocks/TransitionsDustedShowcase/manifest.json +80 -0
  159. package/templates/blocks/rawHtml/Component.tsx +25 -0
  160. package/templates/blocks/rawHtml/config.ts +56 -0
  161. package/templates/blocks/rawHtml/manifest.json +80 -0
@@ -0,0 +1,244 @@
1
+ 'use client'
2
+ import { cn } from '@/utilities/ui'
3
+ import React, { Fragment } from 'react'
4
+
5
+ import { AnimatePresence } from 'framer-motion'
6
+ import { FadeOnScroll } from './utilities/FadeOnScroll'
7
+ import { extractCustomCSS } from './utilities/renderCustomCSS'
8
+ import ParallaxWrapper from './utilities/ParallaxWrapper'
9
+ import { blockComponents } from './blockComponents'
10
+
11
+ interface BlockProps {
12
+ sectionId?: string
13
+ disableInnerContainer?: boolean
14
+
15
+ scrollBehavior?: 'normal' | 'stickToTop' | 'stickToTopFade' | 'parallax'
16
+
17
+ sticky?: boolean
18
+ stickyTopOffset?: number
19
+ stickyZIndex?: number
20
+
21
+ fadeOnScroll?: boolean
22
+ useAbsoluteScroll?: boolean
23
+ fadeStartProgress?: number
24
+ fadeStartPixels?: number
25
+ fadeEndProgress?: number
26
+ fadeEndPixels?: number
27
+ fadeFinalOpacity?: number
28
+ fadeOffsetStart?: 'start start' | 'start center' | 'start end' | `start ${number}%`
29
+ fadeOffsetEnd?: 'start start' | 'start center' | 'start end' | `start ${number}%`
30
+
31
+ parallaxEnabled?: boolean
32
+ parallaxDistance?: number
33
+ parallaxDirection?: 'up' | 'down'
34
+ showScrollDebugMarkers?: boolean
35
+ gsapSelector?: string
36
+ pinTargetSelector?: string
37
+ pinSpacing?: number
38
+ scrollDuration?: number
39
+ scrollSpeed?: number
40
+
41
+ sectionStyle?: React.CSSProperties
42
+
43
+ initialYFadeValue?: number
44
+ isFadeOnce?: boolean
45
+ fadeStiffValue?: number
46
+ fadeDampValue?: number
47
+ exitYFadeValue?: number | string
48
+
49
+ [key: string]: any
50
+ }
51
+
52
+ const toNumber = (value: unknown, fallback: number): number => {
53
+ if (typeof value === 'number' && Number.isFinite(value)) return value
54
+
55
+ if (typeof value === 'string') {
56
+ const parsed = Number(value)
57
+ if (Number.isFinite(parsed)) return parsed
58
+ }
59
+
60
+ return fallback
61
+ }
62
+
63
+ export const RenderBlocks: React.FC<{
64
+ blocks: Array<BlockProps & { blockType: string }>
65
+ }> = (props) => {
66
+ const { blocks } = props
67
+
68
+ if (!blocks || !Array.isArray(blocks) || blocks.length === 0) return null
69
+
70
+ const customCSS = extractCustomCSS(blocks as any)
71
+
72
+ return (
73
+ <>
74
+ {customCSS && <style dangerouslySetInnerHTML={{ __html: customCSS }} />}
75
+
76
+ {blocks.map((block, index) => {
77
+ const { blockType } = block
78
+
79
+ if (!blockType || !(blockType in blockComponents)) return null
80
+
81
+ const Block = blockComponents[blockType]
82
+ if (!Block) return null
83
+
84
+ const blockProps = block as BlockProps
85
+
86
+ const key =
87
+ 'id' in (block as any) && (block as any).id
88
+ ? `block-${(block as any).id}-${blockType}`
89
+ : `block-${index}-${blockType}`
90
+
91
+ const sectionId = blockProps.sectionId
92
+ const scrollSpeed = blockProps.scrollSpeed
93
+
94
+ const scrollBehavior = blockProps.scrollBehavior || 'normal'
95
+
96
+ const isProjectsSliderSticky =
97
+ blockType === 'projectsSlider' && blockProps.stickyToTop === true
98
+
99
+ const isSticky =
100
+ scrollBehavior === 'stickToTop' ||
101
+ scrollBehavior === 'stickToTopFade' ||
102
+ blockProps.sticky === true
103
+
104
+ const isStickyFade =
105
+ scrollBehavior === 'stickToTopFade' ||
106
+ (blockProps.sticky === true && blockProps.fadeOnScroll === true)
107
+
108
+ const isParallax = scrollBehavior === 'parallax' || blockProps.parallaxEnabled === true
109
+
110
+ const stickyTopOffset = toNumber(blockProps.stickyTopOffset, 0)
111
+ const stickyZIndex = toNumber(blockProps.stickyZIndex, 5)
112
+
113
+ const sectionStyle =
114
+ blockProps.sectionStyle && typeof blockProps.sectionStyle === 'object'
115
+ ? (blockProps.sectionStyle as React.CSSProperties)
116
+ : undefined
117
+
118
+ const wrapperlessBlockTypes = new Set([
119
+ 'asideNav',
120
+ 'studioHorizontalScroll',
121
+ 'studioStackCards',
122
+ ])
123
+
124
+ const shouldRenderWrapperless =
125
+ wrapperlessBlockTypes.has(blockType) ||
126
+ (blockType === 'projectsSlider' && blockProps.stickyToTop === true)
127
+
128
+ if (shouldRenderWrapperless) {
129
+ return <Block suppressHydrationWarning key={key} {...blockProps} disableInnerContainer />
130
+ }
131
+
132
+ const blockElement = <Block {...blockProps} disableInnerContainer />
133
+ const isNineCanvasBlock = blockType === 'nineCanvasBlock'
134
+
135
+ const sharedDataProps = {
136
+ suppressHydrationWarning: true,
137
+ id: isNineCanvasBlock ? 'ninecanvas' : sectionId,
138
+ 'data-block-type': blockType,
139
+ 'data-ninecanvas-gesture-owner': blockType === 'DD-Carousel-A' ? true : undefined,
140
+ 'data-projects-slider-sticky': isProjectsSliderSticky ? 'true' : undefined,
141
+ 'data-scroll': scrollSpeed ? true : undefined,
142
+ 'data-scroll-speed': scrollSpeed ? String(scrollSpeed) : undefined,
143
+ }
144
+
145
+ if (isParallax) {
146
+ return (
147
+ <AnimatePresence key={key} mode="sync">
148
+ <section
149
+ {...sharedDataProps}
150
+ className={cn('m-0 renderblock-container relative overflow-visible', sectionId)}
151
+ style={sectionStyle}
152
+ >
153
+ <ParallaxWrapper
154
+ sectionId={sectionId}
155
+ animationConfig={{
156
+ scrollToSelector: blockProps.scrollToSelector,
157
+ triggerSelector: blockProps.gsapSelector,
158
+ pinTargetSelector: blockProps.pinTargetSelector,
159
+ pinSpacing: blockProps.pinSpacing,
160
+ scrollDuration: blockProps.scrollDuration,
161
+ parallaxDistance: blockProps.parallaxDistance ?? 120,
162
+ parallaxDirection: blockProps.parallaxDirection ?? 'up',
163
+ markers: blockProps.showScrollDebugMarkers,
164
+ }}
165
+ >
166
+ {blockElement}
167
+ </ParallaxWrapper>
168
+ </section>
169
+ </AnimatePresence>
170
+ )
171
+ }
172
+
173
+ if (isStickyFade) {
174
+ return (
175
+ <AnimatePresence key={key} mode="sync">
176
+ <section
177
+ {...sharedDataProps}
178
+ className={cn('m-0 renderblock-container sticky overflow-visible', sectionId)}
179
+ style={{
180
+ top: `${stickyTopOffset}px`,
181
+ zIndex: stickyZIndex,
182
+ ...sectionStyle,
183
+ }}
184
+ >
185
+ <FadeOnScroll
186
+ startFade={
187
+ blockProps.useAbsoluteScroll
188
+ ? (blockProps.fadeStartPixels ?? 1000)
189
+ : (blockProps.fadeStartProgress ?? 0)
190
+ }
191
+ endFade={
192
+ blockProps.useAbsoluteScroll
193
+ ? (blockProps.fadeEndPixels ?? 2000)
194
+ : (blockProps.fadeEndProgress ?? 0.75)
195
+ }
196
+ finalOpacity={blockProps.fadeFinalOpacity ?? 0}
197
+ offsetStart={blockProps.fadeOffsetStart ?? 'start 50%'}
198
+ offsetEnd={blockProps.fadeOffsetEnd ?? 'start 10%'}
199
+ useAbsoluteScroll={blockProps.useAbsoluteScroll ?? false}
200
+ >
201
+ {blockElement}
202
+ </FadeOnScroll>
203
+ </section>
204
+ </AnimatePresence>
205
+ )
206
+ }
207
+
208
+ if (isSticky) {
209
+ return (
210
+ <AnimatePresence key={key} mode="sync">
211
+ <section
212
+ {...sharedDataProps}
213
+ className={cn('m-0 renderblock-container sticky overflow-visible', sectionId)}
214
+ style={{
215
+ top: `${stickyTopOffset}px`,
216
+ zIndex: stickyZIndex,
217
+ ...sectionStyle,
218
+ }}
219
+ >
220
+ {blockElement}
221
+ </section>
222
+ </AnimatePresence>
223
+ )
224
+ }
225
+
226
+ return (
227
+ <AnimatePresence key={key} mode="sync">
228
+ <section
229
+ {...sharedDataProps}
230
+ className={cn(
231
+ 'm-0 renderblock-container relative overflow-visible',
232
+ isNineCanvasBlock && 'ninecanvas',
233
+ sectionId,
234
+ )}
235
+ style={sectionStyle}
236
+ >
237
+ {blockElement}
238
+ </section>
239
+ </AnimatePresence>
240
+ )
241
+ })}
242
+ </>
243
+ )
244
+ }
@@ -0,0 +1,6 @@
1
+ // Demo-only child registry. The original HZ registry is retained in source/.
2
+ import { Content } from '../Content/config'
3
+ import { DDCarouselA } from '../DD-Carousel-A/config'
4
+ import { DDProgress } from '../DD-Progress/config'
5
+ import { RawHtmlBlock } from '../rawHtml/config'
6
+ export const allowedCanvasBlocks = [Content, DDCarouselA, DDProgress, RawHtmlBlock]
@@ -0,0 +1,8 @@
1
+ import dynamic from 'next/dynamic'
2
+ import type { ComponentType } from 'react'
3
+ export const blockComponents: Record<string, ComponentType<any>> = {
4
+ content: dynamic(() => import('../Content/Component').then((m) => m.ContentBlock)),
5
+ 'DD-Carousel-A': dynamic(() => import('../DD-Carousel-A/Component').then((m) => m.DDCarouselA)),
6
+ 'DD-Progress': dynamic(() => import('../DD-Progress/Component').then((m) => m.DDProgress)),
7
+ rawHtml: dynamic(() => import('../rawHtml/Component').then((m) => m.RawHtmlBlock)),
8
+ }
@@ -0,0 +1,316 @@
1
+ 'use client'
2
+ import * as React from 'react'
3
+ import { getClientPerformanceMode } from '../utilities/webglCleanup'
4
+
5
+ export type CanvasPanel = {
6
+ id: string
7
+ x: number
8
+ y: number
9
+ label: string
10
+ content?: React.ReactNode
11
+ }
12
+
13
+ type CanvasGridProps = {
14
+ activeCanvas: CanvasPanel
15
+ canvases: CanvasPanel[]
16
+ onCanvasChange: (canvasId: string) => void
17
+ }
18
+
19
+ export function CanvasGrid({ activeCanvas, canvases, onCanvasChange }: CanvasGridProps) {
20
+ const trackRef = React.useRef<HTMLDivElement>(null)
21
+ const touchStartRef = React.useRef<{
22
+ x: number
23
+ y: number
24
+ lastY: number
25
+ target: EventTarget | null
26
+ canvasGestureAllowed: boolean
27
+ scrolled?: boolean
28
+ } | null>(null)
29
+ const activeContentRef = React.useRef<HTMLDivElement | null>(null)
30
+
31
+ React.useEffect(() => {
32
+ const el = activeContentRef.current
33
+ if (!el) return
34
+
35
+ const reducedPerformance = getClientPerformanceMode() === 'reduced'
36
+ if (reducedPerformance) {
37
+ const previousOverflowY = el.style.overflowY
38
+ el.style.overflowY = 'auto'
39
+ window.dispatchEvent(new CustomEvent('locomotive:resize'))
40
+ return () => {
41
+ el.style.overflowY = previousOverflowY
42
+ }
43
+ }
44
+
45
+ let cancelled = false
46
+ let scroll: { destroy: () => void } | undefined
47
+ let secondFrame = 0
48
+
49
+ const firstFrame = window.requestAnimationFrame(() => {
50
+ secondFrame = window.requestAnimationFrame(async () => {
51
+ const { default: LocomotiveScroll } = await import('locomotive-scroll')
52
+ if (cancelled) return
53
+
54
+ scroll = new LocomotiveScroll({
55
+ lenisOptions: { wrapper: el, content: el, eventsTarget: el, lerp: 0.08 },
56
+ })
57
+ })
58
+ })
59
+
60
+ return () => {
61
+ cancelled = true
62
+ window.cancelAnimationFrame(firstFrame)
63
+ window.cancelAnimationFrame(secondFrame)
64
+ scroll?.destroy()
65
+ }
66
+ }, [activeCanvas.id])
67
+
68
+ React.useEffect(() => {
69
+ const track = trackRef.current
70
+ if (!track) return
71
+
72
+ const getActivePanel = (target: EventTarget | null) => {
73
+ if (!(target instanceof Element)) return null
74
+
75
+ const panel = target.closest<HTMLElement>('.hzCanvasPanel--active')
76
+ return panel && track.contains(panel) ? panel : null
77
+ }
78
+
79
+ const getScrollableRegions = (panel: HTMLElement, target: EventTarget | null) => {
80
+ const regions: HTMLElement[] = []
81
+ let element =
82
+ target instanceof HTMLElement
83
+ ? target
84
+ : target instanceof Element
85
+ ? target.parentElement
86
+ : null
87
+
88
+ while (element && element !== panel) {
89
+ const { overflowY } = window.getComputedStyle(element)
90
+ const canOverflow = /^(auto|scroll|overlay)$/.test(overflowY)
91
+
92
+ if (canOverflow && element.scrollHeight > element.clientHeight) {
93
+ regions.push(element)
94
+ }
95
+
96
+ element = element.parentElement
97
+ }
98
+
99
+ const canvasContent = panel.querySelector<HTMLElement>('.hzCanvasContent')
100
+ if (canvasContent && !regions.includes(canvasContent)) regions.push(canvasContent)
101
+
102
+ return regions
103
+ }
104
+
105
+ const scrollElement = (element: HTMLElement, deltaY: number) => {
106
+ const maxScrollTop = element.scrollHeight - element.clientHeight
107
+ if (maxScrollTop <= 0) return false
108
+
109
+ const nextScrollTop = Math.min(Math.max(element.scrollTop + deltaY, 0), maxScrollTop)
110
+ if (nextScrollTop === element.scrollTop) return false
111
+
112
+ element.scrollTop = nextScrollTop
113
+ return true
114
+ }
115
+
116
+ const scrollFromTarget = (panel: HTMLElement, target: EventTarget | null, deltaY: number) =>
117
+ getScrollableRegions(panel, target).some((region) => scrollElement(region, deltaY))
118
+
119
+ const interactiveGestureSelector = [
120
+ '.carousel',
121
+ '.carouselList',
122
+ '.carouselNav',
123
+ '[class*="carousel-"]',
124
+ '[class*="Carousel"]',
125
+ '.embla',
126
+ '.embla__viewport',
127
+ '.embla__container',
128
+ '.swiper',
129
+ '.swiper-container',
130
+ '.swiper-wrapper',
131
+ '.swiper-slide',
132
+ '.ultimate-embla-carousel',
133
+ '.ultimate-embla-viewport',
134
+ '.ultimate-embla-container',
135
+ '[data-carousel-id]',
136
+ '[data-projects-slider-scroll-scene]',
137
+ '[data-ninecanvas-gesture-owner]',
138
+ 'button',
139
+ 'a',
140
+ 'input',
141
+ 'select',
142
+ 'textarea',
143
+ '[role="button"]',
144
+ '[role="slider"]',
145
+ '[contenteditable="true"]',
146
+ ].join(',')
147
+
148
+ const canUseCanvasGesture = (panel: HTMLElement, target: EventTarget | null) => {
149
+ if (!(target instanceof Element)) return false
150
+
151
+ const gestureOwner = target.closest(interactiveGestureSelector)
152
+ return !gestureOwner || !panel.contains(gestureOwner)
153
+ }
154
+
155
+ const getAdjacentCanvas = (deltaX: number, deltaY: number) => {
156
+ const absX = Math.abs(deltaX)
157
+ const absY = Math.abs(deltaY)
158
+ const xOffset = absX > absY ? (deltaX < 0 ? 1 : -1) : 0
159
+ const yOffset = absY >= absX ? (deltaY > 0 ? -1 : 1) : 0
160
+
161
+ return canvases.find(
162
+ (canvas) => canvas.x === activeCanvas.x + xOffset && canvas.y === activeCanvas.y + yOffset,
163
+ )
164
+ }
165
+
166
+ const changeCanvasFromTouch = (deltaX: number, deltaY: number) => {
167
+ const nextCanvas = getAdjacentCanvas(deltaX, deltaY)
168
+ if (!nextCanvas) return false
169
+
170
+ onCanvasChange(nextCanvas.id)
171
+ return true
172
+ }
173
+
174
+ const notifyCanvasScrollDown = (deltaY: number) => {
175
+ if (deltaY > 0) {
176
+ window.dispatchEvent(new CustomEvent('hz:canvas-scroll-down'))
177
+ }
178
+ }
179
+
180
+ const handleWheel = (event: WheelEvent) => {
181
+ const panel = getActivePanel(event.target)
182
+ if (!panel) return
183
+
184
+ const deltaMultiplier =
185
+ event.deltaMode === WheelEvent.DOM_DELTA_LINE
186
+ ? 16
187
+ : event.deltaMode === WheelEvent.DOM_DELTA_PAGE
188
+ ? panel.clientHeight
189
+ : 1
190
+
191
+ const deltaY = event.deltaY * deltaMultiplier
192
+
193
+ if (!scrollFromTarget(panel, event.target, deltaY)) return
194
+
195
+ notifyCanvasScrollDown(deltaY)
196
+
197
+ event.preventDefault()
198
+ event.stopPropagation()
199
+ }
200
+
201
+ const handleTouchStart = (event: TouchEvent) => {
202
+ const panel = getActivePanel(event.target)
203
+ const touch = event.touches[0]
204
+ if (!panel || !touch) return
205
+
206
+ touchStartRef.current = {
207
+ x: touch.clientX,
208
+ y: touch.clientY,
209
+ lastY: touch.clientY,
210
+ target: event.target,
211
+ canvasGestureAllowed: canUseCanvasGesture(panel, event.target),
212
+ }
213
+ }
214
+
215
+ const handleTouchMove = (event: TouchEvent) => {
216
+ const panel = getActivePanel(event.target)
217
+ const touch = event.touches[0]
218
+ const touchStart = touchStartRef.current
219
+ if (!panel || !touchStart || !touch) return
220
+
221
+ const deltaY = touchStart.lastY - touch.clientY
222
+ touchStart.lastY = touch.clientY
223
+
224
+ if (scrollFromTarget(panel, event.target, deltaY)) {
225
+ touchStart.scrolled = true
226
+ notifyCanvasScrollDown(deltaY)
227
+
228
+ event.preventDefault()
229
+ event.stopPropagation()
230
+ }
231
+ }
232
+
233
+ const handleTouchEnd = (event: TouchEvent) => {
234
+ const panel =
235
+ getActivePanel(event.target) ?? getActivePanel(touchStartRef.current?.target ?? null)
236
+ const touchStart = touchStartRef.current
237
+ touchStartRef.current = null
238
+
239
+ if (!panel || !touchStart || !touchStart.canvasGestureAllowed) return
240
+
241
+ const touch = event.changedTouches[0]
242
+ if (!touch) return
243
+
244
+ const deltaX = touch.clientX - touchStart.x
245
+ const deltaY = touchStart.y - touch.clientY
246
+ const absX = Math.abs(deltaX)
247
+ const absY = Math.abs(deltaY)
248
+ const isHorizontalSwipe = absX >= 56 && absX > absY * 1.25
249
+ const isVerticalSwipe = absY >= 56 && absY > absX * 1.25
250
+
251
+ if (!isHorizontalSwipe && !isVerticalSwipe) return
252
+ // A vertical gesture consumed by nested scrolling must not also change panels.
253
+ if (isVerticalSwipe && touchStart.scrolled) return
254
+
255
+ if (changeCanvasFromTouch(deltaX, deltaY)) {
256
+ event.preventDefault()
257
+ event.stopPropagation()
258
+ }
259
+ }
260
+
261
+ const handleTouchCancel = () => {
262
+ touchStartRef.current = null
263
+ }
264
+ track.addEventListener('wheel', handleWheel, { passive: false })
265
+ track.addEventListener('touchstart', handleTouchStart, { passive: true })
266
+ track.addEventListener('touchmove', handleTouchMove, { passive: false })
267
+ track.addEventListener('touchend', handleTouchEnd, { passive: false })
268
+ track.addEventListener('touchcancel', handleTouchCancel, { passive: true })
269
+
270
+ return () => {
271
+ track.removeEventListener('wheel', handleWheel)
272
+ track.removeEventListener('touchstart', handleTouchStart)
273
+ track.removeEventListener('touchmove', handleTouchMove)
274
+ track.removeEventListener('touchend', handleTouchEnd)
275
+ track.removeEventListener('touchcancel', handleTouchCancel)
276
+ }
277
+ }, [activeCanvas, canvases, onCanvasChange])
278
+
279
+ return (
280
+ <div ref={trackRef} className={`hzCanvasTrack hzCanvasTrack--${activeCanvas.id}`}>
281
+ {canvases.map((canvas) => {
282
+ const isActive = canvas.id === activeCanvas.id
283
+
284
+ return (
285
+ <section
286
+ aria-label={canvas.label}
287
+ aria-hidden={!isActive}
288
+ inert={!isActive}
289
+ className={[
290
+ 'hzCanvasPanel',
291
+ isActive ? 'hzCanvasPanel--active' : '',
292
+ `hzCanvasPanel--x-${canvas.x === -1 ? 'neg1' : canvas.x === 1 ? 'pos1' : '0'}`,
293
+ `hzCanvasPanel--y-${canvas.y === -1 ? 'neg1' : canvas.y === 1 ? 'pos1' : '0'}`,
294
+ ]
295
+ .filter(Boolean)
296
+ .join(' ')}
297
+ data-active={isActive ? 'true' : undefined}
298
+ key={canvas.id}
299
+ onClick={() => onCanvasChange(canvas.id)}
300
+ >
301
+ <div
302
+ ref={isActive ? activeContentRef : undefined}
303
+ className="hzCanvasContent"
304
+ data-canvas-content={canvas.id}
305
+ data-scroll-container={isActive ? true : undefined}
306
+ onClick={(event) => event.stopPropagation()}
307
+ >
308
+ {canvas.content ||
309
+ (isActive ? <p className="hzCanvasEmpty">{canvas.label}</p> : null)}
310
+ </div>
311
+ </section>
312
+ )
313
+ })}
314
+ </div>
315
+ )
316
+ }