@wix/site-service-interactions 1.0.12 → 1.0.14

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 (102) hide show
  1. package/cjs/build/definition/index.d.ts +19 -3
  2. package/cjs/build/definition/index.js.map +1 -1
  3. package/cjs/build/implementations/InteractionsScriptElement.d.ts +1 -1
  4. package/cjs/build/implementations/InteractionsScriptElement.js.map +1 -1
  5. package/cjs/build/implementations/configEntries.d.ts +13 -0
  6. package/cjs/build/implementations/configEntries.js +78 -0
  7. package/cjs/build/implementations/configEntries.js.map +1 -0
  8. package/cjs/build/implementations/coreInteractions.d.ts +2 -2
  9. package/cjs/build/implementations/coreInteractions.js +50 -23
  10. package/cjs/build/implementations/coreInteractions.js.map +1 -1
  11. package/cjs/build/implementations/interactions.js +1 -1
  12. package/cjs/build/implementations/interactions.js.map +1 -1
  13. package/cjs/build/implementations/interactionsExternal.js +1 -1
  14. package/cjs/build/implementations/interactionsExternal.js.map +1 -1
  15. package/cjs/build/implementations/interactionsService.d.ts +5 -4
  16. package/cjs/build/implementations/interactionsService.js.map +1 -1
  17. package/cjs/build/implementations/reviveScriptElement.js.map +1 -1
  18. package/cjs/build/implementations/v2/InteractionsScriptElement.d.ts +2 -2
  19. package/cjs/build/implementations/v2/InteractionsScriptElement.js.map +1 -1
  20. package/cjs/build/implementations/v2/authoredStyles.d.ts +2 -0
  21. package/cjs/build/implementations/v2/authoredStyles.js +90 -0
  22. package/cjs/build/implementations/v2/authoredStyles.js.map +1 -0
  23. package/cjs/build/implementations/v2/cdnUrls.d.ts +2 -1
  24. package/cjs/build/implementations/v2/cdnUrls.js +3 -2
  25. package/cjs/build/implementations/v2/cdnUrls.js.map +1 -1
  26. package/cjs/build/implementations/v2/clickTriggerCss.d.ts +1 -1
  27. package/cjs/build/implementations/v2/clickTriggerCss.js +1 -1
  28. package/cjs/build/implementations/v2/clickTriggerCss.js.map +1 -1
  29. package/cjs/build/implementations/v2/configCss.d.ts +1 -1
  30. package/cjs/build/implementations/v2/configCss.js +3 -36
  31. package/cjs/build/implementations/v2/configCss.js.map +1 -1
  32. package/cjs/build/implementations/v2/configStyleHeadContent.js +8 -6
  33. package/cjs/build/implementations/v2/configStyleHeadContent.js.map +1 -1
  34. package/cjs/build/implementations/v2/interactionsScriptContent.js +18 -7
  35. package/cjs/build/implementations/v2/interactionsScriptContent.js.map +1 -1
  36. package/cjs/build/implementations/v2/motionPresets.d.ts +14 -0
  37. package/cjs/build/implementations/v2/motionPresets.js +76 -0
  38. package/cjs/build/implementations/v2/motionPresets.js.map +1 -0
  39. package/cjs/build/service.d.ts +1 -1
  40. package/cjs/build/types/index.d.ts +1 -1
  41. package/cjs/package.json +1 -1
  42. package/definition/package.json +1 -1
  43. package/dist/definition/index.d.ts +19 -3
  44. package/dist/definition/index.js.map +1 -1
  45. package/dist/implementations/InteractionsScriptElement.d.ts +1 -1
  46. package/dist/implementations/InteractionsScriptElement.js.map +1 -1
  47. package/dist/implementations/configEntries.d.ts +13 -0
  48. package/dist/implementations/configEntries.js +71 -0
  49. package/dist/implementations/configEntries.js.map +1 -0
  50. package/dist/implementations/coreInteractions.d.ts +2 -2
  51. package/dist/implementations/coreInteractions.js +50 -23
  52. package/dist/implementations/coreInteractions.js.map +1 -1
  53. package/dist/implementations/interactions.js +1 -1
  54. package/dist/implementations/interactions.js.map +1 -1
  55. package/dist/implementations/interactionsExternal.js +1 -1
  56. package/dist/implementations/interactionsExternal.js.map +1 -1
  57. package/dist/implementations/interactionsService.d.ts +5 -4
  58. package/dist/implementations/interactionsService.js.map +1 -1
  59. package/dist/implementations/reviveScriptElement.js.map +1 -1
  60. package/dist/implementations/v2/InteractionsScriptElement.d.ts +2 -2
  61. package/dist/implementations/v2/InteractionsScriptElement.js.map +1 -1
  62. package/dist/implementations/v2/authoredStyles.d.ts +2 -0
  63. package/dist/implementations/v2/authoredStyles.js +86 -0
  64. package/dist/implementations/v2/authoredStyles.js.map +1 -0
  65. package/dist/implementations/v2/cdnUrls.d.ts +2 -1
  66. package/dist/implementations/v2/cdnUrls.js +2 -1
  67. package/dist/implementations/v2/cdnUrls.js.map +1 -1
  68. package/dist/implementations/v2/clickTriggerCss.d.ts +1 -1
  69. package/dist/implementations/v2/clickTriggerCss.js +1 -1
  70. package/dist/implementations/v2/clickTriggerCss.js.map +1 -1
  71. package/dist/implementations/v2/configCss.d.ts +1 -1
  72. package/dist/implementations/v2/configCss.js +2 -2
  73. package/dist/implementations/v2/configCss.js.map +1 -1
  74. package/dist/implementations/v2/configStyleHeadContent.js +8 -6
  75. package/dist/implementations/v2/configStyleHeadContent.js.map +1 -1
  76. package/dist/implementations/v2/interactionsScriptContent.js +19 -8
  77. package/dist/implementations/v2/interactionsScriptContent.js.map +1 -1
  78. package/dist/implementations/v2/motionPresets.d.ts +14 -0
  79. package/dist/implementations/v2/motionPresets.js +39 -0
  80. package/dist/implementations/v2/motionPresets.js.map +1 -0
  81. package/dist/service.d.ts +1 -1
  82. package/dist/types/index.d.ts +1 -1
  83. package/implementations/package.json +1 -1
  84. package/package.json +24 -23
  85. package/src/definition/index.ts +21 -4
  86. package/src/implementations/InteractionsScriptElement.tsx +17 -17
  87. package/src/implementations/configEntries.ts +103 -0
  88. package/src/implementations/coreInteractions.ts +102 -68
  89. package/src/implementations/interactions.ts +8 -8
  90. package/src/implementations/interactionsExternal.ts +11 -11
  91. package/src/implementations/interactionsService.ts +8 -8
  92. package/src/implementations/reviveScriptElement.ts +8 -8
  93. package/src/implementations/v2/InteractionsScriptElement.tsx +23 -28
  94. package/src/implementations/v2/authoredStyles.ts +103 -0
  95. package/src/implementations/v2/cdnUrls.ts +4 -1
  96. package/src/implementations/v2/clickTriggerCss.ts +26 -26
  97. package/src/implementations/v2/configCss.ts +6 -7
  98. package/src/implementations/v2/configStyleHeadContent.ts +9 -6
  99. package/src/implementations/v2/interactionsScriptContent.ts +36 -24
  100. package/src/implementations/v2/motionPresets.ts +60 -0
  101. package/src/types/index.ts +1 -1
  102. package/types/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { Interact } from '@wix/interact'
