native-document 1.0.169 → 1.0.173

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 (134) hide show
  1. package/CHANGELOG.md +100 -2
  2. package/docs/vitepress-conventions.md +2 -2
  3. package/index.d.ts +1 -1
  4. package/package.json +11 -4
  5. package/src/components/BaseComponent.js +43 -1
  6. package/src/components/accordion/Accordion.js +1 -0
  7. package/src/components/accordion/AccordionItem.js +1 -0
  8. package/src/components/alert/Alert.js +4 -0
  9. package/src/components/avatar/Avatar.js +1 -0
  10. package/src/components/avatar/AvatarGroup.js +1 -0
  11. package/src/components/badge/Badge.js +1 -0
  12. package/src/components/breadcrumb/BreadCrumb.js +4 -0
  13. package/src/components/button/Button.js +1 -0
  14. package/src/components/card/Card.js +1 -0
  15. package/src/components/context-menu/ContextMenu.js +2 -0
  16. package/src/components/context-menu/ContextMenuGroup.js +2 -0
  17. package/src/components/context-menu/ContextMenuItem.js +2 -0
  18. package/src/components/divider/Divider.js +2 -0
  19. package/src/components/dropdown/Dropdown.js +1 -0
  20. package/src/components/dropdown/DropdownDivider.js +1 -0
  21. package/src/components/dropdown/DropdownGroup.js +1 -0
  22. package/src/components/dropdown/DropdownItem.js +1 -0
  23. package/src/components/dropdown/DropdownTrigger.js +1 -0
  24. package/src/components/form/FormControl.js +1 -0
  25. package/src/components/form/field/Field.js +1 -0
  26. package/src/components/form/field/types/StringField.js +1 -1
  27. package/src/components/icon/Icon.js +1 -0
  28. package/src/components/list/ListItem.js +1 -0
  29. package/src/components/menu/Menu.js +3 -0
  30. package/src/components/menu/MenuDivider.js +1 -0
  31. package/src/components/menu/MenuGroup.js +1 -0
  32. package/src/components/menu/MenuItem.js +1 -0
  33. package/src/components/menu/MenuLink.js +1 -0
  34. package/src/components/modal/Modal.js +4 -0
  35. package/src/components/pagination/Pagination.js +1 -0
  36. package/src/components/popover/Popover.js +1 -0
  37. package/src/components/popover/PopoverFooter.js +1 -0
  38. package/src/components/popover/PopoverHeader.js +1 -0
  39. package/src/components/progress/Progress.js +5 -0
  40. package/src/components/skeleton/Skeleton.js +4 -0
  41. package/src/components/slider/Slider.js +1 -0
  42. package/src/components/spacer/Spacer.js +1 -0
  43. package/src/components/spinner/Spinner.js +1 -0
  44. package/src/components/splitter/Splitter.js +1 -0
  45. package/src/components/splitter/SplitterGutter.js +6 -0
  46. package/src/components/splitter/SplitterPanel.js +1 -0
  47. package/src/components/stacks/AbsoluteStack.js +1 -0
  48. package/src/components/stacks/FixedStack.js +1 -0
  49. package/src/components/stacks/HStack.js +1 -0
  50. package/src/components/stacks/PositionStack.js +1 -0
  51. package/src/components/stacks/RelativeStack.js +1 -0
  52. package/src/components/stacks/Stack.js +1 -0
  53. package/src/components/stacks/VStack.js +1 -0
  54. package/src/components/stepper/Stepper.js +1 -0
  55. package/src/components/stepper/StepperStep.js +1 -0
  56. package/src/components/switch/Switch.js +1 -0
  57. package/src/components/table/DataTable.js +1 -0
  58. package/src/components/table/SimpleTable.js +1 -0
  59. package/src/components/tabs/Tabs.js +1 -0
  60. package/src/components/toast/Toast.js +1 -0
  61. package/src/components/tooltip/Tooltip.js +1 -0
  62. package/src/core/data/MemoryManager.js +6 -1
  63. package/src/core/data/ObservableItem.js +33 -15
  64. package/src/core/data/ObservableObject.js +1 -2
  65. package/src/core/elements/anchor/anchor.js +4 -3
  66. package/src/core/elements/anchor/one-child-anchor-overwriting.js +4 -4
  67. package/src/core/elements/content-formatter.js +19 -0
  68. package/src/core/elements/control/for-each-array-cache.js +59 -0
  69. package/src/core/elements/control/for-each-array.js +15 -23
  70. package/src/core/wrappers/AttributesWrapper.js +12 -12
  71. package/src/core/wrappers/ElementCreator.js +33 -10
  72. package/src/core/wrappers/HtmlElementWrapper.js +17 -13
  73. package/src/core/wrappers/NDElement.js +23 -8
  74. package/src/core/wrappers/NdPrototype.js +64 -39
  75. package/src/core/wrappers/constants.js +12 -12
  76. package/src/core/wrappers/prototypes/nd-element-extensions.js +127 -57
  77. package/src/core/wrappers/template-cloner/NodeCloner.js +10 -6
  78. package/src/core/wrappers/template-cloner/TemplateCloner.js +7 -4
  79. package/src/fetch/NativeFetch.js +1 -0
  80. package/src/ui/components/accordion/AccordionItemRender.js +29 -7
  81. package/src/ui/components/alert/AlertRender.js +6 -0
  82. package/src/ui/components/breadcrumb/BreadcrumbRender.js +9 -1
  83. package/src/ui/components/button/ButtonRender.js +3 -0
  84. package/src/ui/components/contextmenu/ContextmenuRender.js +2 -0
  85. package/src/ui/components/dropdown/DropdownRender.js +3 -0
  86. package/src/ui/components/dropdown/item/DropdownItemRender.js +6 -1
  87. package/src/ui/components/form/FieldCollectionRender.js +4 -0
  88. package/src/ui/components/form/fields/CheckboxFieldRender.js +4 -0
  89. package/src/ui/components/form/fields/CheckboxGroupFieldRender.js +4 -0
  90. package/src/ui/components/form/fields/FieldRender.js +1 -1
  91. package/src/ui/components/form/fields/RadioFieldRender.js +4 -0
  92. package/src/ui/components/form/fields/RangeFieldRender.js +4 -0
  93. package/src/ui/components/form/fields/SelectFieldRender.js +5 -0
  94. package/src/ui/components/form/fields/SliderFieldRender.js +3 -0
  95. package/src/ui/components/icon/tabler/TablerIconRender.js +0 -1
  96. package/src/ui/components/list/item/ListItemRender.js +4 -0
  97. package/src/ui/components/menu/MenuItemRender.js +8 -0
  98. package/src/ui/components/menu/MenuLinkRender.js +11 -0
  99. package/src/ui/components/menu/MenuRender.js +5 -0
  100. package/src/ui/components/menu/helpers.js +2 -1
  101. package/src/ui/components/modal/ModalRender.js +8 -2
  102. package/src/ui/components/progress/ProgressRender.js +9 -0
  103. package/src/ui/components/skeleton/SkeletonRender.js +2 -0
  104. package/src/ui/components/spinner/SpinnerRender.js +3 -0
  105. package/src/ui/components/splitter/SplitterGutterRender.js +4 -0
  106. package/src/ui/components/splitter/SplitterPanelRender.js +4 -0
  107. package/src/ui/components/splitter/SplitterRender.js +2 -0
  108. package/src/ui/components/stepper/StepperRender.js +2 -1
  109. package/src/ui/components/stepper/StepperStepRender.js +8 -1
  110. package/src/ui/components/switch/SwitchRender.js +3 -0
  111. package/src/ui/components/table/data-table/DataTableRender.js +4 -0
  112. package/src/ui/components/table/simple-table/SimpleTableRender.js +2 -0
  113. package/src/ui/components/toast/ToastRender.js +4 -0
  114. package/src/ui/utils/aria.js +19 -0
  115. package/tests/integration/ui/attributes-wrapper.test.js +177 -0
  116. package/tests/integration/ui/for-each-array.test.js +167 -0
  117. package/tests/integration/ui/lifecycle.test.js +146 -0
  118. package/tests/integration/ui/show-if.test.js +168 -0
  119. package/tests/unit/core/observable-array.test.js +404 -0
  120. package/tests/unit/core/observable-item.test.js +428 -0
  121. package/tests/unit/core/observable-object.test.js +239 -0
  122. package/tests/unit/core/observable-resource.test.js +323 -0
  123. package/tests/unit/core/store.test.js +347 -0
  124. package/tests/unit/router/route-matching.test.js +169 -0
  125. package/tests/unit/router/router-guards.test.js +189 -0
  126. package/tests/unit/utils/cache.test.js +120 -0
  127. package/tests/unit/utils/filters.test.js +394 -0
  128. package/types/elements.d.ts +5 -0
  129. package/types/native-fetch.d.ts +1 -1
  130. package/types/observable-resource.d.ts +2 -2
  131. package/types/observable.d.ts +137 -21
  132. package/types/service.d.ts +2 -1
  133. package/utils.js +7 -4
  134. package/vitest.config.js +15 -0
