@json-layout/core 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -3
- package/src/compile/index.js +13 -3
- package/src/state/index.js +17 -24
- package/src/state/state-node.js +4 -1
- package/src/state/types.ts +3 -7
- package/src/state/utils/immutable.js +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@json-layout/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Compilation and state management utilities for JSON Layout.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -68,8 +68,7 @@
|
|
|
68
68
|
"debug": "^4.3.4",
|
|
69
69
|
"immer": "^10.0.3",
|
|
70
70
|
"magicast": "^0.3.3",
|
|
71
|
-
"markdown-it": "^13.0.2"
|
|
72
|
-
"mitt": "^3.0.1"
|
|
71
|
+
"markdown-it": "^13.0.2"
|
|
73
72
|
},
|
|
74
73
|
"devDependencies": {
|
|
75
74
|
"@types/debug": "^4.1.7",
|
package/src/compile/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { makeSkeletonTree } from './skeleton-tree.js'
|
|
|
12
12
|
import { resolveLocaleRefs } from './utils/resolve-refs.js'
|
|
13
13
|
import clone from '../utils/clone.js'
|
|
14
14
|
import { standardComponents } from '@json-layout/vocabulary'
|
|
15
|
+
import { shallowEqualArray } from '../state/utils/immutable.js'
|
|
15
16
|
|
|
16
17
|
export { resolveLocaleRefs } from './utils/resolve-refs.js'
|
|
17
18
|
|
|
@@ -36,9 +37,18 @@ const ajvLocalize = /** @type {typeof ajvLocalizeModule.default} */ (ajvLocalize
|
|
|
36
37
|
export const produceCompileOptions = produce((draft, newOptions) => {
|
|
37
38
|
for (const key of ['ajv', 'ajvOptions', 'code', 'markdown', 'markdownItOptions', 'locale', 'messages', 'optionsKeys', 'components']) {
|
|
38
39
|
// @ts-ignore
|
|
39
|
-
if (key in newOptions)
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
if (key in newOptions) {
|
|
41
|
+
// components is problematic because it is an object with nested objects
|
|
42
|
+
// simply compare there keys instead of the whole object
|
|
43
|
+
if (key === 'components' && shallowEqualArray(Object.keys(draft.components ?? []), Object.keys(newOptions.components ?? []))) {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
draft[key] = newOptions[key]
|
|
48
|
+
} else {
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
delete draft[key]
|
|
51
|
+
}
|
|
42
52
|
}
|
|
43
53
|
})
|
|
44
54
|
|
package/src/state/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// eslint-disable-next-line import/no-named-default
|
|
2
|
-
import mittModule from 'mitt'
|
|
3
2
|
import debug from 'debug'
|
|
4
3
|
import { produce } from 'immer'
|
|
5
4
|
import { evalExpression, producePatchedData } from './state-node.js'
|
|
@@ -14,7 +13,6 @@ export { Display } from './utils/display.js'
|
|
|
14
13
|
* @typedef {import('./types.js').StateNode} StateNode
|
|
15
14
|
* @typedef {import('./types.js').StateTree} StateTree
|
|
16
15
|
* @typedef {import('./types.js').StatefulLayoutOptions} StatefulLayoutOptions
|
|
17
|
-
* @typedef {import('./types.js').StatefulLayoutEvents} StatefulLayoutEvents
|
|
18
16
|
* @typedef {import('./types.js').CreateStateTreeContext} CreateStateTreeContext
|
|
19
17
|
* @typedef {import('./types.js').TextFieldNode} TextFieldNode
|
|
20
18
|
* @typedef {import('./types.js').TextareaNode} TextareaNode
|
|
@@ -51,10 +49,6 @@ export const isItemsNode = (node, components) => !!node && isItemsLayout(node.la
|
|
|
51
49
|
|
|
52
50
|
const logDataBinding = debug('jl:data-binding')
|
|
53
51
|
|
|
54
|
-
// ugly fix of modules whose default export were wrongly declared
|
|
55
|
-
// @ts-ignore
|
|
56
|
-
const mitt = /** @type {typeof mittModule.default} */ (mittModule)
|
|
57
|
-
|
|
58
52
|
/**
|
|
59
53
|
* @param {Partial<StatefulLayoutOptions>} partialOptions
|
|
60
54
|
* @param {import('../index.js').CompiledLayout} compiledLayout
|
|
@@ -78,18 +72,15 @@ function fillOptions (partialOptions, compiledLayout) {
|
|
|
78
72
|
removeAdditional: 'error',
|
|
79
73
|
autofocus: false,
|
|
80
74
|
readOnlyPropertiesMode: 'show',
|
|
75
|
+
onAutofocus: () => {},
|
|
76
|
+
onUpdate: () => {},
|
|
77
|
+
onData: () => {},
|
|
81
78
|
...partialOptions,
|
|
82
79
|
messages
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
export class StatefulLayout {
|
|
87
|
-
/**
|
|
88
|
-
* @readonly
|
|
89
|
-
* @type {import('mitt').Emitter<StatefulLayoutEvents>}
|
|
90
|
-
*/
|
|
91
|
-
events
|
|
92
|
-
|
|
93
84
|
/**
|
|
94
85
|
* @private
|
|
95
86
|
* @readonly
|
|
@@ -218,14 +209,14 @@ export class StatefulLayout {
|
|
|
218
209
|
* @param {unknown} [data]
|
|
219
210
|
*/
|
|
220
211
|
constructor (compiledLayout, skeletonTree, options, data) {
|
|
212
|
+
logDataBinding('create stateful layout', compiledLayout, skeletonTree, options, data)
|
|
221
213
|
this._compiledLayout = compiledLayout
|
|
222
214
|
this.skeletonTree = skeletonTree
|
|
223
|
-
/** @type {import('mitt').Emitter<StatefulLayoutEvents>} */
|
|
224
|
-
this.events = mitt()
|
|
225
215
|
this.prepareOptions(options)
|
|
226
216
|
this._autofocusTarget = this.options.autofocus ? '' : null
|
|
227
217
|
this._previousAutofocusTarget = null
|
|
228
218
|
this._data = data
|
|
219
|
+
this._previousData = data
|
|
229
220
|
this.initValidationState()
|
|
230
221
|
this.activatedItems = {}
|
|
231
222
|
this.updateState()
|
|
@@ -272,7 +263,7 @@ export class StatefulLayout {
|
|
|
272
263
|
this.createStateTree(true)
|
|
273
264
|
}
|
|
274
265
|
logDataBinding('emit update event', this._data, this._stateTree)
|
|
275
|
-
this.
|
|
266
|
+
this.options.onUpdate(this)
|
|
276
267
|
this.emitData()
|
|
277
268
|
}
|
|
278
269
|
|
|
@@ -283,7 +274,7 @@ export class StatefulLayout {
|
|
|
283
274
|
// emit data event only if the data has changed, as it is immutable this simple comparison should suffice
|
|
284
275
|
if (!this._dataWaitingForBlur && this._data !== this._previousData) {
|
|
285
276
|
logDataBinding('emit data event', this._data)
|
|
286
|
-
this.
|
|
277
|
+
this.options.onData(this._data)
|
|
287
278
|
this._previousData = this._data
|
|
288
279
|
}
|
|
289
280
|
}
|
|
@@ -604,20 +595,22 @@ export class StatefulLayout {
|
|
|
604
595
|
prepareSelectItem (node, rawItem) {
|
|
605
596
|
/** @type {Partial<import('@json-layout/vocabulary').SelectItem>} */
|
|
606
597
|
const item = {}
|
|
598
|
+
/** @type {import('@json-layout/vocabulary').ItemsBasedCompObject} */
|
|
599
|
+
const layout = node.layout
|
|
607
600
|
if (typeof rawItem === 'object') {
|
|
608
|
-
item.value =
|
|
609
|
-
item.key =
|
|
610
|
-
item.title =
|
|
601
|
+
item.value = layout.getItems?.itemValue ? this.evalNodeExpression(node, layout.getItems.itemValue, rawItem) : (layout.getItems?.returnObjects ? rawItem : rawItem.value)
|
|
602
|
+
item.key = layout.getItems?.itemKey ? this.evalNodeExpression(node, layout.getItems.itemKey, rawItem) : rawItem.key
|
|
603
|
+
item.title = layout.getItems?.itemTitle ? this.evalNodeExpression(node, layout.getItems.itemTitle, rawItem) : rawItem.title
|
|
611
604
|
item.value = item.value ?? item.key
|
|
612
605
|
item.key = item.key ?? item.value + ''
|
|
613
606
|
item.title = item.title ?? item.key
|
|
614
607
|
if (!item.icon && rawItem.icon) item.icon = rawItem.icon
|
|
615
608
|
} else {
|
|
616
|
-
item.value =
|
|
617
|
-
item.key =
|
|
618
|
-
item.title =
|
|
609
|
+
item.value = layout.getItems?.itemValue ? this.evalNodeExpression(node, layout.getItems.itemValue, rawItem) : rawItem
|
|
610
|
+
item.key = layout.getItems?.itemKey ? this.evalNodeExpression(node, layout.getItems.itemKey, rawItem) : item.value
|
|
611
|
+
item.title = layout.getItems?.itemTitle ? this.evalNodeExpression(node, layout.getItems.itemTitle, rawItem) : item.value
|
|
619
612
|
}
|
|
620
|
-
if (
|
|
613
|
+
if (layout.getItems?.itemIcon) item.icon = this.evalNodeExpression(node, layout.getItems?.itemIcon, rawItem)
|
|
621
614
|
return /** @type {import('@json-layout/vocabulary').SelectItem} */(item)
|
|
622
615
|
}
|
|
623
616
|
|
|
@@ -660,7 +653,7 @@ export class StatefulLayout {
|
|
|
660
653
|
this._previousAutofocusTarget = autofocusTarget
|
|
661
654
|
setTimeout(() => {
|
|
662
655
|
logDataBinding('emit autofocus event', autofocusTarget)
|
|
663
|
-
this.
|
|
656
|
+
this.options.onAutofocus(autofocusTarget)
|
|
664
657
|
})
|
|
665
658
|
}
|
|
666
659
|
}
|
package/src/state/state-node.js
CHANGED
|
@@ -449,7 +449,10 @@ export function createStateNode (
|
|
|
449
449
|
} else {
|
|
450
450
|
if (layout.getDefaultData && useDefaultData(nodeData, layout, options)) {
|
|
451
451
|
if (!context.rehydrate) {
|
|
452
|
-
|
|
452
|
+
const defaultData = evalExpression(compiledLayout.expressions, layout.getDefaultData, nodeData, options, display, layout, compiledLayout.validates, context.rootData, parentContext)
|
|
453
|
+
if (nodeData === undefined || !isDataEmpty(defaultData)) {
|
|
454
|
+
nodeData = defaultData
|
|
455
|
+
}
|
|
453
456
|
}
|
|
454
457
|
} else {
|
|
455
458
|
if (isDataEmpty(nodeData)) {
|
package/src/state/types.ts
CHANGED
|
@@ -96,17 +96,13 @@ export interface ValidationState {
|
|
|
96
96
|
validatedChildren: string[]
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
100
|
-
export type StatefulLayoutEvents = {
|
|
101
|
-
data: any
|
|
102
|
-
update: StatefulLayout
|
|
103
|
-
autofocus: string
|
|
104
|
-
}
|
|
105
|
-
|
|
106
99
|
// StateNodeOptionsBase come from the vocabulary and should contain all node options that can be set in the layout
|
|
107
100
|
export type StateNodeOptions = Required<StateNodeOptionsBase & {
|
|
108
101
|
context: Record<string, any>
|
|
109
102
|
messages: LocaleMessages
|
|
103
|
+
onData: (data: any) => void
|
|
104
|
+
onUpdate: (statefulLayout: StatefulLayout) => void
|
|
105
|
+
onAutofocus: (key: string) => void
|
|
110
106
|
}>
|
|
111
107
|
|
|
112
108
|
export type StatefulLayoutOptions = StateNodeOptions & {
|
|
@@ -28,11 +28,12 @@ export function shallowProduceObject (previousObj = {}, newObj = {}) {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* @template ItemType
|
|
31
|
-
* @param {any[]} a1
|
|
32
|
-
* @param {any[]} a2
|
|
31
|
+
* @param {any[] | null | undefined} a1
|
|
32
|
+
* @param {any[] | null | undefined} a2
|
|
33
33
|
* @returns {boolean}
|
|
34
34
|
*/
|
|
35
35
|
export function shallowEqualArray (a1 = [], a2 = []) {
|
|
36
|
+
if (!a1 || !a2) return a1 === a2
|
|
36
37
|
if (a1.length !== a2.length) return false
|
|
37
38
|
for (let i = 0; i < a1.length; i++) { if (a1[i] !== a2[i]) return false }
|
|
38
39
|
return true
|