@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@json-layout/core",
3
- "version": "0.22.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",
@@ -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) draft[key] = newOptions[key]
40
- // @ts-ignore
41
- else delete draft[key]
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
 
@@ -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.events.emit('update', 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.events.emit('data', this._data)
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 = node.layout.getItems?.itemValue ? this.evalNodeExpression(node, node.layout.getItems.itemValue, rawItem) : (node.layout.getItems?.returnObjects ? rawItem : rawItem.value)
609
- item.key = node.layout.getItems?.itemKey ? this.evalNodeExpression(node, node.layout.getItems.itemKey, rawItem) : rawItem.key
610
- item.title = node.layout.getItems?.itemTitle ? this.evalNodeExpression(node, node.layout.getItems.itemTitle, rawItem) : rawItem.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 = node.layout.getItems?.itemValue ? this.evalNodeExpression(node, node.layout.getItems.itemValue, rawItem) : rawItem
617
- item.key = node.layout.getItems?.itemKey ? this.evalNodeExpression(node, node.layout.getItems.itemKey, rawItem) : item.value
618
- item.title = node.layout.getItems?.itemTitle ? this.evalNodeExpression(node, node.layout.getItems.itemTitle, rawItem) : item.value
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 (node.layout.getItems?.itemIcon) item.icon = this.evalNodeExpression(node, node.layout.getItems?.itemIcon, rawItem)
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.events.emit('autofocus', autofocusTarget)
656
+ this.options.onAutofocus(autofocusTarget)
664
657
  })
665
658
  }
666
659
  }
@@ -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
- nodeData = evalExpression(compiledLayout.expressions, layout.getDefaultData, nodeData, options, display, layout, compiledLayout.validates, context.rootData, parentContext)
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)) {
@@ -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