@@ -37,6 +37,7 @@ export default function SplitterPanel(content, props = {}) {
37
37
  render: null,
38
38
  props,
39
39
  };
40
+ this.aria = { 'role': 'region' };
40
41
  }
41
42
 
42
43
  BaseComponent.extends(SplitterPanel);
@@ -15,6 +15,7 @@ export default function AbsoluteStack(content, props = {}) {
15
15
  }
16
16
  PositionStack.call(this, content, props);
17
17
  this.$description.position = 'absolute';
18
+ this.aria = {};
18
19
  }
19
20
 
20
21
  BaseComponent.extends(AbsoluteStack, PositionStack);
@@ -15,6 +15,7 @@ export default function FixedStack(content, props = {}) {
15
15
  }
16
16
  PositionStack.call(this, content, props);
17
17
  this.$description.position = 'fixed';
18
+ this.aria = {};
18
19
  }
19
20
 
20
21
  BaseComponent.extends(FixedStack, PositionStack);
@@ -16,6 +16,7 @@ export default function HStack(content, props = {}) {
16
16
 
17
17
  Stack.call(this, content, props);
18
18
  this.$description.orientation = 'horizontal';
19
+ this.aria = {};
19
20
  }
20
21
 
21
22
  BaseComponent.extends(HStack, Stack);