2
- import type { InteractConfig } from '@wix/interact'
2
+ import type { InteractConfig as InteractV1Config } from '@wix/interact'
3
3
  import { DeviceInfoDefinition } from '@wix/site-service-device-info/definition'
4
4
  import { ExperimentsDefinition } from '@wix/site-service-experiments/definition'
5
5
  // Deliberately the deprecated pages service. Thunderbolt registers the site-service-pages page
@@ -11,91 +11,125 @@ import { HeadAppenderDefinition } from '@wix/site-service-head-appender/definiti
11
11
  import { HeadContentType } from '@wix/site-service-head-appender/types'
12
12
  import { ProvideComponentServiceDefinition } from '@wix/services-manager-react/core-services/provide-component'
13
13
  import type { ServicesManager } from '@wix/services-manager/types'
14
+ import type { InteractConfig, InteractConfigEntry, IInteractionsServiceConfig } from '../definition'
14
15
  import { createInteractionsScriptElement } from './InteractionsScriptElement'
15
16
  import { createV2InteractionsScriptElement } from './v2/InteractionsScriptElement'
16
17
  import { getV2ConfigCss } from './v2/configCss'
17
18
  import { getV2ConfigStyleHeadContent } from './v2/configStyleHeadContent'
18
19
  import { getV2ModulePreloadHeadContent } from './v2/interactionsScriptContent'
20
+ import { getAuthoredStylesCss } from './v2/authoredStyles'
21
+ import {
22
+ getInteractConfigEntries,
23
+ getInteractiveKeys,
24
+ getResolvedConfigEntries,
25
+ resolveInteractKeyConflicts,
26
+ } from './configEntries'
19
27
 
