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
@@ -0,0 +1,146 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { Observable } from '../../../src/core/data/Observable';
3
+
4
+ vi.mock('../../../src/core/utils/debug-manager', () => ({
5
+ default: { log: vi.fn(), warn: vi.fn(), error: vi.fn(), disable: vi.fn() },
6
+ }));
7
+
8
+ import { Div } from '../../../elements';
9
+
10
+ // Wait for MutationObserver to fire
11
+ const tick = () => new Promise((r) => setTimeout(r, 0));
12
+
13
+ // Helper - returns the HTMLElement and a stable .nd reference
14
+ const mkEl = (text = 'content') => {
15
+ const el = Div({}, text);
16
+ return { el, nd: el.nd };
17
+ };
18
+
19
+ describe('Lifecycle', () => {
20
+
21
+ // -- mounted() ------------------------------------------------------------
22
+
23
+ describe('mounted()', () => {
24
+ it('callback is called when element is inserted into DOM', async () => {
25
+ const { el, nd } = mkEl('hello');
26
+ const cb = vi.fn();
27
+ nd.mounted(cb);
28
+ document.body.innerHTML = '';
29
+ document.body.appendChild(el);
30
+ await tick();
31
+ expect(cb).toHaveBeenCalledOnce();
32
+ });
33
+
34
+ it('callback is not called before insertion', async () => {
35
+ const { nd } = mkEl('hello');
36
+ const cb = vi.fn();
37
+ nd.mounted(cb);
38
+ await tick();
39
+ expect(cb).not.toHaveBeenCalled();
40
+ });
41
+
42
+ it('multiple mounted callbacks are all called', async () => {
43
+ const { el, nd } = mkEl('hello');
44
+ const cb1 = vi.fn();
45
+ const cb2 = vi.fn();
46
+ nd.mounted(cb1);
47
+ nd.mounted(cb2);
48
+ document.body.innerHTML = '';
49
+ document.body.appendChild(el);
50
+ await tick();
51
+ expect(cb1).toHaveBeenCalledOnce();
52
+ expect(cb2).toHaveBeenCalledOnce();
53
+ });
54
+
55
+ it('lifecycle({ mounted }) shorthand works', async () => {
56
+ const { el, nd } = mkEl('hello');
57
+ const cb = vi.fn();
58
+ nd.lifecycle({ mounted: cb });
59
+ document.body.innerHTML = '';
60
+ document.body.appendChild(el);
61
+ await tick();
62
+ expect(cb).toHaveBeenCalledOnce();
63
+ });
64
+ });
65
+
66
+ // -- unmounted() ----------------------------------------------------------
67
+
68
+ describe('unmounted()', () => {
69
+ it('callback is called when element is removed from DOM', async () => {
70
+ const { el, nd } = mkEl('hello');
71
+ const cb = vi.fn();
72
+ nd.unmounted(cb);
73
+ document.body.innerHTML = '';
74
+ document.body.appendChild(el);
75
+ await tick();
76
+ el.remove();
77
+ await tick();
78
+ expect(cb).toHaveBeenCalledOnce();
79
+ });
80
+
81
+ it('callback is not called before removal', async () => {
82
+ const { el, nd } = mkEl('hello');
83
+ const cb = vi.fn();
84
+ nd.unmounted(cb);
85
+ document.body.innerHTML = '';
86
+ document.body.appendChild(el);
87
+ await tick();
88
+ expect(cb).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('lifecycle({ unmounted }) shorthand works', async () => {
92
+ const { el, nd } = mkEl('hello');
93
+ const cb = vi.fn();
94
+ nd.lifecycle({ unmounted: cb });
95
+ document.body.innerHTML = '';
96
+ document.body.appendChild(el);
97
+ await tick();
98
+ el.remove();
99
+ await tick();
100
+ expect(cb).toHaveBeenCalledOnce();
101
+ });
102
+ });
103
+
104
+ // -- destroy() ------------------------------------------------------------
105
+
106
+ describe('destroy()', () => {
107
+ it('nulls the internal $element reference', () => {
108
+ const { nd } = mkEl('hello');
109
+ nd.destroy();
110
+ expect(nd.$element).toBeNull();
111
+ });
112
+
113
+ it('does not throw when called multiple times', () => {
114
+ const { nd } = mkEl('hello');
115
+ nd.destroy();
116
+ expect(() => nd.destroy()).not.toThrow();
117
+ });
118
+
119
+ it('unsubscribes from lifecycle callbacks after destroy', async () => {
120
+ const { el, nd } = mkEl('hello');
121
+ const cb = vi.fn();
122
+ nd.mounted(cb);
123
+ nd.destroy();
124
+ document.body.innerHTML = '';
125
+ document.body.appendChild(el);
126
+ await tick();
127
+ expect(cb).not.toHaveBeenCalled();
128
+ });
129
+ });
130
+
131
+ // -- destroyOnUnmount() ---------------------------------------------------
132
+
133
+ describe('destroyOnUnmount()', () => {
134
+ it('nulls $element when element is removed from DOM', async () => {
135
+ const { el, nd } = mkEl('hello');
136
+ nd.destroyOnUnmount();
137
+ document.body.innerHTML = '';
138
+ document.body.appendChild(el);
139
+ await tick();
140
+ el.remove();
141
+ await tick();
142
+ expect(nd.$element).toBeNull();
143
+ });
144
+ });
145
+
146
+ });
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { Observable } from '../../../src/core/data/Observable';
3
+
4
+ vi.mock('../../../src/core/utils/debug-manager', () => ({
5
+ default: { log: vi.fn(), warn: vi.fn(), error: vi.fn(), disable: vi.fn() },
6
+ }));
7
+
8
+ // Initialize NDElement prototypes (toNdElement, nd, etc.)
9
+ import { Div, ShowIf, HideIf, HideIfNot } from '../../../elements';
10
+
11
+ // Helper - create a NativeDocument Div element (requires toNdElement prototype)
12
+ const el = (text = 'content') => Div({}, text);
13
+
14
+ // Mount anchor into the document to enable DOM queries
15
+ const mount = (anchor) => {
16
+ document.body.innerHTML = '';
17
+ document.body.appendChild(anchor);
18
+ return document.body;
19
+ };
20
+
21
+ describe('ShowIf', () => {
22
+
23
+ // -- static boolean -------------------------------------------------------
24
+
25
+ describe('static boolean condition', () => {
26
+ it('returns the element when condition is true', () => {
27
+ const result = ShowIf(true, el('hello'));
28
+ expect(result).not.toBeNull();
29
+ });
30
+
31
+ it('returns null when condition is false', () => {
32
+ const result = ShowIf(false, el('hello'));
33
+ expect(result).toBeNull();
34
+ });
35
+ });
36
+
37
+ // -- observable condition -------------------------------------------------
38
+
39
+ describe('observable condition', () => {
40
+ it('renders content when condition is initially true', () => {
41
+ const visible = Observable(true);
42
+ const anchor = ShowIf(visible, el('hello'));
43
+ const body = mount(anchor);
44
+ expect(body.textContent).toContain('hello');
45
+ });
46
+
47
+ it('does not render content when condition is initially false', () => {
48
+ const visible = Observable(false);
49
+ const anchor = ShowIf(visible, el('hello'));
50
+ const body = mount(anchor);
51
+ expect(body.textContent).not.toContain('hello');
52
+ });
53
+
54
+ it('mounts content when observable changes to true', () => {
55
+ const visible = Observable(false);
56
+ const anchor = ShowIf(visible, el('hello'));
57
+ const body = mount(anchor);
58
+ visible.set(true);
59
+ expect(body.textContent).toContain('hello');
60
+ });
61
+
62
+ it('unmounts content when observable changes to false', () => {
63
+ const visible = Observable(true);
64
+ const anchor = ShowIf(visible, el('hello'));
65
+ const body = mount(anchor);
66
+ visible.set(false);
67
+ expect(body.textContent).not.toContain('hello');
68
+ });
69
+
70
+ it('remounts content on second true', () => {
71
+ const visible = Observable(true);
72
+ const anchor = ShowIf(visible, el('hello'));
73
+ const body = mount(anchor);
74
+ visible.set(false);
75
+ visible.set(true);
76
+ expect(body.textContent).toContain('hello');
77
+ });
78
+ });
79
+
80
+ // -- factory child --------------------------------------------------------
81
+
82
+ describe('factory child', () => {
83
+ it('accepts a function as child', () => {
84
+ const visible = Observable(true);
85
+ const factory = () => el('from factory');
86
+ const anchor = ShowIf(visible, factory);
87
+ const body = mount(anchor);
88
+ expect(body.textContent).toContain('from factory');
89
+ });
90
+
91
+ it('shouldKeepInCache=false re-creates element on each mount', () => {
92
+ let callCount = 0;
93
+ const visible = Observable(false);
94
+ const factory = () => { callCount++; return el('dynamic'); };
95
+ ShowIf(visible, factory, { shouldKeepInCache: false });
96
+ visible.set(true);
97
+ visible.set(false);
98
+ visible.set(true);
99
+ expect(callCount).toBe(2);
100
+ });
101
+
102
+ it('shouldKeepInCache=true (default) creates element only once', () => {
103
+ let callCount = 0;
104
+ const visible = Observable(false);
105
+ const factory = () => { callCount++; return el('cached'); };
106
+ ShowIf(visible, factory, { shouldKeepInCache: true });
107
+ visible.set(true);
108
+ visible.set(false);
109
+ visible.set(true);
110
+ expect(callCount).toBe(1);
111
+ });
112
+ });
113
+
114
+ // -- non-observable condition ---------------------------------------------
115
+
116
+ describe('non-observable condition', () => {
117
+ it('does not throw for invalid condition type', () => {
118
+ // DebugManager.warn is already mocked via vi.mock at the top
119
+ expect(() => ShowIf('invalid', el())).not.toThrow();
120
+ });
121
+ });
122
+
123
+ });
124
+
125
+ // -- HideIf ------------------------------------------------------------------
126
+
127
+ describe('HideIf', () => {
128
+ it('hides content when condition is true', () => {
129
+ const flag = Observable(true);
130
+ const anchor = HideIf(flag, el('hidden'));
131
+ const body = mount(anchor);
132
+ expect(body.textContent).not.toContain('hidden');
133
+ });
134
+
135
+ it('shows content when condition is false', () => {
136
+ const flag = Observable(false);
137
+ const anchor = HideIf(flag, el('visible'));
138
+ const body = mount(anchor);
139
+ expect(body.textContent).toContain('visible');
140
+ });
141
+
142
+ it('reacts when condition changes', () => {
143
+ const flag = Observable(false);
144
+ const anchor = HideIf(flag, el('content'));
145
+ const body = mount(anchor);
146
+ expect(body.textContent).toContain('content');
147
+ flag.set(true);
148
+ expect(body.textContent).not.toContain('content');
149
+ });
150
+ });
151
+
152
+ // -- HideIfNot ---------------------------------------------------------------
153
+
154
+ describe('HideIfNot', () => {
155
+ it('shows content when condition is true (same as ShowIf)', () => {
156
+ const visible = Observable(true);
157
+ const anchor = HideIfNot(visible, el('shown'));
158
+ const body = mount(anchor);
159
+ expect(body.textContent).toContain('shown');
160
+ });
161
+
162
+ it('hides content when condition is false', () => {
163
+ const visible = Observable(false);
164
+ const anchor = HideIfNot(visible, el('shown'));
165
+ const body = mount(anchor);
166
+ expect(body.textContent).not.toContain('shown');
167
+ });
168
+ });