native-document 1.0.169 → 1.0.172
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/CHANGELOG.md +62 -1
- package/docs/vitepress-conventions.md +2 -2
- package/package.json +11 -4
- package/src/components/BaseComponent.js +43 -1
- package/src/components/accordion/Accordion.js +1 -0
- package/src/components/accordion/AccordionItem.js +1 -0
- package/src/components/alert/Alert.js +4 -0
- package/src/components/avatar/Avatar.js +1 -0
- package/src/components/avatar/AvatarGroup.js +1 -0
- package/src/components/badge/Badge.js +1 -0
- package/src/components/breadcrumb/BreadCrumb.js +4 -0
- package/src/components/button/Button.js +1 -0
- package/src/components/card/Card.js +1 -0
- package/src/components/context-menu/ContextMenu.js +2 -0
- package/src/components/context-menu/ContextMenuGroup.js +2 -0
- package/src/components/context-menu/ContextMenuItem.js +2 -0
- package/src/components/divider/Divider.js +2 -0
- package/src/components/dropdown/Dropdown.js +1 -0
- package/src/components/dropdown/DropdownDivider.js +1 -0
- package/src/components/dropdown/DropdownGroup.js +1 -0
- package/src/components/dropdown/DropdownItem.js +1 -0
- package/src/components/dropdown/DropdownTrigger.js +1 -0
- package/src/components/form/FormControl.js +1 -0
- package/src/components/form/field/Field.js +1 -0
- package/src/components/form/field/types/StringField.js +1 -1
- package/src/components/icon/Icon.js +1 -0
- package/src/components/list/ListItem.js +1 -0
- package/src/components/menu/Menu.js +3 -0
- package/src/components/menu/MenuDivider.js +1 -0
- package/src/components/menu/MenuGroup.js +1 -0
- package/src/components/menu/MenuItem.js +1 -0
- package/src/components/menu/MenuLink.js +1 -0
- package/src/components/modal/Modal.js +4 -0
- package/src/components/pagination/Pagination.js +1 -0
- package/src/components/popover/Popover.js +1 -0
- package/src/components/popover/PopoverFooter.js +1 -0
- package/src/components/popover/PopoverHeader.js +1 -0
- package/src/components/progress/Progress.js +5 -0
- package/src/components/skeleton/Skeleton.js +4 -0
- package/src/components/slider/Slider.js +1 -0
- package/src/components/spacer/Spacer.js +1 -0
- package/src/components/spinner/Spinner.js +1 -0
- package/src/components/splitter/Splitter.js +1 -0
- package/src/components/splitter/SplitterGutter.js +6 -0
- package/src/components/splitter/SplitterPanel.js +1 -0
- package/src/components/stacks/AbsoluteStack.js +1 -0
- package/src/components/stacks/FixedStack.js +1 -0
- package/src/components/stacks/HStack.js +1 -0
- package/src/components/stacks/PositionStack.js +1 -0
- package/src/components/stacks/RelativeStack.js +1 -0
- package/src/components/stacks/Stack.js +1 -0
- package/src/components/stacks/VStack.js +1 -0
- package/src/components/stepper/Stepper.js +1 -0
- package/src/components/stepper/StepperStep.js +1 -0
- package/src/components/switch/Switch.js +1 -0
- package/src/components/table/DataTable.js +1 -0
- package/src/components/table/SimpleTable.js +1 -0
- package/src/components/tabs/Tabs.js +1 -0
- package/src/components/toast/Toast.js +1 -0
- package/src/components/tooltip/Tooltip.js +1 -0
- package/src/core/data/MemoryManager.js +6 -1
- package/src/core/data/ObservableItem.js +28 -10
- package/src/core/elements/anchor/anchor.js +4 -3
- package/src/core/elements/anchor/one-child-anchor-overwriting.js +4 -4
- package/src/core/elements/content-formatter.js +19 -0
- package/src/core/elements/control/for-each-array-cache.js +59 -0
- package/src/core/elements/control/for-each-array.js +15 -23
- package/src/core/wrappers/AttributesWrapper.js +12 -12
- package/src/core/wrappers/ElementCreator.js +33 -10
- package/src/core/wrappers/HtmlElementWrapper.js +9 -4
- package/src/core/wrappers/NDElement.js +23 -8
- package/src/core/wrappers/NdPrototype.js +31 -39
- package/src/core/wrappers/constants.js +12 -12
- package/src/core/wrappers/prototypes/nd-element-extensions.js +127 -57
- package/src/ui/components/accordion/AccordionItemRender.js +29 -7
- package/src/ui/components/alert/AlertRender.js +6 -0
- package/src/ui/components/breadcrumb/BreadcrumbRender.js +9 -1
- package/src/ui/components/button/ButtonRender.js +3 -0
- package/src/ui/components/contextmenu/ContextmenuRender.js +2 -0
- package/src/ui/components/dropdown/DropdownRender.js +3 -0
- package/src/ui/components/dropdown/item/DropdownItemRender.js +6 -1
- package/src/ui/components/form/FieldCollectionRender.js +4 -0
- package/src/ui/components/form/fields/CheckboxFieldRender.js +4 -0
- package/src/ui/components/form/fields/CheckboxGroupFieldRender.js +4 -0
- package/src/ui/components/form/fields/RadioFieldRender.js +4 -0
- package/src/ui/components/form/fields/RangeFieldRender.js +4 -0
- package/src/ui/components/form/fields/SelectFieldRender.js +5 -0
- package/src/ui/components/form/fields/SliderFieldRender.js +3 -0
- package/src/ui/components/icon/tabler/TablerIconRender.js +0 -1
- package/src/ui/components/list/item/ListItemRender.js +4 -0
- package/src/ui/components/menu/MenuItemRender.js +8 -0
- package/src/ui/components/menu/MenuLinkRender.js +11 -0
- package/src/ui/components/menu/MenuRender.js +5 -0
- package/src/ui/components/menu/helpers.js +2 -1
- package/src/ui/components/modal/ModalRender.js +8 -2
- package/src/ui/components/progress/ProgressRender.js +9 -0
- package/src/ui/components/skeleton/SkeletonRender.js +2 -0
- package/src/ui/components/spinner/SpinnerRender.js +3 -0
- package/src/ui/components/splitter/SplitterGutterRender.js +4 -0
- package/src/ui/components/splitter/SplitterPanelRender.js +4 -0
- package/src/ui/components/splitter/SplitterRender.js +2 -0
- package/src/ui/components/stepper/StepperRender.js +2 -1
- package/src/ui/components/stepper/StepperStepRender.js +8 -1
- package/src/ui/components/switch/SwitchRender.js +3 -0
- package/src/ui/components/table/data-table/DataTableRender.js +4 -0
- package/src/ui/components/table/simple-table/SimpleTableRender.js +2 -0
- package/src/ui/components/toast/ToastRender.js +4 -0
- package/src/ui/utils/aria.js +19 -0
- package/tests/integration/ui/attributes-wrapper.test.js +177 -0
- package/tests/integration/ui/for-each-array.test.js +167 -0
- package/tests/integration/ui/lifecycle.test.js +146 -0
- package/tests/integration/ui/show-if.test.js +168 -0
- package/tests/unit/core/observable-array.test.js +404 -0
- package/tests/unit/core/observable-item.test.js +428 -0
- package/tests/unit/core/observable-object.test.js +239 -0
- package/tests/unit/core/observable-resource.test.js +323 -0
- package/tests/unit/core/store.test.js +347 -0
- package/tests/unit/router/route-matching.test.js +169 -0
- package/tests/unit/router/router-guards.test.js +189 -0
- package/tests/unit/utils/cache.test.js +120 -0
- package/tests/unit/utils/filters.test.js +394 -0
- package/types/elements.d.ts +5 -0
- package/vitest.config.js +15 -0
|
@@ -187,16 +187,32 @@ NDElement.prototype.destroyOnUnmount = function() {
|
|
|
187
187
|
* @returns {this}
|
|
188
188
|
*/
|
|
189
189
|
NDElement.prototype.destroy = function() {
|
|
190
|
-
this.$element
|
|
190
|
+
if(!this.$element) {
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
const observer = $lifeCycleObservers.get(this.$element);
|
|
194
|
+
if(observer) {
|
|
195
|
+
observer.disconnect();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const treewalk = document.createTreeWalker(this.$element, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
199
|
+
const nodes = [];
|
|
200
|
+
while(treewalk.nextNode()) {
|
|
201
|
+
nodes.push(treewalk.currentNode);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for(let i = 0, length = nodes.length; i < length; i++) {
|
|
205
|
+
const child = nodes[i];
|
|
191
206
|
child.remove();
|
|
192
|
-
|
|
193
|
-
|
|
207
|
+
const childObserver = $lifeCycleObservers.get(child);
|
|
208
|
+
if(childObserver) {
|
|
209
|
+
childObserver.disconnect();
|
|
210
|
+
}
|
|
194
211
|
$lifeCycleObservers.delete(child);
|
|
195
|
-
}
|
|
212
|
+
}
|
|
196
213
|
|
|
197
|
-
this.$element.__$controller?.abort();
|
|
198
|
-
this.$element.__$controller = null;
|
|
199
214
|
$lifeCycleObservers.delete(this.$element);
|
|
215
|
+
this.$element.remove();
|
|
200
216
|
this.$element = null;
|
|
201
217
|
};
|
|
202
218
|
|
|
@@ -418,7 +434,6 @@ NDElement.prototype.style = function(style) {
|
|
|
418
434
|
return this;
|
|
419
435
|
};
|
|
420
436
|
|
|
421
|
-
|
|
422
437
|
/**
|
|
423
438
|
* Extends the NDElement prototype with new methods available to all NDElement instances.
|
|
424
439
|
* Use this to add global methods to all NDElements.
|
|
@@ -618,7 +633,7 @@ NDElement.prototype.contentEditable = function($obs, {format = 'html'} = {}) {
|
|
|
618
633
|
|
|
619
634
|
this.$element.addEventListener('input', () => {
|
|
620
635
|
$obs?.set(getValue());
|
|
621
|
-
}
|
|
636
|
+
});
|
|
622
637
|
|
|
623
638
|
return this;
|
|
624
639
|
};
|
|
@@ -20,65 +20,42 @@ Object.defineProperty(NDElement.prototype, 'nd', {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
// ----------------------------------------------------------------
|
|
25
24
|
// Events helpers
|
|
26
25
|
// ----------------------------------------------------------------
|
|
27
26
|
EVENTS.forEach(eventSourceName => {
|
|
28
27
|
const eventName = eventSourceName.toLowerCase();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const inlineHandler = 'on'+eventName;
|
|
29
|
+
NDElement.prototype['on'+eventSourceName] = function(callback = null, options = null) {
|
|
30
|
+
if(!this.$element[inlineHandler] && !options) {
|
|
31
|
+
this.$element[inlineHandler] = callback;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
this.$element.addEventListener(eventName, callback, options);
|
|
34
35
|
return this;
|
|
35
36
|
};
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
EVENTS_WITH_STOP.forEach(eventSourceName => {
|
|
39
40
|
const eventName = eventSourceName.toLowerCase();
|
|
40
|
-
NDElement.prototype['onStop'+eventSourceName] = function(callback = null, options =
|
|
41
|
-
_stop(this.$element, eventName, callback,
|
|
42
|
-
signal: this.$getSignal(),
|
|
43
|
-
...options,
|
|
44
|
-
});
|
|
41
|
+
NDElement.prototype['onStop'+eventSourceName] = function(callback = null, options = null) {
|
|
42
|
+
_stop(this.$element, eventName, callback, options);
|
|
45
43
|
return this;
|
|
46
44
|
};
|
|
47
|
-
NDElement.prototype['onPreventStop'+eventSourceName] = function(callback = null, options =
|
|
48
|
-
_preventStop(this.$element, eventName, callback,
|
|
49
|
-
signal: this.$getSignal(),
|
|
50
|
-
...options,
|
|
51
|
-
});
|
|
45
|
+
NDElement.prototype['onPreventStop'+eventSourceName] = function(callback = null, options = null) {
|
|
46
|
+
_preventStop(this.$element, eventName, callback, options);
|
|
52
47
|
return this;
|
|
53
48
|
};
|
|
54
49
|
});
|
|
55
50
|
|
|
56
51
|
EVENTS_WITH_PREVENT.forEach(eventSourceName => {
|
|
57
52
|
const eventName = eventSourceName.toLowerCase();
|
|
58
|
-
NDElement.prototype['onPrevent'+eventSourceName] = function(callback = null, options =
|
|
59
|
-
_prevent(this.$element, eventName, callback,
|
|
60
|
-
signal: this.$getSignal(),
|
|
61
|
-
...options,
|
|
62
|
-
});
|
|
53
|
+
NDElement.prototype['onPrevent'+eventSourceName] = function(callback = null, options = null) {
|
|
54
|
+
_prevent(this.$element, eventName, callback, options);
|
|
63
55
|
return this;
|
|
64
56
|
};
|
|
65
57
|
});
|
|
66
58
|
|
|
67
|
-
/**
|
|
68
|
-
* Retrieves or creates an AbortController signal tied to this element's lifecycle.
|
|
69
|
-
* The signal is automatically aborted when the element is removed via .nd.remove().
|
|
70
|
-
* Used internally by all event listeners (onClick, onInput, etc.) to auto-cleanup on unmount.
|
|
71
|
-
*
|
|
72
|
-
* @internal
|
|
73
|
-
* @returns {AbortSignal} The signal for this element's AbortController
|
|
74
|
-
*/
|
|
75
|
-
NDElement.prototype.$getSignal = function() {
|
|
76
|
-
if(!this.$element.__$controller) {
|
|
77
|
-
this.$element.__$controller = new AbortController();
|
|
78
|
-
}
|
|
79
|
-
return this.$element.__$controller.signal;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
59
|
|
|
83
60
|
/**
|
|
84
61
|
* Adds a native event listener to the underlying HTMLElement.
|
|
@@ -91,7 +68,6 @@ NDElement.prototype.$getSignal = function() {
|
|
|
91
68
|
*/
|
|
92
69
|
NDElement.prototype.on = function(name, callback, options) {
|
|
93
70
|
this.$element.addEventListener(name.toLowerCase(), callback, {
|
|
94
|
-
signal: this.$getSignal(),
|
|
95
71
|
...options,
|
|
96
72
|
});
|
|
97
73
|
return this;
|
|
@@ -119,7 +95,6 @@ NDElement.prototype.off = function(name, callback) {
|
|
|
119
95
|
*/
|
|
120
96
|
NDElement.prototype.once = function(name, callback) {
|
|
121
97
|
this.$element.addEventListener(name.toLowerCase(), callback, {
|
|
122
|
-
signal: this.$getSignal(),
|
|
123
98
|
once: true,
|
|
124
99
|
});
|
|
125
100
|
return this;
|
|
@@ -151,8 +126,13 @@ const _prevent = function(element, eventName, callback, options) {
|
|
|
151
126
|
event.preventDefault();
|
|
152
127
|
callback && callback.call(element, event);
|
|
153
128
|
};
|
|
129
|
+
|
|
130
|
+
const inlineHandler = 'on' + eventName;
|
|
131
|
+
if(!element[inlineHandler] && !options) {
|
|
132
|
+
element[inlineHandler] = handler;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
154
135
|
element.addEventListener(eventName, handler, options);
|
|
155
|
-
return this;
|
|
156
136
|
};
|
|
157
137
|
|
|
158
138
|
const _stop = function(element, eventName, callback, options) {
|
|
@@ -160,6 +140,13 @@ const _stop = function(element, eventName, callback, options) {
|
|
|
160
140
|
event.stopPropagation();
|
|
161
141
|
callback && callback.call(element, event);
|
|
162
142
|
};
|
|
143
|
+
|
|
144
|
+
const inlineHandler = 'on' + eventName;
|
|
145
|
+
if(!element[inlineHandler] && !options) {
|
|
146
|
+
element[inlineHandler] = handler;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
163
150
|
element.addEventListener(eventName, handler, options);
|
|
164
151
|
return this;
|
|
165
152
|
};
|
|
@@ -170,6 +157,11 @@ const _preventStop = function(element, eventName, callback, options) {
|
|
|
170
157
|
event.preventDefault();
|
|
171
158
|
callback && callback.call(element, event);
|
|
172
159
|
};
|
|
160
|
+
const inlineHandler = 'on' + eventName;
|
|
161
|
+
if(!element[inlineHandler] && !options) {
|
|
162
|
+
element[inlineHandler] = handler;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
173
165
|
element.addEventListener(eventName, handler, options);
|
|
174
166
|
return this;
|
|
175
167
|
};
|
|
@@ -3,19 +3,19 @@ export const BOOLEAN_ATTRIBUTES = new Set([
|
|
|
3
3
|
'checked',
|
|
4
4
|
'selected',
|
|
5
5
|
'disabled',
|
|
6
|
-
'readonly',
|
|
6
|
+
'readonly', 'readOnly',
|
|
7
7
|
'required',
|
|
8
|
-
'autofocus',
|
|
8
|
+
'autofocus', 'autoFocus',
|
|
9
9
|
'multiple',
|
|
10
|
-
'autocomplete',
|
|
10
|
+
'autocomplete', 'autoComplete',
|
|
11
11
|
'hidden',
|
|
12
|
-
'contenteditable',
|
|
13
|
-
'spellcheck',
|
|
12
|
+
'contenteditable', 'contentEditable',
|
|
13
|
+
'spellcheck', 'spellCheck',
|
|
14
14
|
'translate',
|
|
15
15
|
'draggable',
|
|
16
16
|
'async',
|
|
17
17
|
'defer',
|
|
18
|
-
'autoplay',
|
|
18
|
+
'autoplay', 'autoPlay',
|
|
19
19
|
'controls',
|
|
20
20
|
'loop',
|
|
21
21
|
'muted',
|
|
@@ -23,13 +23,13 @@ export const BOOLEAN_ATTRIBUTES = new Set([
|
|
|
23
23
|
'reversed',
|
|
24
24
|
'open',
|
|
25
25
|
'default',
|
|
26
|
-
'formnovalidate',
|
|
27
|
-
'novalidate',
|
|
26
|
+
'formnovalidate', 'formNoValidate',
|
|
27
|
+
'novalidate', 'noValidate',
|
|
28
28
|
'scoped',
|
|
29
|
-
'itemscope',
|
|
30
|
-
'allowfullscreen',
|
|
31
|
-
'allowpaymentrequest',
|
|
32
|
-
'playsinline',
|
|
29
|
+
'itemscope', 'itemScope',
|
|
30
|
+
'allowfullscreen', 'allowFullscreen',
|
|
31
|
+
'allowpaymentrequest', 'allowPaymentRequest',
|
|
32
|
+
'playsinline', 'playsInline',
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
35
|
export const BOOL_ATTRIBUTES_NAME = {
|
|
@@ -1,103 +1,174 @@
|
|
|
1
1
|
import ObservableItem from '../../data/ObservableItem';
|
|
2
|
-
import {NDElement} from '../NDElement';
|
|
2
|
+
import { NDElement } from '../NDElement';
|
|
3
3
|
import TemplateBinding from '../TemplateBinding';
|
|
4
|
-
import {ElementCreator} from '../ElementCreator';
|
|
4
|
+
import { ElementCreator } from '../ElementCreator';
|
|
5
5
|
import PluginsManager from '../../utils/plugins-manager';
|
|
6
6
|
import ObservableChecker from '../../data/ObservableChecker';
|
|
7
|
+
import MemoryManager from '../../data/MemoryManager';
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
const OBS_PATTERN = /({{obs:\d+}})/;
|
|
12
|
+
const OBS_ID_PATTERN = /\d+/;
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
|
-
*
|
|
16
|
+
* Parses a string containing Observable placeholders and returns an array
|
|
17
|
+
* of strings and Observable instances.
|
|
13
18
|
*
|
|
14
|
-
*
|
|
19
|
+
* Placeholders are generated automatically when an ObservableItem is interpolated
|
|
20
|
+
* in a template string via its toString() method.
|
|
21
|
+
*
|
|
22
|
+
* @returns {Array<string|ObservableItem>} Mixed array of static strings and resolved Observables
|
|
23
|
+
* @example
|
|
24
|
+
* const name = Observable('John');
|
|
25
|
+
* const count = Observable(5);
|
|
26
|
+
*
|
|
27
|
+
* `Hello ${name}, count: ${count}`.toNdChildren()
|
|
28
|
+
* // → ['Hello ', ObservableItem(John), ', count: ', ObservableItem(5)]
|
|
15
29
|
*/
|
|
16
|
-
String.prototype
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
Object.defineProperty(String.prototype, 'toNdChildren', {
|
|
31
|
+
value: function() {
|
|
32
|
+
return this.split(OBS_PATTERN).filter(Boolean).map((item) => {
|
|
33
|
+
if(OBS_PATTERN.test(item)) {
|
|
34
|
+
const id = item.match(OBS_ID_PATTERN)[0];
|
|
35
|
+
return MemoryManager.getObservableById(Number(id));
|
|
36
|
+
}
|
|
37
|
+
return item;
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
writable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
enumerable: false,
|
|
43
|
+
});
|
|
19
44
|
|
|
20
45
|
/**
|
|
21
|
-
*
|
|
46
|
+
* Registers a non-enumerable toNdElement method on a prototype.
|
|
22
47
|
*
|
|
23
|
-
* @
|
|
48
|
+
* @param {object} target - The prototype to extend
|
|
49
|
+
* @param {Function} fn - The toNdElement implementation
|
|
24
50
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
const defineToNdElement = (target, fn) => {
|
|
52
|
+
Object.defineProperty(target, 'toNdElement', {
|
|
53
|
+
value: fn,
|
|
54
|
+
writable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
enumerable: false,
|
|
57
|
+
});
|
|
27
58
|
};
|
|
28
59
|
|
|
29
60
|
/**
|
|
30
|
-
*
|
|
61
|
+
* Marks a prototype as a valid NativeDocument child node.
|
|
31
62
|
*
|
|
32
|
-
* @
|
|
63
|
+
* @param {object} target - The prototype to mark
|
|
33
64
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
65
|
+
const defineValidNdChild = (target) => {
|
|
66
|
+
Object.defineProperty(target, '__$isValidNdChild', {
|
|
67
|
+
value: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
enumerable: false,
|
|
71
|
+
});
|
|
36
72
|
};
|
|
37
73
|
|
|
38
74
|
/**
|
|
39
|
-
*
|
|
75
|
+
* Marks a prototype as a native DOM node.
|
|
76
|
+
* Faster alternative to instanceof Node for hot paths.
|
|
40
77
|
*
|
|
41
|
-
* @
|
|
78
|
+
* @param {object} target - The prototype to mark
|
|
42
79
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
80
|
+
const defineNativeNode = (target) => {
|
|
81
|
+
Object.defineProperty(target, '__$isNativeNode', {
|
|
82
|
+
value: true,
|
|
83
|
+
writable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
enumerable: false,
|
|
86
|
+
});
|
|
45
87
|
};
|
|
46
88
|
|
|
89
|
+
NDElement.$getChild = ElementCreator.getChild;
|
|
90
|
+
|
|
91
|
+
// -- __$isNativeNode ----------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
defineNativeNode(Element.prototype);
|
|
94
|
+
defineNativeNode(Text.prototype);
|
|
95
|
+
defineNativeNode(Comment.prototype);
|
|
96
|
+
defineNativeNode(Document.prototype);
|
|
97
|
+
defineNativeNode(DocumentFragment.prototype);
|
|
98
|
+
|
|
99
|
+
// -- __$isValidNdChild --------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
defineValidNdChild(String.prototype);
|
|
102
|
+
defineValidNdChild(Number.prototype);
|
|
103
|
+
defineValidNdChild(Boolean.prototype);
|
|
104
|
+
defineValidNdChild(Array.prototype);
|
|
105
|
+
defineValidNdChild(Element.prototype);
|
|
106
|
+
defineValidNdChild(Text.prototype);
|
|
107
|
+
defineValidNdChild(Comment.prototype);
|
|
108
|
+
defineValidNdChild(DocumentFragment.prototype);
|
|
109
|
+
defineValidNdChild(NDElement.prototype);
|
|
110
|
+
defineValidNdChild(ObservableItem.prototype);
|
|
111
|
+
defineValidNdChild(ObservableChecker.prototype);
|
|
112
|
+
defineValidNdChild(TemplateBinding.prototype);
|
|
113
|
+
defineValidNdChild(Function.prototype);
|
|
114
|
+
|
|
115
|
+
// -- toNdElement --------------------------------------------------------------
|
|
116
|
+
|
|
47
117
|
/**
|
|
48
|
-
*
|
|
118
|
+
* Converts a string to a static text node.
|
|
49
119
|
*
|
|
50
|
-
* @returns {
|
|
120
|
+
* @returns {Text}
|
|
51
121
|
*/
|
|
52
|
-
|
|
53
|
-
return this;
|
|
54
|
-
};
|
|
122
|
+
defineToNdElement(String.prototype, function () {
|
|
123
|
+
return ElementCreator.createStaticTextNodeWithoutParent(this);
|
|
124
|
+
});
|
|
55
125
|
|
|
56
126
|
/**
|
|
57
|
-
*
|
|
127
|
+
* Converts a number to a static text node.
|
|
58
128
|
*
|
|
59
|
-
* @returns {
|
|
129
|
+
* @returns {Text}
|
|
60
130
|
*/
|
|
61
|
-
|
|
62
|
-
return this;
|
|
63
|
-
};
|
|
131
|
+
defineToNdElement(Number.prototype, function () {
|
|
132
|
+
return ElementCreator.createStaticTextNodeWithoutParent(this.toString());
|
|
133
|
+
});
|
|
64
134
|
|
|
65
135
|
/**
|
|
66
|
-
* Returns the
|
|
136
|
+
* Returns the DOM node itself (identity).
|
|
67
137
|
*
|
|
68
|
-
* @returns {DocumentFragment}
|
|
138
|
+
* @returns {Element|Text|Comment|Document|DocumentFragment}
|
|
69
139
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
140
|
+
defineToNdElement(Element.prototype, function () { return this; });
|
|
141
|
+
defineToNdElement(Text.prototype, function () { return this; });
|
|
142
|
+
defineToNdElement(Comment.prototype, function () { return this; });
|
|
143
|
+
defineToNdElement(Document.prototype, function () { return this; });
|
|
144
|
+
defineToNdElement(DocumentFragment.prototype, function () { return this; });
|
|
73
145
|
|
|
74
146
|
/**
|
|
75
|
-
* Converts
|
|
147
|
+
* Converts an ObservableItem to a reactive text node that updates when the value changes.
|
|
76
148
|
*
|
|
77
|
-
* @returns {Text}
|
|
149
|
+
* @returns {Text}
|
|
78
150
|
*/
|
|
79
|
-
ObservableItem.prototype
|
|
80
|
-
return ElementCreator.
|
|
81
|
-
};
|
|
151
|
+
defineToNdElement(ObservableItem.prototype, function () {
|
|
152
|
+
return ElementCreator.createObservableNodeWithoutParent(this);
|
|
153
|
+
});
|
|
82
154
|
|
|
83
155
|
ObservableChecker.prototype.toNdElement = ObservableItem.prototype.toNdElement;
|
|
84
156
|
|
|
85
157
|
/**
|
|
86
|
-
*
|
|
158
|
+
* Returns the underlying HTMLElement of this NDElement.
|
|
87
159
|
*
|
|
88
|
-
* @returns {HTMLElement|DocumentFragment}
|
|
160
|
+
* @returns {HTMLElement|DocumentFragment}
|
|
89
161
|
*/
|
|
90
|
-
NDElement.prototype
|
|
162
|
+
defineToNdElement(NDElement.prototype, function () {
|
|
91
163
|
return this.$element;
|
|
92
|
-
};
|
|
164
|
+
});
|
|
93
165
|
|
|
94
166
|
/**
|
|
95
|
-
* Converts the array to a DocumentFragment containing all elements.
|
|
96
|
-
* Each item is processed through ElementCreator.getChild().
|
|
167
|
+
* Converts the array to a DocumentFragment containing all child elements.
|
|
97
168
|
*
|
|
98
|
-
* @returns {DocumentFragment}
|
|
169
|
+
* @returns {DocumentFragment}
|
|
99
170
|
*/
|
|
100
|
-
Array.prototype
|
|
171
|
+
defineToNdElement(Array.prototype, function () {
|
|
101
172
|
const fragment = document.createDocumentFragment();
|
|
102
173
|
for(let i = 0, length = this.length; i < length; i++) {
|
|
103
174
|
const child = ElementCreator.getChild(this[i]);
|
|
@@ -105,27 +176,26 @@ Array.prototype.toNdElement = function () {
|
|
|
105
176
|
fragment.appendChild(child);
|
|
106
177
|
}
|
|
107
178
|
return fragment;
|
|
108
|
-
};
|
|
179
|
+
});
|
|
109
180
|
|
|
110
181
|
/**
|
|
111
182
|
* Calls the function and converts its return value to a DOM node.
|
|
112
|
-
* Used internally by ElementCreator to process function-based children.
|
|
113
183
|
*
|
|
114
|
-
* @returns {Node}
|
|
184
|
+
* @returns {Node}
|
|
115
185
|
*/
|
|
116
|
-
Function.prototype
|
|
186
|
+
defineToNdElement(Function.prototype, function () {
|
|
117
187
|
const child = this;
|
|
118
188
|
if(process.env.NODE_ENV === 'development') {
|
|
119
189
|
PluginsManager.emit('BeforeProcessComponent', child);
|
|
120
190
|
}
|
|
121
191
|
return ElementCreator.getChild(child());
|
|
122
|
-
};
|
|
192
|
+
});
|
|
123
193
|
|
|
124
194
|
/**
|
|
125
|
-
* Converts the TemplateBinding to a hydratable DOM node
|
|
195
|
+
* Converts the TemplateBinding to a hydratable DOM node.
|
|
126
196
|
*
|
|
127
|
-
* @returns {Node}
|
|
197
|
+
* @returns {Node}
|
|
128
198
|
*/
|
|
129
|
-
TemplateBinding.prototype
|
|
199
|
+
defineToNdElement(TemplateBinding.prototype, function () {
|
|
130
200
|
return ElementCreator.createHydratableNode(null, this);
|
|
131
|
-
};
|
|
201
|
+
});
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import {Div, Span, ShowIf} from '../../../core/elements';
|
|
2
2
|
|
|
3
|
+
// [a11y] ID counter for aria-controls
|
|
4
|
+
let _accordionIdCounter = 0;
|
|
5
|
+
|
|
3
6
|
export default function AccordionItemRender($desc, instance) {
|
|
4
7
|
const props = instance.getEditableProps();
|
|
5
8
|
|
|
6
9
|
props.class.add('accordion-item');
|
|
7
10
|
props.class.add({'is-disabled': $desc.disabled});
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
const
|
|
12
|
+
// [a11y] Generate unique IDs for aria-controls / aria-labelledby
|
|
13
|
+
const accordionId = ++_accordionIdCounter;
|
|
14
|
+
const headerId = `accordion-header-${accordionId}`;
|
|
15
|
+
const panelId = `accordion-panel-${accordionId}`;
|
|
16
|
+
|
|
17
|
+
const header = buildHeader($desc, instance, headerId, panelId);
|
|
18
|
+
const content = buildContent($desc, instance, headerId, panelId);
|
|
11
19
|
|
|
12
20
|
return Div(instance.resolveProps(), [header, content]);
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
const buildHeader = ($desc, instance) => {
|
|
23
|
+
const buildHeader = ($desc, instance, headerId, panelId) => {
|
|
16
24
|
if($desc.renderHeader) {
|
|
17
25
|
return $desc.renderHeader($desc, instance);
|
|
18
26
|
}
|
|
@@ -27,7 +35,13 @@ const buildHeader = ($desc, instance) => {
|
|
|
27
35
|
buildIndicator($desc),
|
|
28
36
|
];
|
|
29
37
|
|
|
30
|
-
const header = Div({
|
|
38
|
+
const header = Div({
|
|
39
|
+
class: 'accordion-header',
|
|
40
|
+
id: headerId,
|
|
41
|
+
// [a11y] aria-expanded reactive, aria-controls points to panel
|
|
42
|
+
'aria-expanded': $desc.expanded.transform((v) => String(v)),
|
|
43
|
+
'aria-controls': panelId,
|
|
44
|
+
}, content);
|
|
31
45
|
|
|
32
46
|
if(!$desc.disabled?.val()) {
|
|
33
47
|
header.nd.onClick(() => {
|
|
@@ -50,14 +64,22 @@ const buildIndicator = ($desc) => {
|
|
|
50
64
|
}, '▾');
|
|
51
65
|
};
|
|
52
66
|
|
|
53
|
-
const buildContent = ($desc, instance) => {
|
|
67
|
+
const buildContent = ($desc, instance, headerId, panelId) => {
|
|
68
|
+
// [a11y] role=region + aria-labelledby on panel
|
|
69
|
+
const panelProps = {
|
|
70
|
+
class: 'accordion-content',
|
|
71
|
+
id: panelId,
|
|
72
|
+
role: 'region',
|
|
73
|
+
'aria-labelledby': headerId,
|
|
74
|
+
};
|
|
75
|
+
|
|
54
76
|
if($desc.renderContent) {
|
|
55
77
|
return ShowIf($desc.expanded, () =>
|
|
56
|
-
Div(
|
|
78
|
+
Div(panelProps, $desc.renderContent($desc, instance)),
|
|
57
79
|
);
|
|
58
80
|
}
|
|
59
81
|
|
|
60
82
|
return ShowIf($desc.expanded, () =>
|
|
61
|
-
Div(
|
|
83
|
+
Div(panelProps, $desc.content),
|
|
62
84
|
);
|
|
63
85
|
};
|
|
@@ -10,6 +10,12 @@ export default function AlertRender($desc, instance) {
|
|
|
10
10
|
props.class.add('is-' + ($desc.variant || 'info'));
|
|
11
11
|
props.class.add('is-' + ($desc.appearance || 'bordered'));
|
|
12
12
|
|
|
13
|
+
// [a11y] aria-live: assertive for error/warning, polite for others
|
|
14
|
+
const ariaLive = ($desc.variant === 'error' || $desc.variant === 'danger' || $desc.variant === 'warning')
|
|
15
|
+
? 'assertive'
|
|
16
|
+
: 'polite';
|
|
17
|
+
props['aria-live'] = ariaLive;
|
|
18
|
+
|
|
13
19
|
const content = [];
|
|
14
20
|
|
|
15
21
|
if($desc.showIcon && $desc.icon) {
|
|
@@ -6,6 +6,9 @@ export default function BreadcrumbRender($desc, instance) {
|
|
|
6
6
|
const props = instance.getEditableProps();
|
|
7
7
|
props.class.add('breadcrumb');
|
|
8
8
|
|
|
9
|
+
// [a11y] aria-label on nav
|
|
10
|
+
props['aria-label'] = (typeof $desc.label === 'string') ? $desc.label : 'Breadcrumb';
|
|
11
|
+
|
|
9
12
|
return Nav(instance.resolveProps(),
|
|
10
13
|
OrderedList({ class: 'breadcrumb-list' },
|
|
11
14
|
ForEachArray($desc.items, (item) => buildItem(item, $desc, instance)),
|
|
@@ -40,5 +43,10 @@ const buildItem = (item, $desc, instance) => {
|
|
|
40
43
|
|
|
41
44
|
content.push(buildSeparator($desc));
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
// [a11y] aria-current on last item (no href = current page)
|
|
47
|
+
const isCurrent = !item.href;
|
|
48
|
+
return ListItem({
|
|
49
|
+
class: 'breadcrumb-item',
|
|
50
|
+
...( isCurrent ? { 'aria-current': 'page' } : {} ),
|
|
51
|
+
}, content);
|
|
44
52
|
};
|
|
@@ -36,6 +36,9 @@ export default function ButtonRender($desc, instance) {
|
|
|
36
36
|
...instance.resolveProps(),
|
|
37
37
|
type: $desc.type || 'button',
|
|
38
38
|
disabled: $desc.disabled,
|
|
39
|
+
// [a11y] aria-busy and aria-disabled
|
|
40
|
+
...($desc.loading ? { 'aria-busy': $desc.loading } : {}),
|
|
41
|
+
...($desc.disabled ? { 'aria-disabled': $desc.disabled } : {}),
|
|
39
42
|
}, content);
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -3,6 +3,7 @@ import {createPortal} from '../../../core/elements/anchor/anchor';
|
|
|
3
3
|
import {computePosition, flip, shift} from '@floating-ui/dom';
|
|
4
4
|
|
|
5
5
|
import './contextmenu.css';
|
|
6
|
+
import {ariaTrigger} from '../../utils/aria';
|
|
6
7
|
|
|
7
8
|
export default function ContextMenuRender($desc, instance) {
|
|
8
9
|
const $positionX = instance.$description.positionX, $positionY = instance.$description.positionY;
|
|
@@ -19,6 +20,7 @@ export default function ContextMenuRender($desc, instance) {
|
|
|
19
20
|
createPortal(container, {name: 'context-menu'});
|
|
20
21
|
|
|
21
22
|
if($desc.trigger && instance) {
|
|
23
|
+
ariaTrigger($desc.trigger, $desc.isOpen, 'menu');
|
|
22
24
|
contextMenuHandler($desc.trigger, instance);
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -4,9 +4,12 @@ import {createFilter} from '../../../core/utils/filters';
|
|
|
4
4
|
import {normalizeDropdownItem} from '../../../components/dropdown/helpers';
|
|
5
5
|
|
|
6
6
|
import './dropdown.css';
|
|
7
|
+
import {ariaTrigger} from '../../utils/aria';
|
|
7
8
|
|
|
8
9
|
export default function DropdownRender($desc, instance) {
|
|
9
10
|
$desc.content = buildDropdownContent($desc, instance);
|
|
11
|
+
ariaTrigger($desc.trigger, $desc.isOpen, 'listbox');
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
return PopoverRender($desc, instance, 'dropdown');
|
|
12
15
|
}
|
|
@@ -5,7 +5,12 @@ export default function DropdownItemRender($desc, instance) {
|
|
|
5
5
|
const props = instance.getEditableProps();
|
|
6
6
|
|
|
7
7
|
props.class.add('dropdown-item');
|
|
8
|
-
|
|
8
|
+
props.class.add({ 'is-disabled': $desc.disabled, 'is-selected': $desc.selected });
|
|
9
|
+
|
|
10
|
+
if($desc.selected != null) {
|
|
11
|
+
props['aria-selected'] = $desc.selected;
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
const content = [];
|
|
11
16
|
if($desc.renderContent) {
|
|
@@ -7,6 +7,10 @@ export default function FieldCollectionRender($desc, instance) {
|
|
|
7
7
|
const props = instance.getEditableProps();
|
|
8
8
|
|
|
9
9
|
props.class.add('field-collection');
|
|
10
|
+
// [a11y] aria-label if provided
|
|
11
|
+
if($desc.label) {
|
|
12
|
+
props['aria-label'] = $desc.label;
|
|
13
|
+
}
|
|
10
14
|
|
|
11
15
|
const $items = $desc.value;
|
|
12
16
|
const $itemsMap = new WeakMap();
|