20
- const initInteract = (config: InteractConfig, isReducedMotion: boolean) => {
21
- // Bundled runtime is v1. v2 on the client is the CDN loader.
22
- Interact.forceReducedMotion = isReducedMotion
23
- Interact.setup({
24
- scrollOptionsGetter: () => globalThis.document.getElementById('site-root'),
25
- allowA11yTriggers: true,
26
- viewEnter: {
27
- useSafeViewEnter: true,
28
- },
29
- })
30
- Interact.create(config)
28
+ /** An entry without interactions contributes CSS only; nothing has to load Interact for it. */
29
+ const getRuntimeConfigs = (entries: InteractConfigEntry[]): InteractConfig[] =>
30
+ entries.map(({ interact }) => interact).filter(({ interactions }) => interactions.length > 0)
31
+
32
+ const initInteract = (entries: InteractConfigEntry[], isReducedMotion: boolean) => {
33
+ // Bundled runtime is v1. v2 on the client is the CDN loader.
34
+ Interact.forceReducedMotion = isReducedMotion
35
+ Interact.setup({
36
+ scrollOptionsGetter: () => globalThis.document.getElementById('site-root'),
37
+ allowA11yTriggers: true,
38
+ viewEnter: {
39
+ useSafeViewEnter: true,
40
+ },
41
+ })
42
+ for (const { interact } of entries) {
43
+ Interact.create(interact as InteractV1Config)
44
+ }
45
+ }
46
+
47
+ const getV2HeadContent = (entries: InteractConfigEntry[]): string => {
48
+ const generatedCss = entries
49
+ .map(({ interact }) => getV2ConfigCss(interact).trim())
50
+ .filter(Boolean)
51
+ .join('\n')
52
+ const css = [getAuthoredStylesCss(entries).trim(), generatedCss].filter(Boolean).join('\n')
53
+
54
+ return (
55
+ (getRuntimeConfigs(entries).length ? getV2ModulePreloadHeadContent() : '') +
56
+ (css ? getV2ConfigStyleHeadContent(css) : '')
57
+ )
31
58
  }
32
59
 
33
60
  const initRuntime = (
34
- config: InteractConfig,
35
- isReducedMotion: boolean,
36
- getService: ServicesManager['getService'],
37
- useV2: boolean
61
+ entries: InteractConfigEntry[],
62
+ isReducedMotion: boolean,
63
+ getService: ServicesManager['getService'],
64
+ useV2: boolean
38
65
  ) => {
39
- if (!config.interactions?.length) {
40
- return
41
- }
66
+ const runtimeConfigs = getRuntimeConfigs(entries)
67
+ if (!runtimeConfigs.length) {
68
+ return
69
+ }
42
70
 
43
- const pageId = getService(PagesDefinition).currentPageId()
44
- if (!pageId) {
45
- return
46
- }
71
+ const pageId = getService(PagesDefinition).currentPageId()
72
+ if (!pageId) {
73
+ return
74
+ }
47
75
 
48
- const { addTopComponent } = getService(ProvideComponentServiceDefinition)
76
+ const { addTopComponent } = getService(ProvideComponentServiceDefinition)
49
77
 
50
- const interactionsScript = useV2
51
- ? createV2InteractionsScriptElement(config, isReducedMotion, pageId)
52
- : createInteractionsScriptElement(config, isReducedMotion, pageId)
78
+ if (!useV2) {
79
+ // v1 serializes a single config and has no path for authored-style CSS, so anything beyond
80
+ // that would be silently dropped.
81
+ if (entries.length > 1 || entries.some(({ styles }) => styles?.length)) {
82
+ throw new Error('Multiple interactions config entries and authored styles require Interact v2.')
83
+ }
84
+ addTopComponent(createInteractionsScriptElement(runtimeConfigs[0], isReducedMotion, pageId))
85
+ return
86
+ }
53
87
 
54
- addTopComponent(interactionsScript)
88
+ addTopComponent(createV2InteractionsScriptElement(runtimeConfigs, isReducedMotion, pageId))
55
89
  }
56
90
 
57
- const getInteractiveComps = (interactions: InteractConfig['interactions']) =>
58
- interactions.reduce((acc, { key, effects }) => {
59
- // add trigger comp
60
- acc.add(key)
61
- // add reaction comps
62
- // there must be a key
63
- effects?.forEach(({ key }) => acc.add(key!))
64
-
65
- return acc
66
- }, new Set<string>())
91
+ const getInteractiveComps = (entries: InteractConfigEntry[]): Set<string> => {
92
+ const keys = new Set<string>()
93
+ for (const entry of entries) {
94
+ getInteractiveKeys(entry).forEach((key) => keys.add(key))
95
+ }
96
+ return keys
97
+ }
67
98
 
68
- export const coreInteractions = (config: InteractConfig, getService: ServicesManager['getService']) => {
69
- const { reducedMotion: isReducedMotion } = getService(DeviceInfoDefinition)
70
- const { isOpen } = getService(ExperimentsDefinition)
99
+ export const coreInteractions = (config: IInteractionsServiceConfig, getService: ServicesManager['getService']) => {
100
+ const { reducedMotion: isReducedMotion } = getService(DeviceInfoDefinition)
101
+ const { isOpen } = getService(ExperimentsDefinition)
102
+ const useV2 = isOpen('specs.thunderbolt.vsm_interactions_v2')
103
+ // The v1 runtime has no preset registration and doesn't understand LEGACY_* names; skip
104
+ // aliasing on those paths so named reaction effects remain resolvable.
105
+ const entries = resolveInteractKeyConflicts((useV2 ? getResolvedConfigEntries : getInteractConfigEntries)(config))
71
106
 
72
- if (isOpen('specs.thunderbolt.vsm_interactions_v2')) {
73
- // Both belong in the head: the preload links only help while the document is still parsing,
74
- // and the generated CSS hides elements before their entrance effect, so it has to apply
75
- // before the body renders.
76
- if (!process.env.browser) {
77
- const css = getV2ConfigCss(config).trim()
78
- // GENERAL rather than setHead's AFTER_SECURITY default: thunderbolt only reads the
79
- // after-security bucket on its SEO path, so the default drops this on every normal
80
- // request. GENERAL reaches the document as raw markup before `</head>`.
81
- getService(HeadAppenderDefinition).setHead(
82
- getV2ModulePreloadHeadContent() + (css ? getV2ConfigStyleHeadContent(css) : ''),
83
- HeadContentType.GENERAL
84
- )
85
- }
107
+ if (useV2) {
108
+ // Both belong in the head: the preload links only help while the document is still parsing,
109
+ // and the generated CSS hides elements before their entrance effect, so it has to apply
110
+ // before the body renders.
111
+ if (!process.env.browser) {
112
+ const headContent = getV2HeadContent(entries)
113
+ // GENERAL rather than setHead's AFTER_SECURITY default: thunderbolt only reads the
114
+ // after-security bucket on its SEO path, so the default drops this on every normal
115
+ // request. GENERAL reaches the document as raw markup before `</head>`.
116
+ if (headContent) {
117
+ getService(HeadAppenderDefinition).setHead(headContent, HeadContentType.GENERAL)
118
+ }
119
+ }
86
120
 
87
- initRuntime(config, isReducedMotion, getService, true)
88
- } else if (isOpen('specs.thunderbolt.interactionsInSsr')) {
89
- // Emits the config and loader scripts. Works in both environments: SSR serves markup the
90
- // parser executes, and on the client the loader's ref revives it into a script that runs —
91
- // which is what pages rendered only on the client (password-protected / members-only,
92
- // client-side navigation) need, since they never get a server-rendered document.
93
- initRuntime(config, isReducedMotion, getService, false)
94
- } else {
95
- initInteract(config, isReducedMotion)
96
- }
121
+ initRuntime(entries, isReducedMotion, getService, true)
122
+ } else if (isOpen('specs.thunderbolt.interactionsInSsr')) {
123
+ // Emits the config and loader scripts. Works in both environments: SSR serves markup the
124
+ // parser executes, and on the client the loader's ref revives it into a script that runs —
125
+ // which is what pages rendered only on the client (password-protected / members-only,
126
+ // client-side navigation) need, since they never get a server-rendered document.
127
+ initRuntime(entries, isReducedMotion, getService, false)
128
+ } else {
129
+ initInteract(entries, isReducedMotion)
130
+ }
97
131
 
98
- return {
99
- interactiveComps: getInteractiveComps(config.interactions),
100
- }
132
+ return {
133
+ interactiveComps: getInteractiveComps(entries),
134
+ }
101
135
  }
@@ -3,16 +3,16 @@ import type { IInteractionsService, IInteractionsServiceConfig } from '../defini
3
3
  import { coreInteractions } from './coreInteractions'
4
4
 
5
5
  export const createInteractions = (
6
- config: IInteractionsServiceConfig,
7
- getService: ServicesManager['getService']
6
+ config: IInteractionsServiceConfig,
7
+ getService: ServicesManager['getService']
8
8
  ): IInteractionsService => {
9
- const { interactiveComps } = coreInteractions(config, getService)
9
+ const { interactiveComps } = coreInteractions(config, getService)
10
10
 
11
- const getConfigPath = (path: string) => path.replace(/\[[^\]]+]/g, '[]')
11
+ const getConfigPath = (path: string) => path.replace(/\[[^\]]+]/g, '[]')
12
12
 
13
- const hasInteractions: IInteractionsService['hasInteractions'] = path => interactiveComps.has(getConfigPath(path))
13
+ const hasInteractions: IInteractionsService['hasInteractions'] = (path) => interactiveComps.has(getConfigPath(path))
14
14
 
15
- return {
16
- hasInteractions,
17
- }
15
+ return {
16
+ hasInteractions,
17
+ }
18
18
  }