@@ -34,6 +34,7 @@ export default function PositionStack(content, props = {}) {
34
34
  anchor: null,
35
35
  props,
36
36
  };
37
+ this.aria = {};
37
38
  }
38
39
 
39
40
  BaseComponent.extends(PositionStack);
@@ -15,6 +15,7 @@ export default function RelativeStack(content, props = {}) {
15
15
  }
16
16
  PositionStack.call(this, content, props);
17
17
  this.$description.position = 'relative';
18
+ this.aria = {};
18
19
  }
19
20
 
20
21
  BaseComponent.extends(RelativeStack, PositionStack);
@@ -36,6 +36,7 @@ export default function Stack(content, props = {}) {
36
36
  reverse: false,
37
37
  props,
38
38
  };
39
+ this.aria = {};
39
40
  }
40
41
 
41
42
  BaseComponent.extends(Stack);
@@ -18,6 +18,7 @@ export default function VStack(content, props = {}) {
18
18
 
19
19
  this.$description.orientation = 'vertical';
20
20
  this.$description.alignment = 'leading';
21
+ this.aria = {};
21
22
  }
22
23
  BaseComponent.extends(VStack, Stack);
23
24
 
@@ -50,6 +50,7 @@ export default function Stepper(props = {}) {
50
50
  position: 'bottom',
51
51
  props,
52
52
  };
53
+ this.aria = { 'role': 'list' };
53
54
 
54
55
  this.$element = null;
55
56
  }
@@ -47,6 +47,7 @@ export default function StepperStep(label, props = {}) {
47
47
  isVisible: $(true),
48
48
  props,
49
49
  };
50
+ this.aria = { 'role': 'listitem', 'tabindex': '0' };
50
51
  }
51
52
 
52
53
  BaseComponent.extends(StepperStep);
@@ -43,6 +43,7 @@ export default function Switch(props = {}) {
43
43
  innerOnLabel: null,
44
44
  innerOffLabel: null,
45
45
  };
46
+ this.aria = { 'role': 'switch' };
46
47
  }
47
48
 
48
49
  BaseComponent.extends(Switch);
@@ -115,6 +115,7 @@ export default function DataTable(props = {}) {
115
115
  isExpandedIcon: null,
116
116
  isNotExpandedIcon: null,
117
117
  };
118
+ this.aria = { 'role': 'table' };
118
119
  }
119
120
 
120
121
  BaseComponent.extends(DataTable);
@@ -28,6 +28,7 @@ export default function SimpleTable(props = {}) {
28
28
  headerProps: null,
29
29
  props,
30
30
  };
31
+ this.aria = { 'role': 'table' };
31
32
  }
32
33
 
33
34
  BaseComponent.extends(SimpleTable);
@@ -47,6 +47,7 @@ export default function Tabs(props = {}) {
47
47
  renderPlusButton: null,
48
48
  props,
49
49
  };
