@json-layout/core 2.5.2 → 2.7.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 +3 -2
- package/src/compile/skeleton-node.js +8 -0
- package/src/state/index.js +25 -1
- package/src/state/state-node.js +122 -73
- package/src/state/types.ts +5 -1
- package/src/state/utils/modified.js +32 -0
- package/src/utils/build.js +4 -2
- package/src/webmcp/index.js +141 -22
- package/src/webmcp/project.js +210 -35
- package/src/webmcp/tools/describe-state.js +28 -3
- package/src/webmcp/tools/edit-array.js +97 -0
- package/src/webmcp/tools/fill-form-skill.js +2 -2
- package/src/webmcp/tools/set-data.js +8 -6
- package/src/webmcp/tools/set-field-value.js +19 -7
- package/types/compile/serialize.d.ts.map +1 -1
- package/types/compile/skeleton-node.d.ts.map +1 -1
- package/types/state/index.d.ts +14 -1
- package/types/state/index.d.ts.map +1 -1
- package/types/state/state-node.d.ts.map +1 -1
- package/types/state/types.d.ts +5 -1
- package/types/state/types.d.ts.map +1 -1
- package/types/state/utils/modified.d.ts +17 -0
- package/types/state/utils/modified.d.ts.map +1 -0
- package/types/utils/build.d.ts.map +1 -1
- package/types/webmcp/index.d.ts +21 -9
- package/types/webmcp/index.d.ts.map +1 -1
- package/types/webmcp/project.d.ts +80 -28
- package/types/webmcp/project.d.ts.map +1 -1
- package/types/webmcp/tools/describe-state.d.ts +10 -1
- package/types/webmcp/tools/describe-state.d.ts.map +1 -1
- package/types/webmcp/tools/edit-array.d.ts +88 -0
- package/types/webmcp/tools/edit-array.d.ts.map +1 -0
- package/types/webmcp/tools/set-data.d.ts +11 -16
- package/types/webmcp/tools/set-data.d.ts.map +1 -1
- package/types/webmcp/tools/set-field-value.d.ts +35 -17
- package/types/webmcp/tools/set-field-value.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@json-layout/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Compilation and state management utilities for JSON Layout.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
"homepage": "https://github.com/json-layout/json-layout#readme",
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"@json-layout/vocabulary": "^2.
|
|
85
|
+
"@json-layout/vocabulary": "^2.13.0"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"ajv": "^8.17.1",
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"ajv-formats": "^3.0.1",
|
|
91
91
|
"ajv-i18n": "^4.2.0",
|
|
92
92
|
"debug": "^4.3.4",
|
|
93
|
+
"fast-deep-equal": "^3.1.3",
|
|
93
94
|
"immer": "^10.0.3",
|
|
94
95
|
"magicast": "^0.3.3",
|
|
95
96
|
"marked": "^15.0.7"
|
|
@@ -324,6 +324,14 @@ export function makeSkeletonNode (
|
|
|
324
324
|
if (compObject.defaultData === undefined) compObject.defaultData = defaultData
|
|
325
325
|
if (compObject.defaultData !== undefined && !compObject.getDefaultData) compObject.getDefaultData = { type: 'js-eval', expr: 'layout.defaultData', pure: true, dataAlias: 'value' }
|
|
326
326
|
if (compObject.getDefaultData) pushExpression(expressions, compObject.getDefaultData)
|
|
327
|
+
|
|
328
|
+
if (compObject.options !== undefined && !compObject.getOptions) compObject.getOptions = { type: 'js-eval', expr: 'layout.options', pure: true, dataAlias: 'value' }
|
|
329
|
+
if (compObject.getOptions) pushExpression(expressions, compObject.getOptions)
|
|
330
|
+
|
|
331
|
+
if (compObject.props !== undefined && !compObject.getProps) compObject.getProps = { type: 'js-eval', expr: 'layout.props', pure: true, dataAlias: 'value' }
|
|
332
|
+
if (compObject.getProps) pushExpression(expressions, compObject.getProps)
|
|
333
|
+
|
|
334
|
+
if (compObject.transformData) pushExpression(expressions, compObject.transformData)
|
|
327
335
|
}
|
|
328
336
|
|
|
329
337
|
normalizedLayouts[oneOfPointer] = normalizationResult.layout
|
package/src/state/index.js
CHANGED
|
@@ -183,13 +183,20 @@ export class StatefulLayout {
|
|
|
183
183
|
*/
|
|
184
184
|
files = []
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* @private
|
|
188
|
+
* @type {unknown}
|
|
189
|
+
*/
|
|
190
|
+
_savedData
|
|
191
|
+
|
|
186
192
|
/**
|
|
187
193
|
* @param {import("../index.js").CompiledLayout} compiledLayout
|
|
188
194
|
* @param {import("../index.js").SkeletonTree} skeletonTree
|
|
189
195
|
* @param {Partial<StatefulLayoutOptions>} options
|
|
190
196
|
* @param {unknown} [data]
|
|
197
|
+
* @param {unknown} [savedData]
|
|
191
198
|
*/
|
|
192
|
-
constructor (compiledLayout, skeletonTree, options, data) {
|
|
199
|
+
constructor (compiledLayout, skeletonTree, options, data, savedData) {
|
|
193
200
|
logDataBinding('create stateful layout', compiledLayout, skeletonTree, options, data)
|
|
194
201
|
this._compiledLayout = compiledLayout
|
|
195
202
|
this.skeletonTree = skeletonTree
|
|
@@ -197,6 +204,7 @@ export class StatefulLayout {
|
|
|
197
204
|
this._autofocusTarget = this.options.autofocus ? '' : null
|
|
198
205
|
this._previousAutofocusTarget = null
|
|
199
206
|
this._data = data
|
|
207
|
+
this._savedData = savedData
|
|
200
208
|
this._previousData = data
|
|
201
209
|
this.initValidationState()
|
|
202
210
|
this.activatedItems = {}
|
|
@@ -284,6 +292,7 @@ export class StatefulLayout {
|
|
|
284
292
|
rehydrate,
|
|
285
293
|
cacheKeys: this._lastCreateStateTreeContext?.cacheKeys ?? {},
|
|
286
294
|
rootData: this._data,
|
|
295
|
+
savedData: this._savedData,
|
|
287
296
|
files: [],
|
|
288
297
|
nodes: [],
|
|
289
298
|
nodesMap: new Map(),
|
|
@@ -362,6 +371,21 @@ export class StatefulLayout {
|
|
|
362
371
|
return this._lastCreateStateTreeContext.nodes.findIndex(node => node.error && !node.validated) !== -1
|
|
363
372
|
}
|
|
364
373
|
|
|
374
|
+
/**
|
|
375
|
+
* @returns {boolean}
|
|
376
|
+
*/
|
|
377
|
+
get modified () {
|
|
378
|
+
if (this._savedData === undefined) return false
|
|
379
|
+
return !!(this._stateTree.root.modified || this._stateTree.root.childModified)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
get savedData () { return this._savedData }
|
|
383
|
+
/** @param {unknown} savedData */
|
|
384
|
+
set savedData (savedData) {
|
|
385
|
+
this._savedData = savedData
|
|
386
|
+
this.updateState()
|
|
387
|
+
}
|
|
388
|
+
|
|
365
389
|
/**
|
|
366
390
|
* @private
|
|
367
391
|
* @param {StateNode} node
|
package/src/state/state-node.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Display, getChildDisplay } from './utils/display.js'
|
|
|
5
5
|
import { shallowEqualArray, shallowProduceArray, shallowProduceObject } from './utils/immutable.js'
|
|
6
6
|
import { getRegexp } from './utils/regexps.js'
|
|
7
7
|
import { pathURL } from './utils/urls.js'
|
|
8
|
+
import { resolveDataPath, isNodeModified } from './utils/modified.js'
|
|
8
9
|
|
|
9
10
|
const logStateNode = debug('jl:state-node')
|
|
10
11
|
const logValidation = debug('jl:validation')
|
|
@@ -34,8 +35,8 @@ export const useDefaultData = (data, layout, options) => {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
// use Immer for efficient updating with immutability and no-op detection
|
|
37
|
-
/** @type {(draft: import('./types.js').StateNode, key: string | number, fullKey: string, parentFullKey: string | null, dataPath: string, parentDataPath: string | null, skeleton: import('../index.js').SkeletonNode, layout: import('@json-layout/vocabulary').BaseCompObject, width: number, cols: number, data: unknown, error: string | undefined, validated: boolean, options: import('./types.js').StateNodeOptions, titleDepth: number, autofocus: boolean, shouldLoadData: boolean, props: import('@json-layout/vocabulary').StateNodePropsLib, slots: import('@json-layout/vocabulary').Slots | undefined, itemsCacheKey: any, children: import('../index.js').StateNode[] | undefined) => import('../index.js').StateNode} */
|
|
38
|
-
const produceStateNode = produce((draft, key, fullKey, parentFullKey, dataPath, parentDataPath, skeleton, layout, width, cols, data, error, validated, options, titleDepth, autofocus, shouldLoadData, props, slots, itemsCacheKey, children) => {
|
|
38
|
+
/** @type {(draft: import('./types.js').StateNode, key: string | number, fullKey: string, parentFullKey: string | null, dataPath: string, parentDataPath: string | null, skeleton: import('../index.js').SkeletonNode, layout: import('@json-layout/vocabulary').BaseCompObject, width: number, cols: number, data: unknown, error: string | undefined, validated: boolean, options: import('./types.js').StateNodeOptions, titleDepth: number, autofocus: boolean, shouldLoadData: boolean, props: import('@json-layout/vocabulary').StateNodePropsLib, slots: import('@json-layout/vocabulary').Slots | undefined, itemsCacheKey: any, children: import('../index.js').StateNode[] | undefined, modified: boolean | undefined, childModified: boolean | undefined) => import('../index.js').StateNode} */
|
|
39
|
+
const produceStateNode = produce((draft, key, fullKey, parentFullKey, dataPath, parentDataPath, skeleton, layout, width, cols, data, error, validated, options, titleDepth, autofocus, shouldLoadData, props, slots, itemsCacheKey, children, modified, childModified) => {
|
|
39
40
|
draft.messages = layout.messages ? produceStateNodeMessages(draft.messages || {}, layout.messages, options) : options.messages
|
|
40
41
|
|
|
41
42
|
draft.key = key
|
|
@@ -55,6 +56,8 @@ const produceStateNode = produce((draft, key, fullKey, parentFullKey, dataPath,
|
|
|
55
56
|
draft.error = error
|
|
56
57
|
draft.itemsCacheKey = itemsCacheKey
|
|
57
58
|
draft.childError = children && (children.findIndex(c => c.error || c.childError) !== -1)
|
|
59
|
+
draft.modified = modified
|
|
60
|
+
draft.childModified = childModified
|
|
58
61
|
draft.validated = validated
|
|
59
62
|
if (autofocus) {
|
|
60
63
|
draft.autofocus = true
|
|
@@ -374,6 +377,10 @@ export function createStateNode (
|
|
|
374
377
|
) {
|
|
375
378
|
logStateNode('createStateNode', fullKey)
|
|
376
379
|
|
|
380
|
+
// Resolve saved data for this node (needed for cache key and modified computation)
|
|
381
|
+
const nodeSavedData = context.savedData !== undefined ? resolveDataPath(context.savedData, dataPath) : undefined
|
|
382
|
+
let suppressChildModified = false
|
|
383
|
+
|
|
377
384
|
/** @type {import('./types.js').StateNodeCacheKey | null} */
|
|
378
385
|
let cacheKey = null
|
|
379
386
|
|
|
@@ -395,7 +402,8 @@ export function createStateNode (
|
|
|
395
402
|
context.initial,
|
|
396
403
|
context.rehydrateErrors?.length ?? 0,
|
|
397
404
|
data,
|
|
398
|
-
titleDepth
|
|
405
|
+
titleDepth,
|
|
406
|
+
context.savedData !== undefined ? nodeSavedData : undefined
|
|
399
407
|
]
|
|
400
408
|
const hasNewErrors = context.errors?.some(e => matchLocalError(e, skeleton, dataPath, parentDataPath))
|
|
401
409
|
if (!hasNewErrors && reusedNode && context.cacheKeys[fullKey] && shallowEqualArray(context.cacheKeys[fullKey], cacheKey)) {
|
|
@@ -607,30 +615,39 @@ export function createStateNode (
|
|
|
607
615
|
const activeChildKey = `${fullKey}/${activeChildTreeIndex}`
|
|
608
616
|
if (context.autofocusTarget === fullKey) context.autofocusTarget = activeChildKey
|
|
609
617
|
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
618
|
+
const savedDataBackup = context.savedData
|
|
619
|
+
if (context.savedData !== undefined && isNodeModified(nodeSavedData, nodeData)) {
|
|
620
|
+
suppressChildModified = true
|
|
621
|
+
context.savedData = undefined
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
const child = createStateNode(
|
|
625
|
+
context,
|
|
626
|
+
options,
|
|
627
|
+
compiledLayout,
|
|
628
|
+
activeChildTreeIndex,
|
|
629
|
+
activeChildKey,
|
|
630
|
+
fullKey,
|
|
631
|
+
dataPath,
|
|
632
|
+
dataPath,
|
|
633
|
+
activeChildNode,
|
|
634
|
+
null,
|
|
635
|
+
display,
|
|
636
|
+
nodeData,
|
|
637
|
+
{ parent: parentContext, data: nodeData },
|
|
638
|
+
validationState,
|
|
639
|
+
titleDepth,
|
|
640
|
+
reusedNode?.children?.[0]
|
|
641
|
+
)
|
|
642
|
+
// the oneOf was hydrated
|
|
643
|
+
if (child.data !== nodeData) {
|
|
644
|
+
// nodeData = produceHydratedStateNodeData(nodeData, dataPath, child, skeleton.children?.map(childSkeletonKey => compiledLayout.skeletonNodes[childSkeletonKey]))
|
|
645
|
+
nodeData = child.data
|
|
646
|
+
}
|
|
647
|
+
children = [child]
|
|
648
|
+
} finally {
|
|
649
|
+
context.savedData = savedDataBackup
|
|
632
650
|
}
|
|
633
|
-
children = [child]
|
|
634
651
|
}
|
|
635
652
|
}
|
|
636
653
|
|
|
@@ -698,54 +715,64 @@ export function createStateNode (
|
|
|
698
715
|
const listItemOptions = layout.listEditMode === 'inline' ? options : produceReadonlyArrayItemOptions(options)
|
|
699
716
|
children = []
|
|
700
717
|
let focusChild = context.autofocusTarget === fullKey
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
context,
|
|
707
|
-
(layout.listEditMode === 'inline-single' && context.activatedItems[fullKey] === i) ? options : listItemOptions,
|
|
708
|
-
compiledLayout,
|
|
709
|
-
i,
|
|
710
|
-
childFullKey,
|
|
711
|
-
fullKey,
|
|
712
|
-
`${dataPath}/${i}`,
|
|
713
|
-
dataPath,
|
|
714
|
-
childSkeleton,
|
|
715
|
-
null,
|
|
716
|
-
display,
|
|
717
|
-
itemData,
|
|
718
|
-
{ parent: parentContext, data: arrayData },
|
|
719
|
-
validationState,
|
|
720
|
-
titleDepth,
|
|
721
|
-
reusedNode?.children?.[i]
|
|
722
|
-
)
|
|
723
|
-
if (child.autofocus || child.autofocusChild !== undefined) focusChild = false
|
|
724
|
-
children.push(child)
|
|
718
|
+
// For arrays: check if saved data differs, suppress child modified tracking if so
|
|
719
|
+
const savedDataBackup = context.savedData
|
|
720
|
+
if (context.savedData !== undefined && isNodeModified(nodeSavedData, nodeData)) {
|
|
721
|
+
suppressChildModified = true
|
|
722
|
+
context.savedData = undefined
|
|
725
723
|
}
|
|
724
|
+
try {
|
|
725
|
+
for (let i = 0; i < arrayData.length; i++) {
|
|
726
|
+
const itemData = arrayData[i]
|
|
727
|
+
const childFullKey = `${fullKey}/${i}`
|
|
728
|
+
if (focusChild) context.autofocusTarget = childFullKey
|
|
729
|
+
const child = createStateNode(
|
|
730
|
+
context,
|
|
731
|
+
(layout.listEditMode === 'inline-single' && context.activatedItems[fullKey] === i) ? options : listItemOptions,
|
|
732
|
+
compiledLayout,
|
|
733
|
+
i,
|
|
734
|
+
childFullKey,
|
|
735
|
+
fullKey,
|
|
736
|
+
`${dataPath}/${i}`,
|
|
737
|
+
dataPath,
|
|
738
|
+
childSkeleton,
|
|
739
|
+
null,
|
|
740
|
+
display,
|
|
741
|
+
itemData,
|
|
742
|
+
{ parent: parentContext, data: arrayData },
|
|
743
|
+
validationState,
|
|
744
|
+
titleDepth,
|
|
745
|
+
reusedNode?.children?.[i]
|
|
746
|
+
)
|
|
747
|
+
if (child.autofocus || child.autofocusChild !== undefined) focusChild = false
|
|
748
|
+
children.push(child)
|
|
749
|
+
}
|
|
726
750
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
751
|
+
// duplicate active child at the end of the list in case of dialog/menu edition
|
|
752
|
+
if (context.activatedItems[fullKey] !== undefined && (layout.listEditMode === 'menu' || layout.listEditMode === 'dialog')) {
|
|
753
|
+
const i = /** @type {number} */(context.activatedItems[fullKey])
|
|
754
|
+
const activeChild = createStateNode(
|
|
755
|
+
context,
|
|
756
|
+
options,
|
|
757
|
+
compiledLayout,
|
|
758
|
+
i,
|
|
759
|
+
`${fullKey}/${i}`,
|
|
760
|
+
fullKey,
|
|
761
|
+
`${dataPath}/${i}`,
|
|
762
|
+
dataPath,
|
|
763
|
+
childSkeleton,
|
|
764
|
+
null,
|
|
765
|
+
new Display(layout.listEditMode === 'menu' ? options.listMenuWidth : options.listDialogWidth),
|
|
766
|
+
arrayData[i],
|
|
767
|
+
{ parent: parentContext, data: arrayData },
|
|
768
|
+
validationState,
|
|
769
|
+
titleDepth,
|
|
770
|
+
reusedNode?.children?.[i]
|
|
771
|
+
)
|
|
772
|
+
children.push(activeChild)
|
|
773
|
+
}
|
|
774
|
+
} finally {
|
|
775
|
+
context.savedData = savedDataBackup
|
|
749
776
|
}
|
|
750
777
|
}
|
|
751
778
|
}
|
|
@@ -893,6 +920,26 @@ export function createStateNode (
|
|
|
893
920
|
logGetItems(fullKey, 'list component with unchanged getItems cache key, no fetch will be triggered', itemsCacheKey)
|
|
894
921
|
}
|
|
895
922
|
|
|
923
|
+
// Compute modified flags
|
|
924
|
+
/** @type {boolean | undefined} */
|
|
925
|
+
let modified
|
|
926
|
+
/** @type {boolean | undefined} */
|
|
927
|
+
let childModified
|
|
928
|
+
if (context.savedData !== undefined) {
|
|
929
|
+
if (suppressChildModified) {
|
|
930
|
+
// Array or oneOf that differs from saved — mark as modified, children were not individually compared
|
|
931
|
+
modified = true
|
|
932
|
+
childModified = true
|
|
933
|
+
} else if (children) {
|
|
934
|
+
// Composite node (including equal arrays/oneOf): aggregate from children
|
|
935
|
+
modified = false
|
|
936
|
+
childModified = children.some(c => c.modified || c.childModified) || false
|
|
937
|
+
} else {
|
|
938
|
+
// Leaf node: compare directly
|
|
939
|
+
modified = isNodeModified(nodeSavedData, nodeData)
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
896
943
|
const node = produceStateNode(
|
|
897
944
|
reusedNode ?? /** @type {import('./types.js').StateNode} */({}),
|
|
898
945
|
key,
|
|
@@ -914,7 +961,9 @@ export function createStateNode (
|
|
|
914
961
|
props,
|
|
915
962
|
slots,
|
|
916
963
|
itemsCacheKey,
|
|
917
|
-
children && shallowProduceArray(reusedNode?.children, children)
|
|
964
|
+
children && shallowProduceArray(reusedNode?.children, children),
|
|
965
|
+
modified,
|
|
966
|
+
childModified
|
|
918
967
|
)
|
|
919
968
|
|
|
920
969
|
if (cacheKey) {
|
package/src/state/types.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface StateNode {
|
|
|
45
45
|
data: unknown
|
|
46
46
|
error: string | undefined
|
|
47
47
|
childError: boolean | undefined
|
|
48
|
+
modified: boolean | undefined
|
|
49
|
+
childModified: boolean | undefined
|
|
48
50
|
validated: boolean
|
|
49
51
|
width: number
|
|
50
52
|
options: StateNodeOptions
|
|
@@ -78,6 +80,7 @@ export interface CreateStateTreeContext {
|
|
|
78
80
|
rehydrate: boolean
|
|
79
81
|
cacheKeys: Record<string, StateNodeCacheKey>
|
|
80
82
|
rootData: unknown
|
|
83
|
+
savedData: unknown
|
|
81
84
|
nodes: StateNode[],
|
|
82
85
|
nodesMap: Map<string, StateNode>,
|
|
83
86
|
getItemsDataRequests: StateNode[]
|
|
@@ -102,7 +105,8 @@ export type StateNodeCacheKey = [
|
|
|
102
105
|
boolean,
|
|
103
106
|
number,
|
|
104
107
|
unknown,
|
|
105
|
-
number
|
|
108
|
+
number,
|
|
109
|
+
unknown // savedData resolved for this node
|
|
106
110
|
]
|
|
107
111
|
|
|
108
112
|
export interface ValidationState {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import equal from 'fast-deep-equal'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a JSON pointer data path against a data object.
|
|
5
|
+
* @param {unknown} data
|
|
6
|
+
* @param {string} dataPath - JSON pointer (e.g. '', '/a/b', '/arr/0')
|
|
7
|
+
* @returns {unknown}
|
|
8
|
+
*/
|
|
9
|
+
export function resolveDataPath (data, dataPath) {
|
|
10
|
+
if (dataPath === '') return data
|
|
11
|
+
if (data === undefined || data === null) return undefined
|
|
12
|
+
const segments = dataPath.slice(1).split('/')
|
|
13
|
+
let current = data
|
|
14
|
+
for (const segment of segments) {
|
|
15
|
+
if (current === undefined || current === null || typeof current !== 'object') return undefined
|
|
16
|
+
current = /** @type {any} */(current)[segment]
|
|
17
|
+
}
|
|
18
|
+
return current
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Compare a saved value against a current value.
|
|
23
|
+
* The caller is responsible for checking if the feature is active (context.savedData !== undefined)
|
|
24
|
+
* before calling this function.
|
|
25
|
+
* @param {unknown} savedValue
|
|
26
|
+
* @param {unknown} currentValue
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
export function isNodeModified (savedValue, currentValue) {
|
|
30
|
+
if (savedValue === currentValue) return false
|
|
31
|
+
return !equal(savedValue, currentValue)
|
|
32
|
+
}
|
package/src/utils/build.js
CHANGED
|
@@ -8,7 +8,8 @@ export const commonjsDeps = [
|
|
|
8
8
|
'ajv-formats/dist/formats.js',
|
|
9
9
|
'ajv-i18n',
|
|
10
10
|
'ajv-errors',
|
|
11
|
-
'debug'
|
|
11
|
+
'debug',
|
|
12
|
+
'fast-deep-equal'
|
|
12
13
|
]
|
|
13
14
|
|
|
14
15
|
/** @type {string[][]} */
|
|
@@ -19,5 +20,6 @@ export const commonjsDepsPaths = [
|
|
|
19
20
|
['@json-layout/core', 'ajv-formats/dist/formats.js'],
|
|
20
21
|
['@json-layout/core', 'ajv-i18n'],
|
|
21
22
|
['@json-layout/core', 'ajv-errors'],
|
|
22
|
-
['@json-layout/core', 'debug']
|
|
23
|
+
['@json-layout/core', 'debug'],
|
|
24
|
+
['@json-layout/core', 'fast-deep-equal']
|
|
23
25
|
]
|