@@ -5,21 +5,21 @@ import { coreInteractions } from './coreInteractions'
5
5
  import { hasClickTriggerCss, wixInteractElementCss } from './stylesToAdd'
6
6
 
7
7
  export const createInteractionsExternal = (
8
- config: IInteractionsServiceConfig,
9
- getService: ServicesManager['getService']
8
+ config: IInteractionsServiceConfig,
9
+ getService: ServicesManager['getService']
10
10
  ): IInteractionsService => {
11
- const provideCssService = getService(ProvideCssDefinition)
11
+ const provideCssService = getService(ProvideCssDefinition)
12
12
 
13
- const styles = [wixInteractElementCss, hasClickTriggerCss]
14
- const cssToAdd = styles.join('\n')
13
+ const styles = [wixInteractElementCss, hasClickTriggerCss]
14
+ const cssToAdd = styles.join('\n')
15
15
 
16
- provideCssService.addStyle(cssToAdd)
16
+ provideCssService.addStyle(cssToAdd)
17
17
 
18
- const { interactiveComps } = coreInteractions(config, getService)
18
+ const { interactiveComps } = coreInteractions(config, getService)
19
19
 
20
- const hasInteractions: IInteractionsService['hasInteractions'] = path => interactiveComps.has(path)
20
+ const hasInteractions: IInteractionsService['hasInteractions'] = (path) => interactiveComps.has(path)
21
21
 
22
- return {
23
- hasInteractions,
24
- }
22
+ return {
23
+ hasInteractions,
24
+ }
25
25
  }