50
+ this.aria = { 'role': 'tablist' };
50
51
  }
51
52
 
52
53
  BaseComponent.extends(Tabs);
@@ -45,6 +45,7 @@ export default function Toast(content, props = {}) {
45
45
  render: null,
46
46
  props,
47
47
  };
48
+ this.aria = { 'role': 'status', 'aria-live': 'polite' };
48
49
  }
49
50
 
50
51
  BaseComponent.extends(Toast);
@@ -47,6 +47,7 @@ export default function Tooltip(content, props = {}) {
47
47
  updatePositionOn: null,
48
48
  props,
49
49
  };
50
+ this.aria = { 'role': 'tooltip' };
50
51
  }
51
52
 
52
53
  BaseComponent.extends(Tooltip);
@@ -23,7 +23,12 @@ const MemoryManager = (function() {
23
23
  $observables.delete(id);
24
24
  },
25
25
  getObservableById(id) {
26
- return $observables.get(id)?.deref();
26
+ const observable = $observables.get(id);
27
+ if(!observable) {
28
+ $observables.delete(id);
29
+ return null;
30
+ }
31
+ return observable.deref();
27
32
  },
28
33
  cleanup() {
29
34
  for (const [_, weakObservableRef] of $observables) {
@@ -18,10 +18,10 @@ import {$getFromStorage, $saveToStorage} from '../utils/localstorage';
18
18
  * @param {boolean} [configs.propagation] - Controls whether changes propagate to parent observables
19
19
  * @example
20
20
  * const count = new ObservableItem(0);
21
- * const name = new ObservableItem('John', { reset: true });
21
+ * const name = new ObservableItem('John', { reset: true });
22
22
  */
23
23
  export default function ObservableItem(value, configs = null) {
24
- value = Validator.isObservable(value) ? value.val() : value;
24
+ value = value?.__$Observable ? value.val() : value;
25
25
 
26
26
  this.$previousValue = null;
27
27
  this.$currentValue = value;
@@ -33,9 +33,9 @@ export default function ObservableItem(value, configs = null) {
33
33
  this.$listeners = null;
34
34
  this.$watchers = null;
35
35
 
36
- this.$memoryId = null;
36
+ this.$id = null;
37
37
 
38
- if(configs) {
38
+ if(configs !== null) {
39
39
  this.configs = configs;
40
40
  if(configs.reset) {
41
41
  this.$initialValue = Validator.isObject(value) ? deepClone(value) : value;
@@ -124,6 +124,9 @@ ObservableItem.prototype.triggerListeners = function(operations) {
124
124
  * @param {{ action?: string, args?: any[], result?: any }} [operations] - Mutation metadata
125
125
  */
126
126
  ObservableItem.prototype.triggerWatchers = function(operations) {
127
+ if(!this.$watchers) {
128
+ return;
129
+ }
127
130
  const $watchers = this.$watchers;
128
131
  const $previousValue = this.$previousValue;
129
132
  const $currentValue = this.$currentValue;
@@ -160,6 +163,7 @@ ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations)
160
163
  this.triggerFirstListener(operations);
161
164
  };
162
165
 
166
+
163
167
  /**
164
168
  * Selects and assigns the optimal trigger strategy based on the current
165
169
  * combination of listeners and watchers (internal).
@@ -167,31 +171,40 @@ ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations)
167
171
  *
168
172
  * @internal
169
173
  */
170
- ObservableItem.prototype.assocTrigger = function() {
174
+ ObservableItem.prototype.$runAssocTrigger = function() {
171
175
  this.$firstListener = null;
172
176
  if(this.$watchers?.size && this.$listeners?.length) {
173
- this.$firstListener = this.$listeners[0];
174
- this.trigger = this.$firstListener.length === 0 ? this.$firstListener : this.triggerFirstListener;
175
- this.trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
177
+ this.$trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
176
178
  return;
177
179
  }
178
180
  if(this.$listeners?.length) {
179
181
  if(this.$listeners.length === 1) {
180
182
  this.$firstListener = this.$listeners[0];
181
- this.trigger = this.$firstListener.length === 0 ? this.$firstListener : this.triggerFirstListener;
183
+ this.$trigger = this.$firstListener.length === 0 ? this.$firstListener : this.triggerFirstListener;
182
184
  }
183
185
  else {
184
- this.trigger = this.triggerListeners;
186
+ this.$trigger = this.triggerListeners;
185
187
  }
186
188
  return;
187
189
  }
188
190
  if(this.$watchers?.size) {
189
- this.trigger = this.triggerWatchers;
191
+ this.$trigger = this.triggerWatchers;
190
192
  return;
191
193
  }
192
- this.trigger = noneTrigger;
194
+ this.$trigger = noneTrigger;
193
195
  };
194
- ObservableItem.prototype.trigger = noneTrigger;
196
+ ObservableItem.prototype.assocTrigger = function() {
197
+ this.$trigger = null;
198
+ };
199
+ Object.defineProperty(ObservableItem.prototype, 'trigger', {
200
+ value: function (operations) {
201
+ if(!this.$trigger) {
202
+ this.$runAssocTrigger();
203
+ }
204
+ return this.$trigger(operations);
205
+ },
206
+ configurable: false,
207
+ });
195
208
 
196
209
 
197
210
  const $setOperation = { action: 'set' };
@@ -295,7 +308,7 @@ ObservableItem.prototype.cleanup = function() {
295
308
  }
296
309
  this.$cleanupListeners = null;
297
310
  }
298
- MemoryManager.unregister(this.$memoryId);
311
+ MemoryManager.unregister(this.$id);
299
312
  this.disconnectAll();
300
313
  if(process.env.NODE_ENV === 'development') {
301
314
  this.$isCleanedUp = true;
@@ -543,7 +556,12 @@ ObservableItem.prototype.reset = function() {
543
556
  * @returns {string} String representation of the current value
544
557
  */
545
558
  ObservableItem.prototype.toString = function() {
546
- return String(this.$currentValue);
559
+ if(this.$id) {
560
+ return this.$id.toString();
561
+ }
562
+ const id = MemoryManager.register(this);
563
+ this.$id = id;
564
+ return '{{obs:'+id+'}}';
547
565
  };
548
566
 
549
567
  /**
@@ -198,14 +198,13 @@ ObservableObject.prototype.$updateWith = ObservableObject.prototype.set;
198
198
 
199
199
  /**
200
200
  * Returns an array of all internal observable instances (one per property).
201
- * Alias: $observables()
202
201
  *
203
202
  * @returns {ObservableItem[]} Array of observable instances
204
203
  */
205
204
  ObservableObject.prototype.observables = function() {
206
205
  return Object.values(this.$observables);
207
206
  };
208
- ObservableObject.prototype.$observables = ObservableObject.prototype.observables;
207
+ // ObservableObject.prototype.$observables = ObservableObject.prototype.observables;
209
208
 
210
209
  /**
211
210
  * Returns all property names of the observable object.
@@ -2,6 +2,7 @@ import Validator from '../../utils/validator';
2
2
  import {ElementCreator} from '../../wrappers/ElementCreator';
3
3
  import AnchorWithSentinel from './anchor-with-sentinel';
4
4
  import oneChildAnchorOverwriting from './one-child-anchor-overwriting';
5
+ import DebugManager from '../../utils/debug-manager';
5
6
 
6
7
  /**
7
8
  * Creates an anchor fragment — a managed DocumentFragment delimited by comment sentinels.
@@ -34,7 +35,7 @@ export default function Anchor(name, isUniqueChild = false) {
34
35
  ? () => true: (parent) => (parent.firstChild === anchorStart && parent.lastChild === anchorEnd);
35
36
 
36
37
  const insertBefore = (parent, child, target) => {
37
- const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
38
+ const childElement = child.__$isNativeNode ? child : ElementCreator.getChild(child);
38
39
  insertBeforeRaw(parent, childElement, target);
39
40
  };
40
41
 
@@ -84,7 +85,7 @@ export default function Anchor(name, isUniqueChild = false) {
84
85
  anchorFragment.appendRaw = anchorFragment.appendChildRaw;
85
86
 
86
87
  anchorFragment.insertAtStart = function(child) {
87
- child = Validator.isElement(child) ? child : ElementCreator.getChild(child);
88
+ child = child.__$isNativeNode? child : ElementCreator.getChild(child);
88
89
  anchorFragment.insertAtStartRaw(child);
89
90
  };
90
91
 
@@ -141,7 +142,7 @@ export default function Anchor(name, isUniqueChild = false) {
141
142
  anchorFragment.delete = anchorFragment.removeWithAnchors;
142
143
 
143
144
  anchorFragment.replaceContent = function(child) {
144
- const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
145
+ const childElement = child.__$isNativeNode ? child : ElementCreator.getChild(child);
145
146
  anchorFragment.replaceContentRaw(childElement);
146
147
  };
147
148
 
@@ -10,7 +10,7 @@ export default function oneChildAnchorOverwriting(anchor, parent) {
10
10
  anchor.getParent = () => parent;
11
11
 
12
12
  anchor.appendChild = (child) => {
13
- child = Validator.isElement(child) ? child : ElementCreator.getChild(child);
13
+ child = child.__$isNativeNode ? child : ElementCreator.getChild(child);
14
14
  parent.appendChild(child);
15
15
  };
16
16
 
@@ -19,7 +19,7 @@ export default function oneChildAnchorOverwriting(anchor, parent) {
19
19
  anchor.appendRaw = anchor.appendChildRaw;
20
20
 
21
21
  anchor.insertAtStart = (child) => {
22
- child = Validator.isElement(child) ? child : ElementCreator.getChild(child);
22
+ child = child.__$isNativeNode ? child : ElementCreator.getChild(child);
23
23
  parent.firstChild ? parent.insertBefore(child, parent.firstChild) : parent.appendChild(child);
24
24
  };
25
25
  anchor.insertAtStartRaw = (child) => {
@@ -33,7 +33,7 @@ export default function oneChildAnchorOverwriting(anchor, parent) {
33
33
  };
34
34
 
35
35
  anchor.replaceContent = function(content) {
36
- const child = Validator.isElement(content) ? content : ElementCreator.getChild(content);
36
+ const child = (content && content.__$isNativeNode) ? content : ElementCreator.getChild(content);
37
37
  parent.replaceChildren(child);
38
38
  };
39
39
 
@@ -43,7 +43,7 @@ export default function oneChildAnchorOverwriting(anchor, parent) {
43
43
  anchor.setContent = anchor.replaceContent;
44
44
 
45
45
  anchor.insertBefore = (child, anchor) => {
46
- child = Validator.isElement(child) ? child : ElementCreator.getChild(child);
46
+ child = child.__$isNativeNode ? child : ElementCreator.getChild(child);
47
47
  parent.insertBefore(child, anchor);
48
48
  };
49
49
  anchor.insertBeforeRaw = (child, anchor) => {
@@ -24,6 +24,25 @@ export const Label = HtmlElementWrapper('label');
24
24
  */
25
25
  export const P = HtmlElementWrapper('p');
26
26
 
27
+
28
+ /**
29
+ * Parses a string containing Observable placeholders and returns an array
30
+ * of strings and Observable instances ready to be used as NativeDocument children.
31
+ *
32
+ * Observable placeholders are generated automatically when an ObservableItem
33
+ * is interpolated in a template string via its toString() method.
34
+ *
35
+ * @param {string} string - Template string with optional Observable placeholders
36
+ * @returns {Array<string|ObservableItem>} Mixed array of static strings and resolved Observables
37
+ * @example
38
+ * const name = Observable('John');
39
+ * const count = Observable(5);
40
+ *
41
+ * Text(`Hello ${name}, count: ${count}`)
42
+ * // → ['Hello ', ObservableItem(John), ', count: ', ObservableItem(5)]
43
+ */
44
+ export const Text = (string) => string.toNdChildren();
45
+
27
46
  /**
28
47
  * Alias for {@link P}.
29
48
  * @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLParagraphElement}
@@ -0,0 +1,59 @@
1
+
2
+
3
+ export default function ForEachArrayCache(isIndexesRequired) {
4
+ this.$nodes = new Map();
5
+ this.$indexes = isIndexesRequired ? new Map() : null;
6
+
7
+ this.has = this.$nodes.has.bind(this.$nodes);
8
+
9
+ this.keys = function() {
10
+ return Array.from(this.$nodes.keys());
11
+ };
12
+
13
+ if(isIndexesRequired) {
14
+ this.delete = function(item) {
15
+ this.$nodes.get(item)?.nd.destroy();
16
+ this.$nodes.delete(item);
17
+ this.$indexes.delete(item);
18
+ };
19
+
20
+ this.set = function(item, child, index) {
21
+ this.$nodes.set(item, child);
22
+ this.$indexes.set(item, index);
23
+ };
24
+
25
+ this.clear = function() {
26
+ for(const [_, node] in this.$nodes.entries()) {
27
+ node.nd.destroy();
28
+ }
29
+ this.$nodes.clear();
30
+ this.$indexes.clear();
31
+ };
32
+
33
+ this.get = function(item) {
34
+ return {
35
+ child: this.$nodes.get(item),
36
+ indexObserver: this.$indexes.get(item),
37
+ };
38
+ };
39
+ } else {
40
+
41
+ this.delete = (item) => {
42
+ this.$nodes.get(item)?.nd.destroy();
43
+ this.$nodes.delete(item);
44
+ };
45
+ this.set = this.$nodes.set.bind(this.$nodes);
46
+ this.clear = function() {
47
+ for(const [_, node] in this.$nodes.entries()) {
48
+ node.nd.destroy();
49
+ }
50
+ this.$nodes.clear();
51
+ };
52
+
53
+ this.get = function(item) {
54
+ return { child: this.$nodes.get(item), indexObserver: null };
55
+ };
56
+ }
57
+
58
+ }
59
+
@@ -1,6 +1,7 @@
1
1
  import Anchor from '../anchor/anchor';
2
2
  import { ElementCreator } from '../../wrappers/ElementCreator';
3
3
  import NativeDocumentError from '../../errors/NativeDocumentError';
4
+ import ForEachArrayCache from './for-each-array-cache';
4
5
 
5
6
 
6
7
  const CREATE_AND_CACHE_ACTIONS = new Set(['clear', 'push', 'unshift', 'replace']);
@@ -30,9 +31,9 @@ export function ForEachArray(data, callback, configs = {}) {
30
31
  const element = Anchor('ForEach Array', configs.isParentUniqueChild);
31
32
  const blockEnd = element.endElement();
32
33
 
33
- const cache = new Map();
34
34
  let lastNumberOfItems = 0;
35
35
  const isIndexRequired = callback.length >= 2;
36
+ const cache = new ForEachArrayCache(isIndexRequired);
36
37
 
37
38
  const clear = (items) => {
38
39
  element.removeChildren();
@@ -64,7 +65,7 @@ export function ForEachArray(data, callback, configs = {}) {
64
65
  throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
65
66
  }
66
67
  }
67
- cache.set(item, { child, indexObserver: null });
68
+ cache.set(item, child);
68
69
  return child;
69
70
  };
70
71
 
@@ -76,7 +77,7 @@ export function ForEachArray(data, callback, configs = {}) {
76
77
  throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
77
78
  }
78
79
  }
79
- cache.set(item, { child, indexObserver });
80
+ cache.set(item, child, indexObserver);
80
81
  return child;
81
82
  };
82
83
  if(!data.__$Observable) {
@@ -87,7 +88,7 @@ export function ForEachArray(data, callback, configs = {}) {
87
88
  throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
88
89
  }
89
90
  }
90
- cache.set(item, { child, indexObserver: null });
91
+ cache.set(item, child);
91
92
  return child;
92
93
  };
93
94
  }
@@ -150,14 +151,14 @@ export function ForEachArray(data, callback, configs = {}) {
150
151
  if(Array.isArray(data)) {
151
152
  set = () => {
152
153
  clear(data);
153
- element.appendChildRaw(Actions.toFragment(data));
154
+ Actions.appendToElement(data);
154
155
  };
155
156
  }
156
157
  else {
157
158
  set = () => {
158
159
  const items = data.val();
159
160
  clear(items);
160
- element.appendChildRaw(Actions.toFragment(items));
161
+ Actions.appendToElement(items);
161
162
  };
162
163
  }
163
164
 
@@ -166,45 +167,36 @@ export function ForEachArray(data, callback, configs = {}) {
166
167
  const fragment = document.createDocumentFragment();
167
168
  for(let i = 0, length = items.length; i < length; i++) {
168
169
  fragment.appendChild(buildItem(items[i], lastNumberOfItems));
169
- lastNumberOfItems++;
170
170
  }
171
171
  return fragment;
172
172
  },
173
+ appendToElement: (items) => {
174
+ for(let i = 0, length = items.length; i < length; i++) {
175
+ element.appendChild(buildItem(items[i], i));
176
+ }
177
+ },
173
178
  add: (items) => {
174
- element.appendChildRaw(Actions.toFragment(items));
179
+ Actions.appendToElement(items);
175
180
  },
176
181
  replace: (items) => {
177
182
  clear(items);
178
- element.appendChildRaw(Actions.toFragment(items));
183
+ Actions.appendToElement(items);
179
184
  },
180
185
  set,
181
186
  reOrder: (items) => {
182
187
  let child = null;
183
- const fragment = document.createDocumentFragment();
184
188
  for(const item of items) {
185
189
  child = getItemChild(item);
186
190
  if(child) {
187
- fragment.appendChild(child);
191
+ element.appendChild(child);
188
192
  }
189
193
  }
190
194
  child = null;
191
- element.appendChildRaw(fragment);
192
195
  },
193
196
  removeOne: (element, index) => {
194
197
  removeCacheItem(element, true);
195
198
  },
196
199
  clear,
197
- populate: ([target, iteration, callback]) => {
198
- const fragment = document.createDocumentFragment();
199
- for (let i = 0; i < iteration; i++) {
200
- const data = callback(i);
201
- target.push(data);
202
- fragment.append(buildItem(data, i));
203
- lastNumberOfItems++;
204
- }
205
- element.appendChildRaw(fragment);
206
- fragment.replaceChildren();
207
- },
208
200
  unshift: (values) => {
209
201
  element.insertAtStartRaw(Actions.toFragment(values));
210
202
  },
@@ -1,8 +1,9 @@
1
1
  import Validator from '../utils/validator';
2
- import NativeDocumentError from '../errors/NativeDocumentError';
3
2
  import {BOOL_ATTRIBUTES_NAME, BOOLEAN_ATTRIBUTES} from './constants.js';
4
3
  import {Observable} from '../data/Observable';
5
4
 
5
+
6
+
6
7
  /**
7
8
  * Applies a reactive class map to an HTMLElement.
8
9
  * Each key is a CSS class name; each value is a boolean or ObservableItem<boolean>.
@@ -14,7 +15,6 @@ import {Observable} from '../data/Observable';
14
15
  export const bindClassAttribute = (element, data) => {
15
16
  for(const className in data) {
16
17
  const value = data[className];
17
-
18
18
  if (value.__$isObservableChecker) {
19
19
  let lastClass = value.val();
20
20
  if (typeof lastClass === 'string') {
@@ -24,19 +24,19 @@ export const bindClassAttribute = (element, data) => {
24
24
  element.classes.toggle(currentValue, true);
25
25
  lastClass = currentValue;
26
26
  });
27
- continue;
27
+ return;
28
28
  }
29
29
  }
30
30
 
31
31
  if (value.__$Observable) {
32
32
  element.classes.toggle(className, value.val());
33
33
  value.subscribe((shouldAdd) => element.classes.toggle(className, shouldAdd));
34
- continue;
34
+ return;
35
35
  }
36
36
 
37
37
  if (value.$hydrate) {
38
38
  value.$hydrate(element, className);
39
- continue;
39
+ return;
40
40
  }
41
41
 
42
42
  element.classes.toggle(className, value);
@@ -99,7 +99,7 @@ export const bindBooleanAttribute = (element, attributeName, value) => {
99
99
  const isObservable = value.__$Observable;
100
100
  const defaultValue = isObservable? value.val() : value;
101
101
 
102
- const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName];
102
+ const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName] || attributeName;
103
103
 
104
104
  if(typeof defaultValue === 'boolean') {
105
105
  element[attributeRealName] = defaultValue;
@@ -145,29 +145,29 @@ export const bindAttributeWithObservable = (element, attributeName, value) => {
145
145
  * @param {HTMLElement} element
146
146
  * @param {Object} attributes
147
147
  */
148
- const AttributesWrapper = (element, attributes) => {
148
+ const AttributesWrapper = (element, attributes = {}) => {
149
149
 
150
150
  if(process.env.NODE_ENV === 'development') {
151
151
  Validator.validateAttributes(attributes);
152
152
  }
153
153
 
154
- for(const originalAttributeName in attributes) {
155
- const value = attributes[originalAttributeName];
154
+ for(const attributeName in attributes) {
155
+ const value = attributes[attributeName];
156
156
  if(value == null) {
157
157
  continue;
158
158
  }
159
159
  const type = typeof value;
160
160
  if(type === 'string' || type === 'number') {
161
- element.setAttribute(originalAttributeName, value);
161
+ element.setAttribute(attributeName, value);
162
162
  continue;
163
163
  }
164
- const attributeName = originalAttributeName.toLowerCase();
164
+ // const attributeName = originalAttributeName.toLowerCase();
165
165
  if(value.__$Observable) {
166
166
  if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
167
167
  bindBooleanAttribute(element, attributeName, value);
168
168
  continue;
169
169
  }
170
- bindAttributeWithObservable(element, originalAttributeName, value);
170
+ bindAttributeWithObservable(element, attributeName, value);
171
171
  continue;
172
172
  }
173
173
  if(type === 'object') {