@nordcraft/runtime 1.0.88 → 1.0.90
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/dist/custom-element.main.esm.js +24 -24
- package/dist/custom-element.main.esm.js.map +4 -4
- package/dist/page.main.esm.js +3 -3
- package/dist/page.main.esm.js.map +4 -4
- package/dist/src/components/createComponent.js +37 -36
- package/dist/src/components/createComponent.js.map +1 -1
- package/dist/src/components/createElement.js +0 -2
- package/dist/src/components/createElement.js.map +1 -1
- package/dist/src/components/createNode.js +12 -4
- package/dist/src/components/createNode.js.map +1 -1
- package/dist/src/components/createNode.test.d.ts +1 -0
- package/dist/src/components/createNode.test.js +608 -0
- package/dist/src/components/createNode.test.js.map +1 -0
- package/dist/src/components/meta.d.ts +3 -0
- package/dist/src/components/meta.js +18 -0
- package/dist/src/components/meta.js.map +1 -0
- package/dist/src/components/meta.test.d.ts +1 -0
- package/dist/src/components/meta.test.js +80 -0
- package/dist/src/components/meta.test.js.map +1 -0
- package/dist/src/components/renderComponent.js +2 -4
- package/dist/src/components/renderComponent.js.map +1 -1
- package/dist/src/editor/postMessageToEditor.d.ts +2 -0
- package/dist/src/editor/postMessageToEditor.js +4 -0
- package/dist/src/editor/postMessageToEditor.js.map +1 -0
- package/dist/src/editor-preview.main.js +12 -13
- package/dist/src/editor-preview.main.js.map +1 -1
- package/dist/src/page.main.js +46 -59
- package/dist/src/page.main.js.map +1 -1
- package/dist/src/styles/CustomPropertyStyleSheet.d.ts +0 -1
- package/dist/src/styles/CustomPropertyStyleSheet.js +1 -1
- package/dist/src/styles/CustomPropertyStyleSheet.js.map +1 -1
- package/dist/src/styles/CustomPropertyStyleSheet.test.js +2 -5
- package/dist/src/styles/CustomPropertyStyleSheet.test.js.map +1 -1
- package/dist/src/utils/BatchQueue.d.ts +1 -0
- package/dist/src/utils/BatchQueue.js +2 -1
- package/dist/src/utils/BatchQueue.js.map +1 -1
- package/dist/src/utils/getComponent.d.ts +5 -0
- package/dist/src/utils/getComponent.js +8 -0
- package/dist/src/utils/getComponent.js.map +1 -0
- package/dist/src/utils/getComponent.test.d.ts +1 -0
- package/dist/src/utils/getComponent.test.js +24 -0
- package/dist/src/utils/getComponent.test.js.map +1 -0
- package/dist/src/utils/markSelectedElement.d.ts +1 -0
- package/dist/src/utils/markSelectedElement.js +9 -0
- package/dist/src/utils/markSelectedElement.js.map +1 -0
- package/dist/src/utils/subscribeCustomProperty.d.ts +3 -3
- package/dist/src/utils/subscribeCustomProperty.js +2 -3
- package/dist/src/utils/subscribeCustomProperty.js.map +1 -1
- package/package.json +3 -3
- package/src/components/createComponent.ts +57 -51
- package/src/components/createElement.ts +0 -2
- package/src/components/createNode.test.ts +686 -0
- package/src/components/createNode.ts +17 -6
- package/src/components/meta.test.ts +90 -0
- package/src/components/meta.ts +23 -0
- package/src/components/renderComponent.ts +2 -4
- package/src/editor/postMessageToEditor.ts +5 -0
- package/src/editor-preview.main.ts +12 -15
- package/src/page.main.ts +47 -59
- package/src/styles/CustomPropertyStyleSheet.test.ts +2 -7
- package/src/styles/CustomPropertyStyleSheet.ts +1 -2
- package/src/utils/BatchQueue.ts +2 -2
- package/src/utils/getComponent.test.ts +29 -0
- package/src/utils/getComponent.ts +15 -0
- package/src/utils/markSelectedElement.ts +8 -0
- package/src/utils/subscribeCustomProperty.ts +1 -5
|
@@ -9,6 +9,7 @@ import { toBoolean } from '@nordcraft/core/dist/utils/util'
|
|
|
9
9
|
import type { Signal } from '../signal/signal'
|
|
10
10
|
import { signal } from '../signal/signal'
|
|
11
11
|
import type { ComponentContext } from '../types'
|
|
12
|
+
import { getComponent } from '../utils/getComponent'
|
|
12
13
|
import { ensureEfficientOrdering, getNextSiblingElement } from '../utils/nodes'
|
|
13
14
|
import { createComponent } from './createComponent'
|
|
14
15
|
import { createElement } from './createElement'
|
|
@@ -48,11 +49,13 @@ export function createNode({
|
|
|
48
49
|
...props,
|
|
49
50
|
}),
|
|
50
51
|
]
|
|
51
|
-
case 'component':
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
case 'component': {
|
|
53
|
+
const isLocalComponent =
|
|
54
|
+
getComponent(
|
|
55
|
+
node.name,
|
|
56
|
+
ctx.components,
|
|
57
|
+
ctx.env.runtime !== 'preview',
|
|
58
|
+
) !== undefined
|
|
56
59
|
return createComponent({
|
|
57
60
|
node: { ...node, id }, // we need the node id for instance classes
|
|
58
61
|
...props,
|
|
@@ -63,6 +66,7 @@ export function createNode({
|
|
|
63
66
|
},
|
|
64
67
|
parentElement,
|
|
65
68
|
})
|
|
69
|
+
}
|
|
66
70
|
case 'text':
|
|
67
71
|
return [createText({ ...props, node })]
|
|
68
72
|
case 'slot':
|
|
@@ -168,6 +172,7 @@ export function createNode({
|
|
|
168
172
|
|
|
169
173
|
function repeat(): ReadonlyArray<Element | Text> {
|
|
170
174
|
let firstRun = true
|
|
175
|
+
let lifetimeSize = 0
|
|
171
176
|
let repeatItems = new Map<
|
|
172
177
|
string | number,
|
|
173
178
|
{
|
|
@@ -291,7 +296,13 @@ export function createNode({
|
|
|
291
296
|
node: node!,
|
|
292
297
|
id,
|
|
293
298
|
dataSignal: childDataSignal,
|
|
294
|
-
|
|
299
|
+
// Note that we use the lifetimeSize to ensure that no two items can ever get the same path.
|
|
300
|
+
// Consider a list [A, B, C]:
|
|
301
|
+
// - Update list to [B]
|
|
302
|
+
// - Update list to [A, C, B]
|
|
303
|
+
// Now C and B would have the same path `(1)` if we only used the index or Key, as B would have kept its reference, but the others would be recreated.
|
|
304
|
+
// With lifetimeSize, the keys would be A(3), B(1), C(4) - all unique.
|
|
305
|
+
path: Key === '0' ? path : `${path}(${++lifetimeSize})`,
|
|
295
306
|
ctx,
|
|
296
307
|
namespace,
|
|
297
308
|
parentElement,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { MetaEntry } from '@nordcraft/core/dist/component/component.types'
|
|
2
|
+
import {
|
|
3
|
+
functionFormula,
|
|
4
|
+
valueFormula,
|
|
5
|
+
} from '@nordcraft/core/dist/formula/formulaUtils'
|
|
6
|
+
import { describe, expect, test } from 'bun:test'
|
|
7
|
+
import { getDynamicMetaEntries } from './meta'
|
|
8
|
+
|
|
9
|
+
describe('getDynamicMetaEntries', () => {
|
|
10
|
+
test('returns an empty object if meta is undefined or null', () => {
|
|
11
|
+
expect(getDynamicMetaEntries(undefined)).toEqual({})
|
|
12
|
+
expect(getDynamicMetaEntries(null)).toEqual({})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('returns an empty object if meta is an empty object', () => {
|
|
16
|
+
expect(getDynamicMetaEntries({})).toEqual({})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('returns only entries with dynamic content', () => {
|
|
20
|
+
const meta: Record<string, MetaEntry> = {
|
|
21
|
+
'static-entry': {
|
|
22
|
+
tag: 'meta' as any,
|
|
23
|
+
content: valueFormula('static'),
|
|
24
|
+
},
|
|
25
|
+
'dynamic-entry': {
|
|
26
|
+
tag: 'meta' as any,
|
|
27
|
+
content: functionFormula('dynamic'),
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
const result = getDynamicMetaEntries(meta)
|
|
31
|
+
expect(result).toEqual({ 'dynamic-entry': meta['dynamic-entry'] })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('returns only entries with dynamic attributes', () => {
|
|
35
|
+
const meta: Record<string, MetaEntry> = {
|
|
36
|
+
'static-entry': {
|
|
37
|
+
tag: 'meta' as any,
|
|
38
|
+
attrs: {
|
|
39
|
+
name: valueFormula('static'),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
'dynamic-entry': {
|
|
43
|
+
tag: 'meta' as any,
|
|
44
|
+
attrs: {
|
|
45
|
+
name: functionFormula('dynamic'),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
const result = getDynamicMetaEntries(meta)
|
|
50
|
+
expect(result).toEqual({ 'dynamic-entry': meta['dynamic-entry'] })
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('returns entry if content is dynamic and attributes are static', () => {
|
|
54
|
+
const meta: Record<string, MetaEntry> = {
|
|
55
|
+
'dynamic-entry': {
|
|
56
|
+
tag: 'meta' as any,
|
|
57
|
+
content: functionFormula('dynamic'),
|
|
58
|
+
attrs: {
|
|
59
|
+
name: valueFormula('static'),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
const result = getDynamicMetaEntries(meta)
|
|
64
|
+
expect(result).toEqual({ 'dynamic-entry': meta['dynamic-entry'] })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('returns entry if content is missing and one attribute is dynamic', () => {
|
|
68
|
+
const meta: Record<string, MetaEntry> = {
|
|
69
|
+
'dynamic-entry': {
|
|
70
|
+
tag: 'meta' as any,
|
|
71
|
+
attrs: {
|
|
72
|
+
name: valueFormula('static'),
|
|
73
|
+
property: functionFormula('dynamic'),
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
const result = getDynamicMetaEntries(meta)
|
|
78
|
+
expect(result).toEqual({ 'dynamic-entry': meta['dynamic-entry'] })
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('handles entries with no content or attributes', () => {
|
|
82
|
+
const meta: Record<string, MetaEntry> = {
|
|
83
|
+
'empty-entry': {
|
|
84
|
+
tag: 'meta' as any,
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
const result = getDynamicMetaEntries(meta)
|
|
88
|
+
expect(result).toEqual({})
|
|
89
|
+
})
|
|
90
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MetaEntry } from '@nordcraft/core/dist/component/component.types'
|
|
2
|
+
import type { Nullable } from '@nordcraft/core/dist/types'
|
|
3
|
+
import { isDefined } from '@nordcraft/core/dist/utils/util'
|
|
4
|
+
|
|
5
|
+
export const getDynamicMetaEntries = (
|
|
6
|
+
meta?: Nullable<Record<string, MetaEntry>>,
|
|
7
|
+
): Record<string, MetaEntry> => {
|
|
8
|
+
if (!meta) {
|
|
9
|
+
return {}
|
|
10
|
+
}
|
|
11
|
+
const dynamicMetaEntries: Record<string, MetaEntry> = {}
|
|
12
|
+
for (const key in meta) {
|
|
13
|
+
const entry = meta[key]
|
|
14
|
+
if (isDefined(entry.content) && entry.content.type !== 'value') {
|
|
15
|
+
dynamicMetaEntries[key] = entry
|
|
16
|
+
} else if (
|
|
17
|
+
Object.values(entry.attrs ?? {}).some((a) => a.type !== 'value')
|
|
18
|
+
) {
|
|
19
|
+
dynamicMetaEntries[key] = entry
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return dynamicMetaEntries
|
|
23
|
+
}
|
|
@@ -111,8 +111,7 @@ export function renderComponent({
|
|
|
111
111
|
.subscribe((props) => {
|
|
112
112
|
if (prev) {
|
|
113
113
|
component.onAttributeChange?.actions?.forEach((action) => {
|
|
114
|
-
|
|
115
|
-
handleAction(
|
|
114
|
+
void handleAction(
|
|
116
115
|
action,
|
|
117
116
|
dataSignal.get(),
|
|
118
117
|
ctx,
|
|
@@ -143,8 +142,7 @@ export function renderComponent({
|
|
|
143
142
|
})
|
|
144
143
|
}
|
|
145
144
|
component.onLoad?.actions?.forEach((action) => {
|
|
146
|
-
|
|
147
|
-
handleAction(action, dataSignal.get(), ctx)
|
|
145
|
+
void handleAction(action, dataSignal.get(), ctx)
|
|
148
146
|
})
|
|
149
147
|
})
|
|
150
148
|
return rootElem
|
|
@@ -63,10 +63,10 @@ import { introspectApiRequest } from './editor/graphql'
|
|
|
63
63
|
import { isInputTarget } from './editor/input'
|
|
64
64
|
import { updateComponentLinks } from './editor/links'
|
|
65
65
|
import { getRectData } from './editor/overlay'
|
|
66
|
+
import { postMessageToEditor } from './editor/postMessageToEditor'
|
|
66
67
|
import {
|
|
67
68
|
TextNodeComputedStyles,
|
|
68
69
|
type DragState,
|
|
69
|
-
type EditorPostMessageType,
|
|
70
70
|
type NordcraftPreviewEvent,
|
|
71
71
|
} from './editor/types'
|
|
72
72
|
import { handleAction } from './events/handleAction'
|
|
@@ -81,6 +81,7 @@ import type {
|
|
|
81
81
|
} from './types'
|
|
82
82
|
import { createFormulaCache } from './utils/createFormulaCache'
|
|
83
83
|
import { getThemeSignal } from './utils/getThemeSignal'
|
|
84
|
+
import { markSelectedElement } from './utils/markSelectedElement'
|
|
84
85
|
import { getNodeAndAncestors, isNodeOrAncestorConditional } from './utils/nodes'
|
|
85
86
|
import { rectHasPoint } from './utils/rectHasPoint'
|
|
86
87
|
import {
|
|
@@ -457,6 +458,7 @@ export const createRoot = (
|
|
|
457
458
|
updateConditionalElements()
|
|
458
459
|
|
|
459
460
|
const node = getDOMNodeFromNodeId(selectedNodeId)
|
|
461
|
+
markSelectedElement(node)
|
|
460
462
|
const element =
|
|
461
463
|
component?.nodes?.[node?.getAttribute('data-node-id') ?? '']
|
|
462
464
|
if (
|
|
@@ -875,10 +877,12 @@ export const createRoot = (
|
|
|
875
877
|
'--editor-timeline-timing-function',
|
|
876
878
|
)
|
|
877
879
|
document.body.style.removeProperty('--editor-timeline-fill-mode')
|
|
880
|
+
document.body.removeAttribute('data-animating')
|
|
878
881
|
update()
|
|
879
882
|
return
|
|
880
883
|
}
|
|
881
884
|
|
|
885
|
+
document.body.setAttribute('data-animating', 'true')
|
|
882
886
|
document.body.style.setProperty(
|
|
883
887
|
'--editor-timeline-position',
|
|
884
888
|
`${time}s`,
|
|
@@ -902,17 +906,13 @@ export const createRoot = (
|
|
|
902
906
|
document.head.appendChild(styleTag)
|
|
903
907
|
}
|
|
904
908
|
|
|
905
|
-
// Set the animation styles for self and repeated nodes, but pause for all others
|
|
906
|
-
// TODO: Consider if we should set all other animations to follow the current timeline time, by setting animation-delay with paused
|
|
907
909
|
styleTag.innerHTML = `
|
|
908
|
-
[data-id] {
|
|
909
|
-
animation-play-state: paused !important;
|
|
910
|
-
}
|
|
911
|
-
[data-id="${animationState.animatedElementId}"], [data-id="${animationState.animatedElementId}"] ~ [data-id^="${animationState.animatedElementId}("] {
|
|
910
|
+
body[data-mode="design"] [data-id="${animationState.animatedElementId}"], body[data-mode="design"] [data-id="${animationState.animatedElementId}"] ~ [data-id^="${animationState.animatedElementId}("] {
|
|
912
911
|
animation: preview_timeline 1s paused normal !important;
|
|
913
912
|
animation-fill-mode: var(--editor-timeline-fill-mode) !important;
|
|
914
913
|
animation-timing-function: var(--editor-timeline-timing-function) !important;
|
|
915
914
|
animation-delay: calc(0s - var(--editor-timeline-position)) !important;
|
|
915
|
+
animation-play-state: paused !important;
|
|
916
916
|
}`
|
|
917
917
|
}
|
|
918
918
|
})
|
|
@@ -1547,6 +1547,7 @@ export const createRoot = (
|
|
|
1547
1547
|
scrollStateRestorer((nodeId) =>
|
|
1548
1548
|
document.querySelector(`[data-id="${nodeId}"]`),
|
|
1549
1549
|
)
|
|
1550
|
+
markSelectedElement(getDOMNodeFromNodeId(selectedNodeId))
|
|
1550
1551
|
}
|
|
1551
1552
|
|
|
1552
1553
|
const createContext = (
|
|
@@ -1710,7 +1711,7 @@ const insertHeadTags = (
|
|
|
1710
1711
|
document.createRange().createContextualFragment(`
|
|
1711
1712
|
<link
|
|
1712
1713
|
data-meta-id="${id}"
|
|
1713
|
-
${Object.entries(entry.attrs)
|
|
1714
|
+
${Object.entries(entry.attrs ?? {})
|
|
1714
1715
|
.map(([key, value]) => `${key}="${applyFormula(value, context)}"`)
|
|
1715
1716
|
.join(' ')}
|
|
1716
1717
|
/>
|
|
@@ -1722,10 +1723,10 @@ const insertHeadTags = (
|
|
|
1722
1723
|
document.createRange().createContextualFragment(`
|
|
1723
1724
|
<script
|
|
1724
1725
|
data-meta-id="${id}"
|
|
1725
|
-
${Object.entries(entry.attrs)
|
|
1726
|
+
${Object.entries(entry.attrs ?? {})
|
|
1726
1727
|
.map(([key, value]) => `${key}="${applyFormula(value, context)}"`)
|
|
1727
1728
|
.join(' ')}
|
|
1728
|
-
|
|
1729
|
+
>${applyFormula(entry.content ?? '', context)}</script>
|
|
1729
1730
|
`),
|
|
1730
1731
|
)
|
|
1731
1732
|
case HeadTagTypes.Style:
|
|
@@ -1734,7 +1735,7 @@ const insertHeadTags = (
|
|
|
1734
1735
|
document.createRange().createContextualFragment(`
|
|
1735
1736
|
<style
|
|
1736
1737
|
data-meta-id="${id}"
|
|
1737
|
-
${Object.entries(entry.attrs)
|
|
1738
|
+
${Object.entries(entry.attrs ?? {})
|
|
1738
1739
|
.map(([key, value]) => `${key}="${applyFormula(value, context)}"`)
|
|
1739
1740
|
.join(' ')}
|
|
1740
1741
|
>
|
|
@@ -1913,10 +1914,6 @@ const registerFormulas = (
|
|
|
1913
1914
|
window.toddle.formulas[packageName ?? window.__toddle.project] = formulas
|
|
1914
1915
|
}
|
|
1915
1916
|
|
|
1916
|
-
const postMessageToEditor = (message: EditorPostMessageType) => {
|
|
1917
|
-
window.parent?.postMessage(message, '*')
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
1917
|
let _themeRootSignal = null as Signal<string | null> | null
|
|
1921
1918
|
function setupThemeSubscription(
|
|
1922
1919
|
component: Component,
|
package/src/page.main.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
Toddle,
|
|
17
17
|
} from '@nordcraft/core/dist/types'
|
|
18
18
|
import { mapObject } from '@nordcraft/core/dist/utils/collections'
|
|
19
|
+
import { VOID_HTML_ELEMENTS } from '@nordcraft/core/dist/utils/html'
|
|
19
20
|
import { isDefined } from '@nordcraft/core/dist/utils/util'
|
|
20
21
|
import * as libActions from '@nordcraft/std-lib/dist/actions'
|
|
21
22
|
import * as libFormulas from '@nordcraft/std-lib/dist/formulas'
|
|
@@ -24,6 +25,7 @@ import { match } from 'path-to-regexp'
|
|
|
24
25
|
import { isContextApiV2 } from './api/apiUtils'
|
|
25
26
|
import { createLegacyAPI } from './api/createAPI'
|
|
26
27
|
import { createAPI } from './api/createAPIv2'
|
|
28
|
+
import { getDynamicMetaEntries } from './components/meta'
|
|
27
29
|
import { renderComponent } from './components/renderComponent'
|
|
28
30
|
import { isContextProvider } from './context/isContextProvider'
|
|
29
31
|
import { initLogState, registerComponentToLogState } from './debug/logState'
|
|
@@ -359,22 +361,21 @@ const setupMetaUpdates = (
|
|
|
359
361
|
component: Component,
|
|
360
362
|
dataSignal: Signal<ComponentData>,
|
|
361
363
|
) => {
|
|
364
|
+
const getFormulaContext = (data: ComponentData) => ({
|
|
365
|
+
data,
|
|
366
|
+
component,
|
|
367
|
+
root: document,
|
|
368
|
+
package: undefined,
|
|
369
|
+
toddle: window.toddle,
|
|
370
|
+
env,
|
|
371
|
+
})
|
|
362
372
|
// Handle dynamic updates of the document language
|
|
363
373
|
const langFormula = component.route?.info?.language?.formula
|
|
364
374
|
const dynamicLang = langFormula && langFormula.type !== 'value'
|
|
365
375
|
if (dynamicLang) {
|
|
366
376
|
dataSignal
|
|
367
|
-
.map
|
|
368
|
-
component
|
|
369
|
-
? applyFormula(langFormula, {
|
|
370
|
-
data: dataSignal.get(),
|
|
371
|
-
component,
|
|
372
|
-
root: document,
|
|
373
|
-
package: undefined,
|
|
374
|
-
toddle: window.toddle,
|
|
375
|
-
env,
|
|
376
|
-
})
|
|
377
|
-
: null,
|
|
377
|
+
.map((data) =>
|
|
378
|
+
component ? applyFormula(langFormula, getFormulaContext(data)) : null,
|
|
378
379
|
)
|
|
379
380
|
.subscribe((newLang) => {
|
|
380
381
|
if (isDefined(newLang) && document.documentElement.lang !== newLang) {
|
|
@@ -388,17 +389,8 @@ const setupMetaUpdates = (
|
|
|
388
389
|
const dynamicTitle = titleFormula && titleFormula.type !== 'value'
|
|
389
390
|
if (dynamicTitle) {
|
|
390
391
|
dataSignal
|
|
391
|
-
.map
|
|
392
|
-
component
|
|
393
|
-
? applyFormula(titleFormula, {
|
|
394
|
-
data: dataSignal.get(),
|
|
395
|
-
component,
|
|
396
|
-
root: document,
|
|
397
|
-
package: undefined,
|
|
398
|
-
toddle: window.toddle,
|
|
399
|
-
env,
|
|
400
|
-
})
|
|
401
|
-
: null,
|
|
392
|
+
.map((data) =>
|
|
393
|
+
component ? applyFormula(titleFormula, getFormulaContext(data)) : null,
|
|
402
394
|
)
|
|
403
395
|
.subscribe((newTitle) => {
|
|
404
396
|
if (isDefined(newTitle) && document.title !== newTitle) {
|
|
@@ -411,19 +403,19 @@ const setupMetaUpdates = (
|
|
|
411
403
|
const meta = component.route?.info?.meta
|
|
412
404
|
const dynamicDescription =
|
|
413
405
|
descriptionFormula && descriptionFormula.type !== 'value'
|
|
414
|
-
const dynamicMetaFormulas =
|
|
415
|
-
|
|
416
|
-
r.attrs ?? {}, // fallback to make sure we don't crash on legacy values
|
|
417
|
-
).some((a) => a.type !== 'value'),
|
|
418
|
-
)
|
|
419
|
-
if (dynamicDescription || dynamicMetaFormulas) {
|
|
406
|
+
const dynamicMetaFormulas = getDynamicMetaEntries(meta)
|
|
407
|
+
if (dynamicDescription || Object.keys(dynamicMetaFormulas).length > 0) {
|
|
420
408
|
const findMetaElement = (name: string) =>
|
|
421
409
|
[...document.getElementsByTagName('meta')].find(
|
|
422
410
|
(el) => el.name === name || el.getAttribute('property') === name,
|
|
423
411
|
) ?? null
|
|
424
412
|
|
|
425
413
|
const updateMetaElement = (
|
|
426
|
-
entry: {
|
|
414
|
+
entry: {
|
|
415
|
+
tag: string
|
|
416
|
+
attrs: Record<string, string>
|
|
417
|
+
content: string | undefined
|
|
418
|
+
},
|
|
427
419
|
id?: string,
|
|
428
420
|
) => {
|
|
429
421
|
let existingElement: HTMLElement | null = null
|
|
@@ -452,19 +444,18 @@ const setupMetaUpdates = (
|
|
|
452
444
|
}
|
|
453
445
|
existingElement!.setAttribute(key, value)
|
|
454
446
|
})
|
|
447
|
+
if (
|
|
448
|
+
typeof entry.content === 'string' &&
|
|
449
|
+
!VOID_HTML_ELEMENTS.includes(entry.tag.toLowerCase())
|
|
450
|
+
) {
|
|
451
|
+
existingElement.textContent = entry.content
|
|
452
|
+
}
|
|
455
453
|
}
|
|
456
454
|
if (dynamicDescription) {
|
|
457
455
|
dataSignal
|
|
458
|
-
.map
|
|
456
|
+
.map((data) =>
|
|
459
457
|
component
|
|
460
|
-
? applyFormula(descriptionFormula,
|
|
461
|
-
data,
|
|
462
|
-
component,
|
|
463
|
-
root: document,
|
|
464
|
-
package: undefined,
|
|
465
|
-
toddle: window.toddle,
|
|
466
|
-
env,
|
|
467
|
-
})
|
|
458
|
+
? applyFormula(descriptionFormula, getFormulaContext(data))
|
|
468
459
|
: null,
|
|
469
460
|
)
|
|
470
461
|
.subscribe((newDescription) => {
|
|
@@ -501,46 +492,43 @@ const setupMetaUpdates = (
|
|
|
501
492
|
property: 'og:description',
|
|
502
493
|
content: newDescription,
|
|
503
494
|
},
|
|
495
|
+
content: undefined,
|
|
504
496
|
})
|
|
505
497
|
}
|
|
506
498
|
}
|
|
507
499
|
})
|
|
508
500
|
}
|
|
509
|
-
if (dynamicMetaFormulas) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
// fallback to make sure we don't crash on legacy values.
|
|
514
|
-
Object.values(entry.attrs ?? {}).some((a) => a.type !== 'value'),
|
|
515
|
-
)
|
|
516
|
-
.forEach(([id, entry]) => {
|
|
501
|
+
if (Object.keys(dynamicMetaFormulas).length > 0) {
|
|
502
|
+
for (const id in dynamicMetaFormulas) {
|
|
503
|
+
const entry = dynamicMetaFormulas[id]
|
|
504
|
+
if (entry) {
|
|
517
505
|
dataSignal
|
|
518
|
-
.map
|
|
506
|
+
.map((data) => {
|
|
507
|
+
const context = getFormulaContext(data)
|
|
519
508
|
// Return the new values for all attributes (we assume they're strings)
|
|
520
509
|
const values = Object.entries(entry.attrs ?? {}).reduce(
|
|
521
510
|
(agg, [key, formula]) =>
|
|
522
511
|
component
|
|
523
512
|
? {
|
|
524
513
|
...agg,
|
|
525
|
-
[key]: applyFormula(formula,
|
|
526
|
-
data,
|
|
527
|
-
component,
|
|
528
|
-
root: document,
|
|
529
|
-
package: undefined,
|
|
530
|
-
toddle: window.toddle,
|
|
531
|
-
env,
|
|
532
|
-
}),
|
|
514
|
+
[key]: applyFormula(formula, context),
|
|
533
515
|
}
|
|
534
516
|
: agg,
|
|
535
517
|
{},
|
|
536
518
|
)
|
|
537
|
-
return
|
|
519
|
+
return {
|
|
520
|
+
attrs: values,
|
|
521
|
+
content: entry.content
|
|
522
|
+
? applyFormula(entry.content, context)
|
|
523
|
+
: undefined,
|
|
524
|
+
}
|
|
538
525
|
})
|
|
539
|
-
.subscribe((attrs) =>
|
|
526
|
+
.subscribe(({ attrs, content }) =>
|
|
540
527
|
// Update the meta tags with the new values
|
|
541
|
-
updateMetaElement({ tag: entry.tag, attrs }, id),
|
|
528
|
+
updateMetaElement({ tag: entry.tag, attrs, content }, id),
|
|
542
529
|
)
|
|
543
|
-
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
544
532
|
}
|
|
545
533
|
}
|
|
546
534
|
}
|
|
@@ -121,7 +121,7 @@ describe('CustomPropertyStyleSheet', () => {
|
|
|
121
121
|
'.my-class { --my-other-property: 512px; }',
|
|
122
122
|
)
|
|
123
123
|
instance.unregisterProperty('.my-class', '--my-other-property')
|
|
124
|
-
expect(instance.getStyleSheet().cssRules
|
|
124
|
+
expect(instance.getStyleSheet().cssRules).toBeEmpty()
|
|
125
125
|
})
|
|
126
126
|
|
|
127
127
|
test('it unregisters a property with media queries', () => {
|
|
@@ -149,11 +149,6 @@ describe('CustomPropertyStyleSheet', () => {
|
|
|
149
149
|
mediaQuery: { 'max-width': '600px' },
|
|
150
150
|
},
|
|
151
151
|
)
|
|
152
|
-
expect(instance.getStyleSheet().cssRules
|
|
153
|
-
`\
|
|
154
|
-
@media (max-width: 600px) {
|
|
155
|
-
.my-class-with-media { }
|
|
156
|
-
}`,
|
|
157
|
-
)
|
|
152
|
+
expect(instance.getStyleSheet().cssRules).toBeEmpty()
|
|
158
153
|
})
|
|
159
154
|
})
|
|
@@ -76,7 +76,6 @@ export class CustomPropertyStyleSheet {
|
|
|
76
76
|
options?: Nullable<{
|
|
77
77
|
mediaQuery?: Nullable<MediaQuery>
|
|
78
78
|
startingStyle?: Nullable<boolean>
|
|
79
|
-
deepClean?: Nullable<boolean>
|
|
80
79
|
}>,
|
|
81
80
|
): void {
|
|
82
81
|
if (!this.ruleMap) {
|
|
@@ -97,7 +96,7 @@ export class CustomPropertyStyleSheet {
|
|
|
97
96
|
|
|
98
97
|
// Cleaning up empty selectors is probably not necessary in production and may have performance implications.
|
|
99
98
|
// However, it is required for the editor-preview as it is a dynamic environment and things may get reordered and canvas reused.
|
|
100
|
-
if (
|
|
99
|
+
if (rule.style.length === 0) {
|
|
101
100
|
this.styleSheet.deleteRule(
|
|
102
101
|
Array.from(this.ruleMap.keys()).indexOf(fullSelector),
|
|
103
102
|
)
|
package/src/utils/BatchQueue.ts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* This is more efficient than processing each callback in a separate requestAnimationFrame due to the overhead.
|
|
4
4
|
*/
|
|
5
5
|
export class BatchQueue {
|
|
6
|
+
constructor() {}
|
|
6
7
|
private batchQueue: Array<() => void> = []
|
|
7
8
|
private isProcessing = false
|
|
8
9
|
private processBatch() {
|
|
9
10
|
if (this.isProcessing) return
|
|
10
11
|
this.isProcessing = true
|
|
11
|
-
|
|
12
|
-
requestAnimationFrame(() => {
|
|
12
|
+
queueMicrotask(() => {
|
|
13
13
|
while (this.batchQueue.length > 0) {
|
|
14
14
|
const callback = this.batchQueue.shift()
|
|
15
15
|
callback?.()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { getComponent } from './getComponent'
|
|
3
|
+
|
|
4
|
+
describe('getComponent', () => {
|
|
5
|
+
const compA = { name: 'A', value: 1 }
|
|
6
|
+
const compB = { name: 'B', value: 2 }
|
|
7
|
+
const compC = { name: 'C', value: 3 }
|
|
8
|
+
|
|
9
|
+
it('caches components after first call and ignores new list on subsequent calls with useCache=true', () => {
|
|
10
|
+
// First call: cache is built from [compA, compB]
|
|
11
|
+
expect(getComponent('A', [compA, compB])).toBe(compA)
|
|
12
|
+
expect(getComponent('B', [compA, compB])).toBe(compB)
|
|
13
|
+
|
|
14
|
+
// Second call: provide a different list, but cache should still return from the original cache
|
|
15
|
+
expect(getComponent('C', [compC])).toBeUndefined()
|
|
16
|
+
expect(getComponent('A', [compC])).toBe(compA)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('does not use cache when useCache is false', () => {
|
|
20
|
+
// First call: should find compA in the list
|
|
21
|
+
expect(getComponent('A', [compA, compB], false)).toBe(compA)
|
|
22
|
+
|
|
23
|
+
// Second call: provide a different list, should now find compC
|
|
24
|
+
expect(getComponent('C', [compC], false)).toBe(compC)
|
|
25
|
+
|
|
26
|
+
// Should not find compA in the new list
|
|
27
|
+
expect(getComponent('A', [compC], false)).toBeUndefined()
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Component } from '@nordcraft/core/dist/component/component.types'
|
|
2
|
+
|
|
3
|
+
let componentMap: Map<string, Component> | null
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Project components is not expected to change during runtime, so we can memoize the components in a map for faster lookup.
|
|
7
|
+
*/
|
|
8
|
+
export const getComponent = (
|
|
9
|
+
key: string,
|
|
10
|
+
components: Component[],
|
|
11
|
+
useCache = true,
|
|
12
|
+
) =>
|
|
13
|
+
useCache
|
|
14
|
+
? (componentMap ??= new Map(components.map((c) => [c.name, c]))).get(key)
|
|
15
|
+
: components.find((c) => c.name === key)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function markSelectedElement(node: Element | null) {
|
|
2
|
+
if (node && !node.hasAttribute('data-selected')) {
|
|
3
|
+
document.querySelectorAll('[data-selected="true"]').forEach((el) => {
|
|
4
|
+
el.removeAttribute('data-selected')
|
|
5
|
+
})
|
|
6
|
+
node.setAttribute('data-selected', 'true')
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -2,10 +2,9 @@ import type { Signal } from '../signal/signal'
|
|
|
2
2
|
|
|
3
3
|
import { CUSTOM_PROPERTIES_STYLESHEET_ID } from '@nordcraft/core/dist/styling/theme.const'
|
|
4
4
|
import type { StyleVariant } from '@nordcraft/core/dist/styling/variantSelector'
|
|
5
|
-
import type { Runtime } from '@nordcraft/core/dist/types'
|
|
6
5
|
import { CustomPropertyStyleSheet } from '../styles/CustomPropertyStyleSheet'
|
|
7
6
|
|
|
8
|
-
let customPropertiesStylesheet: CustomPropertyStyleSheet | undefined
|
|
7
|
+
export let customPropertiesStylesheet: CustomPropertyStyleSheet | undefined
|
|
9
8
|
|
|
10
9
|
export function subscribeCustomProperty({
|
|
11
10
|
selector,
|
|
@@ -13,14 +12,12 @@ export function subscribeCustomProperty({
|
|
|
13
12
|
signal,
|
|
14
13
|
variant,
|
|
15
14
|
root,
|
|
16
|
-
runtime,
|
|
17
15
|
}: {
|
|
18
16
|
selector: string
|
|
19
17
|
customPropertyName: string
|
|
20
18
|
signal: Signal<string>
|
|
21
19
|
variant?: StyleVariant
|
|
22
20
|
root: Document | ShadowRoot
|
|
23
|
-
runtime: Runtime
|
|
24
21
|
}) {
|
|
25
22
|
customPropertiesStylesheet ??= new CustomPropertyStyleSheet(
|
|
26
23
|
root,
|
|
@@ -43,7 +40,6 @@ export function subscribeCustomProperty({
|
|
|
43
40
|
selector,
|
|
44
41
|
customPropertyName,
|
|
45
42
|
{
|
|
46
|
-
deepClean: runtime === 'preview',
|
|
47
43
|
mediaQuery: variant?.mediaQuery,
|
|
48
44
|
startingStyle: variant?.startingStyle,
|
|
49
45
|
},
|