@@ -6,15 +6,15 @@ import { createInteractions } from './interactions'
6
6
  import { createInteractionsExternal } from './interactionsExternal'
7
7
 
8
8
  export const InteractionsService = implementStaticService(
9
- InteractionsDefinition,
10
- ({ config, getService }: StaticServiceFactoryOpts<IInteractionsServiceConfig>) => {
11
- return createInteractions(config, getService)
12
- }
9
+ InteractionsDefinition,
10
+ ({ config, getService }: StaticServiceFactoryOpts<IInteractionsServiceConfig>) => {
11
+ return createInteractions(config, getService)
12
+ }
13
13
  )
14
14
 
15
15
  export const InteractionsServiceExternal = implementStaticService(
16
- InteractionsDefinition,
17
- ({ config, getService }: StaticServiceFactoryOpts<IInteractionsServiceConfig>) => {
18
- return createInteractionsExternal(config, getService)
19
- }
16
+ InteractionsDefinition,
17
+ ({ config, getService }: StaticServiceFactoryOpts<IInteractionsServiceConfig>) => {
18
+ return createInteractionsExternal(config, getService)
19
+ }
20
20
  )
@@ -16,14 +16,14 @@
16
16
  * connected, and `connect()` returns early for them, so only the first instance serves a key.
17
17
  */
