native-document 1.0.168 → 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 +99 -4
- package/components.js +3 -1
- package/dist/native-document.components.min.js +197 -171
- package/dist/native-document.dev.js +92 -75
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/components/icons.md +321 -0
- 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/FileField.js +3 -3
- package/src/components/form/field/types/StringField.js +1 -1
- package/src/components/icon/Icon.js +108 -0
- package/src/components/icon/icon-getters.js +142 -0
- package/src/components/icon/icons.js +171 -0
- package/src/components/icon/index.js +17 -0
- package/src/components/icon/types/Icon.d.ts +191 -0
- package/src/components/index.d.ts +6 -1
- package/src/components/list/ListItem.js +1 -0
- package/src/components/list/types/List.d.ts +45 -32
- package/src/components/list/types/ListDivider.ts +16 -0
- package/src/components/list/types/ListGroup.d.ts +51 -29
- package/src/components/list/types/ListItem.d.ts +20 -21
- 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 +35 -2
- 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/elements/form.js +1 -1
- package/src/core/elements/img.js +1 -1
- package/src/core/elements/medias.js +2 -2
- package/src/core/elements/meta-data.js +1 -1
- package/src/core/wrappers/AttributesWrapper.js +44 -29
- package/src/core/wrappers/ElementCreator.js +38 -22
- package/src/core/wrappers/HtmlElementWrapper.js +33 -9
- package/src/core/wrappers/NDElement.js +35 -9
- package/src/core/wrappers/NdPrototype.js +31 -39
- package/src/core/wrappers/constants.js +12 -12
- package/src/core/wrappers/prototypes/attributes-extensions.js +24 -24
- package/src/core/wrappers/prototypes/nd-element-extensions.js +128 -65
- 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/material/MaterialIconRender.js +71 -0
- package/src/ui/components/icon/material/material.css +15 -0
- package/src/ui/components/icon/material/material.map.js +170 -0
- package/src/ui/components/icon/phosphor/PhosphorIconRender.js +71 -0
- package/src/ui/components/icon/phosphor/phosphor.css +17 -0
- package/src/ui/components/icon/phosphor/phosphor.map.js +165 -0
- package/src/ui/components/icon/tabler/TablerIconRender.js +58 -0
- package/src/ui/components/icon/tabler/tabler.css +14 -0
- package/src/ui/components/icon/tabler/tabler.map.js +165 -0
- 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/index.js +5 -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
|
@@ -1,110 +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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if(!this.$attachements.contains(this.$element)) {
|
|
94
|
-
this.$attachements.append(this.$element);
|
|
95
|
-
}
|
|
96
|
-
return this.$attachements;
|
|
97
|
-
}
|
|
98
|
-
return element;
|
|
99
|
-
};
|
|
162
|
+
defineToNdElement(NDElement.prototype, function () {
|
|
163
|
+
return this.$element;
|
|
164
|
+
});
|
|
100
165
|
|
|
101
166
|
/**
|
|
102
|
-
* Converts the array to a DocumentFragment containing all elements.
|
|
103
|
-
* Each item is processed through ElementCreator.getChild().
|
|
167
|
+
* Converts the array to a DocumentFragment containing all child elements.
|
|
104
168
|
*
|
|
105
|
-
* @returns {DocumentFragment}
|
|
169
|
+
* @returns {DocumentFragment}
|
|
106
170
|
*/
|
|
107
|
-
Array.prototype
|
|
171
|
+
defineToNdElement(Array.prototype, function () {
|
|
108
172
|
const fragment = document.createDocumentFragment();
|
|
109
173
|
for(let i = 0, length = this.length; i < length; i++) {
|
|
110
174
|
const child = ElementCreator.getChild(this[i]);
|
|
@@ -112,27 +176,26 @@ Array.prototype.toNdElement = function () {
|
|
|
112
176
|
fragment.appendChild(child);
|
|
113
177
|
}
|
|
114
178
|
return fragment;
|
|
115
|
-
};
|
|
179
|
+
});
|
|
116
180
|
|
|
117
181
|
/**
|
|
118
182
|
* Calls the function and converts its return value to a DOM node.
|
|
119
|
-
* Used internally by ElementCreator to process function-based children.
|
|
120
183
|
*
|
|
121
|
-
* @returns {Node}
|
|
184
|
+
* @returns {Node}
|
|
122
185
|
*/
|
|
123
|
-
Function.prototype
|
|
186
|
+
defineToNdElement(Function.prototype, function () {
|
|
124
187
|
const child = this;
|
|
125
188
|
if(process.env.NODE_ENV === 'development') {
|
|
126
189
|
PluginsManager.emit('BeforeProcessComponent', child);
|
|
127
190
|
}
|
|
128
191
|
return ElementCreator.getChild(child());
|
|
129
|
-
};
|
|
192
|
+
});
|
|
130
193
|
|
|
131
194
|
/**
|
|
132
|
-
* Converts the TemplateBinding to a hydratable DOM node
|
|
195
|
+
* Converts the TemplateBinding to a hydratable DOM node.
|
|
133
196
|
*
|
|
134
|
-
* @returns {Node}
|
|
197
|
+
* @returns {Node}
|
|
135
198
|
*/
|
|
136
|
-
TemplateBinding.prototype
|
|
199
|
+
defineToNdElement(TemplateBinding.prototype, function () {
|
|
137
200
|
return ElementCreator.createHydratableNode(null, this);
|
|
138
|
-
};
|
|
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();
|
|
@@ -25,6 +25,7 @@ export default function CheckboxFieldRender($desc, instance) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const buildCheckbox = ($desc, instance) => {
|
|
28
|
+
const isRequired = $desc.rules?.some(rule => rule.fn?.name === 'required');
|
|
28
29
|
const input = Input({
|
|
29
30
|
class: 'field-checkbox',
|
|
30
31
|
type: 'checkbox',
|
|
@@ -32,6 +33,9 @@ const buildCheckbox = ($desc, instance) => {
|
|
|
32
33
|
id: $desc.id || $desc.name,
|
|
33
34
|
disabled: $desc.disabled,
|
|
34
35
|
checked: $desc.checked,
|
|
36
|
+
// [a11y] aria-required, aria-invalid
|
|
37
|
+
...(isRequired ? { 'aria-required': 'true' } : {}),
|
|
38
|
+
...($desc.hasErrors ? { 'aria-invalid': $desc.hasErrors } : {}),
|
|
35
39
|
...($desc.elementsProps.input || {}),
|
|
36
40
|
});
|
|
37
41
|
|
|
@@ -85,8 +85,12 @@ const buildOptions = ($desc, instance) => {
|
|
|
85
85
|
]);
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
+
const isRequired = $desc.rules?.some(rule => rule.fn?.name === 'required');
|
|
88
89
|
return Div({
|
|
89
90
|
class: 'field-checkbox-group is-' + ($desc.layout || 'vertical'),
|
|
91
|
+
// [a11y] role=group, aria-required
|
|
92
|
+
role: 'group',
|
|
93
|
+
...( isRequired ? { 'aria-required': 'true' } : {} ),
|
|
90
94
|
...($desc.elementsProps.wrapper || {}),
|
|
91
95
|
}, ForEachArray($desc.options, buildItem));
|
|
92
96
|
};
|
|
@@ -70,8 +70,12 @@ const buildOptions = ($desc, instance) => {
|
|
|
70
70
|
]);
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
const isRequired = $desc.rules?.some(rule => rule.fn?.name === 'required');
|
|
73
74
|
return Div({
|
|
74
75
|
class: 'field-radio-group is-' + ($desc.layout || 'vertical'),
|
|
76
|
+
// [a11y] role=radiogroup, aria-required
|
|
77
|
+
role: 'radiogroup',
|
|
78
|
+
...( isRequired ? { 'aria-required': 'true' } : {} ),
|
|
75
79
|
...($desc.elementsProps.wrapper || {}),
|
|
76
80
|
}, ForEachArray($options, buildOption));
|
|
77
81
|
};
|
|
@@ -66,6 +66,10 @@ const buildInput = ($desc, instance) => {
|
|
|
66
66
|
step: $desc.step ?? 1,
|
|
67
67
|
disabled: $desc.disabled,
|
|
68
68
|
value: $desc.value,
|
|
69
|
+
// [a11y] aria-valuemin, aria-valuemax, aria-valuenow
|
|
70
|
+
'aria-valuemin': String($desc.min ?? 0),
|
|
71
|
+
'aria-valuemax': String($desc.max ?? 100),
|
|
72
|
+
...($desc.value?.__$Observable ? { 'aria-valuenow': $desc.value.transform((v) => String(v)) } : { 'aria-valuenow': String($desc.value ?? 0) }),
|
|
69
73
|
...($desc.elementsProps.input || {}),
|
|
70
74
|
});
|
|
71
75
|
|
|
@@ -61,8 +61,13 @@ const buildSelectWrapper = ($desc, instance) => {
|
|
|
61
61
|
}
|
|
62
62
|
triggerOptions.push(Span({class: 'select-field-chevron'}, '▾'));
|
|
63
63
|
|
|
64
|
+
const isRequired = $desc.rules?.some(rule => rule.fn?.name === 'required');
|
|
64
65
|
const trigger = Div({
|
|
65
66
|
class: {'field-input select-field-trigger': true, 'has-tags': isTags},
|
|
67
|
+
// [a11y] aria-haspopup, aria-expanded, aria-required
|
|
68
|
+
'aria-haspopup': 'listbox',
|
|
69
|
+
'aria-expanded': 'false',
|
|
70
|
+
...( isRequired ? { 'aria-required': 'true' } : {} ),
|
|
66
71
|
...($desc.elementsProps.input || {}),
|
|
67
72
|
}, [
|
|
68
73
|
valueDisplay,
|
|
@@ -7,6 +7,9 @@ export default function SliderRender($desc, instance) {
|
|
|
7
7
|
const props = instance.getEditableProps();
|
|
8
8
|
|
|
9
9
|
props.class.add('slider');
|
|
10
|
+
// [a11y] aria-valuemin, aria-valuemax
|
|
11
|
+
props['aria-valuemin'] = String($desc.min ?? 0);
|
|
12
|
+
props['aria-valuemax'] = String($desc.max ?? 100);
|
|
10
13
|
props.class.add({
|
|
11
14
|
'is-vertical': $desc.vertical,
|
|
12
15
|
'is-reverse': $desc.reverse,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import './material.css';
|
|
2
|
+
import MAP from './material.map.js';
|
|
3
|
+
import { Span } from '../../../../core/elements';
|
|
4
|
+
|
|
5
|
+
const SIZES = {
|
|
6
|
+
small: 16,
|
|
7
|
+
medium: 24,
|
|
8
|
+
large: 32,
|
|
9
|
+
extraLarge: 48,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Variant -> CSS class
|
|
13
|
+
// Material Icons uses the class as the font-family selector
|
|
14
|
+
const VARIANT_CLASS = {
|
|
15
|
+
fill: 'material-icons',
|
|
16
|
+
regular: 'material-icons',
|
|
17
|
+
outline: 'material-icons-outlined',
|
|
18
|
+
round: 'material-icons-round',
|
|
19
|
+
sharp: 'material-icons-sharp',
|
|
20
|
+
twoTone: 'material-icons-two-tone',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Renders a Google Material Icon as a <span> element using CSS webfont ligatures.
|
|
25
|
+
* The icon name is the text content of the element — Material Icons uses ligatures.
|
|
26
|
+
*
|
|
27
|
+
* Variants: fill/regular (default), outline, round, sharp, twoTone
|
|
28
|
+
* Each variant requires its own CSS - see material.css.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} $desc
|
|
31
|
+
* @param {string} $desc.name
|
|
32
|
+
* @param {'fill'|'regular'|'outline'|'round'|'sharp'|'twoTone'|Observable<string>|null} $desc.variant
|
|
33
|
+
* @param {'small'|'medium'|'large'|'extraLarge'|number|Observable<string|number>|null} $desc.size
|
|
34
|
+
* @param {string|Observable<string>|null} $desc.color
|
|
35
|
+
* @returns {HTMLElement}
|
|
36
|
+
*/
|
|
37
|
+
export default function MaterialIconRender($desc) {
|
|
38
|
+
const materialName = MAP[$desc.name] ?? $desc.name;
|
|
39
|
+
const style = {};
|
|
40
|
+
|
|
41
|
+
let className;
|
|
42
|
+
|
|
43
|
+
if($desc.variant?.__$Observable) {
|
|
44
|
+
className = $desc.variant.transform((variant) => {
|
|
45
|
+
return VARIANT_CLASS[variant] ?? VARIANT_CLASS.fill;
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
className = VARIANT_CLASS[$desc.variant] ?? VARIANT_CLASS.fill;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if($desc.size?.__$Observable) {
|
|
52
|
+
style.fontSize = $desc.size.transform((size) => {
|
|
53
|
+
const px = typeof size === 'number' ? size : (SIZES[size] ?? SIZES.medium);
|
|
54
|
+
return `${px}px`;
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
const px = typeof $desc.size === 'number' ? $desc.size : (SIZES[$desc.size] ?? SIZES.medium);
|
|
58
|
+
style.fontSize = `${px}px`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if($desc.color) {
|
|
62
|
+
style.color = $desc.color;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Material Icons uses ligatures — the icon name is the text content
|
|
66
|
+
return Span({
|
|
67
|
+
'aria-hidden': 'true',
|
|
68
|
+
class: className,
|
|
69
|
+
style,
|
|
70
|
+
}, materialName);
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Google Material Icons webfont
|
|
3
|
+
* Install : npm install material-icons
|
|
4
|
+
*
|
|
5
|
+
* Import only the variants you need.
|
|
6
|
+
*/
|
|
7
|
+
/*@import 'material-icons/iconfont/filled.css';*/
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* Uncomment the variants you need:
|
|
11
|
+
*/
|
|
12
|
+
/* @import 'material-icons/iconfont/outlined.css'; */
|
|
13
|
+
/* @import 'material-icons/iconfont/round.css'; */
|
|
14
|
+
/* @import 'material-icons/iconfont/sharp.css'; */
|
|
15
|
+
/* @import 'material-icons/iconfont/two-tone.css'; */
|