18
18
  export const reviveScriptElement = (rendered: HTMLScriptElement | null) => {
19
- if (!rendered?.isConnected) {
20
- return
21
- }
19
+ if (!rendered?.isConnected) {
20
+ return
21
+ }
22
22
 
23
- const executable = globalThis.document.createElement('script')
24
- executable.type = rendered.type
25
- executable.id = rendered.id
26
- executable.textContent = rendered.textContent
23
+ const executable = globalThis.document.createElement('script')
24
+ executable.type = rendered.type
25
+ executable.id = rendered.id
26
+ executable.textContent = rendered.textContent
27
27
 
28
- rendered.replaceWith(executable)
28
+ rendered.replaceWith(executable)
29
29
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import type { FunctionComponent } from 'react'
3
- import type { InteractConfig } from '@wix/interact'
3
+ import type { InteractConfig } from '@wix/interact-v2'
4
4
  import { reviveScriptElement } from '../reviveScriptElement'
5
5
  import { getV2InteractionsScriptContent } from './interactionsScriptContent'
6
6
 
@@ -10,33 +10,28 @@ const DANGEROUSLY_SET_INNER_HTML = ['dangerously', 'Set', 'Inner', 'HTML']
10
10
  * The config is written as raw markup, so a `</script` inside any string value would close the
11
11
  * element early. `\u003c` is valid JSON that `JSON.parse` reads back as `<`.
12
12
  */
13
- const serializeConfigForScriptElement = (config: InteractConfig): string =>
14
- JSON.stringify(config).replace(/</g, '\\u003c')
13
+ const serializeConfigForScriptElement = (config: InteractConfig | InteractConfig[]): string =>
14
+ JSON.stringify(config).replace(/</g, '\\u003c')
15
15
 
16
16
  export const createV2InteractionsScriptElement =
17
- (config: InteractConfig, isReducedMotion: boolean, pageId: string): FunctionComponent =>
18
- () => {
19
- // This ID is also interpolated into raw module-script text. Percent-encoding removes
20
- // script delimiters and template-literal syntax while keeping the ID deterministic.
21
- const safePageId = encodeURIComponent(pageId)
22
- const configId = `interactions-config-${safePageId}`
23
- const interactionsScriptContent = getV2InteractionsScriptContent(configId, isReducedMotion)
24
- const configProps = {
25
- [DANGEROUSLY_SET_INNER_HTML.join('')]: { __html: serializeConfigForScriptElement(config) },
26
- }
27
- const loaderProps = {
28
- [DANGEROUSLY_SET_INNER_HTML.join('')]: { __html: interactionsScriptContent },
29
- }
17
+ (config: InteractConfig | InteractConfig[], isReducedMotion: boolean, pageId: string): FunctionComponent =>
18
+ () => {
19
+ // This ID is also interpolated into raw module-script text. Percent-encoding removes
20
+ // script delimiters and template-literal syntax while keeping the ID deterministic.
21
+ const safePageId = encodeURIComponent(pageId)
22
+ const configId = `interactions-config-${safePageId}`
23
+ const interactionsScriptContent = getV2InteractionsScriptContent(configId, isReducedMotion)
24
+ const configProps = {
25
+ [DANGEROUSLY_SET_INNER_HTML.join('')]: { __html: serializeConfigForScriptElement(config) },
26
+ }
27
+ const loaderProps = {
28
+ [DANGEROUSLY_SET_INNER_HTML.join('')]: { __html: interactionsScriptContent },
29
+ }
30
30
 
31
- return (
32
- <>
33
- <script type="application/json" id={configId} {...configProps} />
34
- <script
35
- type="module"
36
- id={`interactions-loader-${safePageId}`}
37
- ref={reviveScriptElement}
38
- {...loaderProps}
39
- />
40
- </>
41
- )
42
- }
31
+ return (
32
+ <>
33
+ <script type="application/json" id={configId} {...configProps} />
34
+ <script type="module" id={`interactions-loader-${safePageId}`} ref={reviveScriptElement} {...loaderProps} />
35
+ </>
36
+ )
37
+ }
@@ -0,0 +1,103 @@
1
+ import type { InteractConfigEntry, InteractStyleRule } from '../../definition'
2
+
3
+ const SELECTOR_TOKEN = /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[()[\]]|,/g
4
+
5
+ const escapeCssString = (value: string): string =>
6
+ value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\a ').replace(/\r/g, '\\d ').replace(/\f/g, '\\c ')
7
+
8
+ const splitSelectorList = (selector: string): string[] => {
9
+ const parts: string[] = []
10
+ let depth = 0
11
+ let start = 0
12
+
13
+ SELECTOR_TOKEN.lastIndex = 0
14
+ for (let match = SELECTOR_TOKEN.exec(selector); match; match = SELECTOR_TOKEN.exec(selector)) {
15
+ const token = match[0]
16
+ if (token === '(' || token === '[') {
17
+ depth++
18
+ } else if (token === ')' || token === ']') {
19
+ depth--
20
+ } else if (token === ',' && depth === 0) {
21
+ parts.push(selector.slice(start, match.index))
22
+ start = match.index + 1
23
+ }
24
+ }
25
+
26
+ parts.push(selector.slice(start))
27
+ return parts.map((part) => part.trim()).filter(Boolean)
28
+ }
29
+
30
+ const indentCssBlock = (css: string): string =>
31
+ css
32
+ .split('\n')
33
+ .map((line) => (line ? ` ${line}` : line))
34
+ .join('\n')
35
+
36
+ const renderProperties = (properties: Record<string, string>): string =>
37
+ Object.entries(properties)
38
+ .map(([property, value]) => ` ${property}: ${value};`)
39
+ .join('\n')
40
+
41
+ const wrapWithConditions = (block: string, rule: InteractStyleRule): string => {
42
+ let result = block
43
+ if (rule['@container']) {
44
+ result = `@container ${rule['@container']} {\n${indentCssBlock(result)}\n}`
45
+ }
46
+ if (rule['@media']) {
47
+ result = `@media ${rule['@media']} {\n${indentCssBlock(result)}\n}`
48
+ }
49
+ if (rule['@supports']) {
50
+ result = `@supports ${rule['@supports']} {\n${indentCssBlock(result)}\n}`
51
+ }
52
+ return result
53
+ }
54
+
55
+ const buildRuleSelector = (namespace: string, rule: InteractStyleRule): string => {
56
+ const scopeWrapper = `[data-interact-key="${escapeCssString(namespace)}"]`
57
+ const scopeRoot = `${scopeWrapper} > :first-child`
58
+
59
+ if (!rule.key) {
60
+ if (!rule.selector) {
61
+ return scopeRoot
62
+ }
63
+ return splitSelectorList(rule.selector)
64
+ .map((selector) => `${scopeRoot} ${selector}`)
65
+ .join(', ')
66
+ }
67
+
68
+ const escapedKey = escapeCssString(rule.key)
69
+ const keyWrapper = rule.key === namespace ? scopeWrapper : `${scopeWrapper} [data-interact-key="${escapedKey}"]`
70
+ const keyRoot = `${keyWrapper} > :first-child`
71
+ if (!rule.selector) {
72
+ return keyRoot
73
+ }
74
+
75
+ const separator = /^\s/.test(rule.selector) ? ' ' : ''
76
+ return splitSelectorList(rule.selector)
77
+ .map((selector) => `${keyRoot}${separator}${selector}`)
78
+ .join(', ')
79
+ }
80
+
81
+ const getEntryStylesCss = ({ namespace, styles = [] }: InteractConfigEntry): string => {
82
+ if (!styles.length) {
83
+ return ''
84
+ }
85
+ if (!namespace) {
86
+ throw new Error('Interactions config entries with authored styles must define a namespace.')
87
+ }
88
+
89
+ return styles
90
+ .map((rule) => {
91
+ const properties = renderProperties(rule.properties)
92
+ if (!properties) {
93
+ return ''
94
+ }
95
+ const block = `${buildRuleSelector(namespace, rule)} {\n${properties}\n}`
96
+ return wrapWithConditions(block, rule)
97
+ })
98
+ .filter(Boolean)
99
+ .join('\n\n')
100
+ }
101
+
102
+ export const getAuthoredStylesCss = (entries: InteractConfigEntry[]): string =>
103
+ entries.map(getEntryStylesCss).filter(Boolean).join('\n\n')
@@ -1,3 +1,6 @@
1
1
  export const INTERACT_V2_IMPORT_URL = 'https://cdn.jsdelivr.net/npm/@wix/interact@2.5.14/dist/es/web.min.js'
2
2
 
3
- export const WIX_MOTION_PRESETS_IMPORT_URL = 'https://cdn.jsdelivr.net/npm/@wix/wix-motion-presets@1.0.0/+esm'
3
+ export const LEGACY_WIX_MOTION_PRESETS_IMPORT_URL = 'https://cdn.jsdelivr.net/npm/@wix/wix-motion-presets@1.2.0/+esm'
4
+
5
+ export const MOTION_PRESETS_IMPORT_URL =
6
+ 'https://cdn.jsdelivr.net/npm/@wix/motion-presets@1.0.7/dist/browser/motion-presets.min.js'
@@ -1,4 +1,4 @@
1
- import type { InteractConfig, Interaction } from '@wix/interact'
1
+ import type { InteractConfig, Interaction } from '@wix/interact-v2'
2
2
 
3
3
  const escapeCssAttr = (value: string): string => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
4
4
 
@@ -7,38 +7,38 @@ const escapeCssAttr = (value: string): string => value.replace(/\\/g, '\\\\').re
7
7
  * plus the `[data-interact-key="…"]` prefix from `CSSRuleToString`.
8
8
  */
9
9
  const getInteractionSourceSelector = (
10
- interaction: Pick<Interaction, 'key' | 'selector' | 'listContainer' | 'listItemSelector'>
10
+ interaction: Pick<Interaction, 'key' | 'selector' | 'listContainer' | 'listItemSelector'>
11
11
  ): string => {
12
- const { key, selector, listContainer, listItemSelector } = interaction
13
- const attrSelector = `[data-interact-key="${escapeCssAttr(key)}"]`
12
+ const { key, selector, listContainer, listItemSelector } = interaction
13
+ const attrSelector = `[data-interact-key="${escapeCssAttr(key)}"]`
14
14
 
15
- if (listContainer) {
16
- const itemFilter = listItemSelector ? ` > ${listItemSelector}` : ''
17
- const inner = selector ? `${listContainer}${itemFilter} ${selector}` : `${listContainer}${itemFilter || ' > *'}`
15
+ if (listContainer) {
16
+ const itemFilter = listItemSelector ? ` > ${listItemSelector}` : ''
17
+ const inner = selector ? `${listContainer}${itemFilter} ${selector}` : `${listContainer}${itemFilter || ' > *'}`
18
18
 
19
- return `${attrSelector} ${inner}`
20
- }
19
+ return `${attrSelector} ${inner}`
20
+ }
21
21
 
22
- if (selector) {
23
- return `${attrSelector} ${selector}`
24
- }
22
+ if (selector) {
23
+ return `${attrSelector} ${selector}`
24
+ }
25
25
 
26
- return `${attrSelector} > :first-child`
26
+ return `${attrSelector} > :first-child`
27
27
  }
28
28
 
29
29
  /** Emits `cursor: pointer` for click interaction triggers (`'activate' || 'click'`), one rule per unique source selector. */
30
30
  export const getClickTriggerCss = (config: InteractConfig): string => {
31
- const selectors = [
32
- ...new Set(
33
- config.interactions
34
- .filter(({ trigger }) => trigger === 'activate' || trigger === 'click')
35
- .map(getInteractionSourceSelector)
36
- ),
37
- ]
38
-
39
- if (!selectors.length) {
40
- return ''
41
- }
42
-
43
- return selectors.map(selector => `${selector} {\ncursor: pointer;\n}`).join('\n')
31
+ const selectors = [
32
+ ...new Set(
33
+ config.interactions
34
+ .filter(({ trigger }) => trigger === 'activate' || trigger === 'click')
35
+ .map(getInteractionSourceSelector)
36
+ ),
37
+ ]
38
+
39
+ if (!selectors.length) {
40
+ return ''
41
+ }
42
+
43
+ return selectors.map((selector) => `${selector} {\ncursor: pointer;\n}`).join('\n')
44
44
  }
@@ -1,19 +1,18 @@
1
1
  import { generate, Interact } from '@wix/interact-v2'
2
- import type { InteractConfig } from '@wix/interact'
3
- import type { InteractConfig as InteractV2Config } from '@wix/interact-v2'
4
- import * as motionPresets from '@wix/wix-motion-presets'
2
+ import type { InteractConfig } from '@wix/interact-v2'
5
3
  import { getClickTriggerCss } from './clickTriggerCss'
4
+ import { motionPresets } from './motionPresets'
6
5
 
7
6
  type PresetRegistry = Parameters<typeof Interact.registerEffects>[0]
8
7
 
9
8
  // Interact's registry is process-global. This module is only pulled into the server bundle
10
- // (`process.env.browser` DCE), so the namespace is registered once per Node process.
9
+ // (`process.env.browser` DCE), so both namespaces are registered once per Node process.
11
10
  Interact.registerEffects(motionPresets as unknown as PresetRegistry)
12
11
 
13
12
  /** Emit animation FOUC CSS and click-trigger cursor rules. */
14
13
  export const getV2ConfigCss = (config: InteractConfig): string => {
15
- const animationCss = generate(config as InteractV2Config, true).trim()
16
- const clickCss = getClickTriggerCss(config).trim()
14
+ const animationCss = generate(config, true).trim()
15
+ const clickCss = getClickTriggerCss(config).trim()
17
16
 
18
- return [animationCss, clickCss].filter(Boolean).join('\n')
17
+ return [animationCss, clickCss].filter(Boolean).join('\n')
19
18
  }
@@ -1,12 +1,15 @@
1
1
  /**
2
- * Head content is a raw HTML string, so anything tag-like in the generated CSS either closes the
3
- * style element early or trips the head appender's script-tag guard, which throws. Nothing in CSS
4
- * opens a tag: `<` appears only in media range queries, followed by a space or a digit. Cutting
5
- * from the first `<` that could start a tag leaves valid CSS untouched.
2
+ * Head content is a raw HTML string. CSS values may contain tag-like text, but an actual closing
3
+ * style tag still terminates the raw-text element, and HeadAppender rejects `<script` substrings.
4
+ * Keep other CSS intact: drop content after a closing style tag and break script-like sequences.
6
5
  */
6
+ const STYLE_END_TAG = /<\/style(?=[\t\n\f\r />])/i
7
+ const SCRIPT_LIKE = /<script/gi
8
+
7
9
  const sanitizeCssForStyleElement = (css: string): string => {
8
- const tagIndex = css.search(/<[a-zA-Z/]/)
9
- return tagIndex === -1 ? css : css.slice(0, tagIndex)
10
+ const styleEndTagIndex = css.search(STYLE_END_TAG)
11
+ const withoutStyleBreakout = styleEndTagIndex === -1 ? css : css.slice(0, styleEndTagIndex)
12
+ return withoutStyleBreakout.replace(SCRIPT_LIKE, '\\3c script')
10
13
  }
11
14
 
12
15
  export const getV2ConfigStyleHeadContent = (css: string): string => `<style>${sanitizeCssForStyleElement(css)}</style>`