mtrl 0.2.7 → 0.2.9
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/index.ts +2 -0
- package/package.json +14 -3
- package/src/components/badge/api.ts +23 -14
- package/src/components/badge/badge.ts +2 -2
- package/src/components/badge/config.ts +10 -11
- package/src/components/badge/features.ts +15 -10
- package/src/components/badge/index.ts +27 -2
- package/src/components/badge/types.ts +28 -8
- package/src/components/bottom-app-bar/bottom-app-bar.ts +2 -44
- package/src/components/bottom-app-bar/config.ts +1 -45
- package/src/components/bottom-app-bar/index.ts +7 -1
- package/src/components/bottom-app-bar/types.ts +7 -1
- package/src/components/button/button.ts +0 -1
- package/src/components/button/config.ts +1 -2
- package/src/components/button/index.ts +10 -2
- package/src/components/button/types.ts +14 -2
- package/src/components/card/config.ts +17 -9
- package/src/components/card/content.ts +8 -10
- package/src/components/card/features.ts +4 -6
- package/src/components/card/index.ts +29 -2
- package/src/components/card/types.ts +6 -23
- package/src/components/checkbox/config.ts +3 -4
- package/src/components/checkbox/index.ts +1 -2
- package/src/components/checkbox/types.ts +12 -3
- package/src/components/chip/api.ts +170 -221
- package/src/components/chip/chip.ts +34 -302
- package/src/components/chip/config.ts +1 -2
- package/src/components/chip/index.ts +10 -2
- package/src/components/chip/types.ts +224 -35
- package/src/components/datepicker/api.ts +18 -25
- package/src/components/datepicker/config.ts +9 -12
- package/src/components/datepicker/datepicker.ts +7 -12
- package/src/components/datepicker/index.ts +10 -7
- package/src/components/datepicker/render.ts +16 -18
- package/src/components/datepicker/types.ts +164 -35
- package/src/components/datepicker/utils.ts +1 -2
- package/src/components/dialog/api.ts +7 -8
- package/src/components/dialog/config.ts +3 -4
- package/src/components/dialog/features.ts +56 -22
- package/src/components/dialog/index.ts +38 -8
- package/src/components/dialog/types.ts +33 -10
- package/src/components/divider/index.ts +5 -1
- package/src/components/extended-fab/config.ts +6 -2
- package/src/components/extended-fab/index.ts +7 -2
- package/src/components/extended-fab/types.ts +21 -4
- package/src/components/fab/config.ts +3 -4
- package/src/components/fab/fab.ts +1 -1
- package/src/components/fab/index.ts +7 -2
- package/src/components/fab/types.ts +21 -4
- package/src/components/list/config.ts +4 -5
- package/src/components/list/features.ts +6 -7
- package/src/components/list/index.ts +7 -9
- package/src/components/list/list-item.ts +12 -13
- package/src/components/list/types.ts +50 -5
- package/src/components/list/utils.ts +30 -3
- package/src/components/menu/features/items-manager.ts +9 -9
- package/src/components/menu/features/positioning.ts +7 -7
- package/src/components/menu/features/visibility.ts +7 -7
- package/src/components/menu/index.ts +7 -9
- package/src/components/menu/menu-item.ts +6 -6
- package/src/components/menu/menu.ts +22 -0
- package/src/components/menu/types.ts +29 -10
- package/src/components/menu/utils.ts +67 -0
- package/src/components/navigation/api.ts +131 -96
- package/src/components/navigation/config.ts +22 -10
- package/src/components/navigation/features/controller.ts +273 -0
- package/src/components/navigation/features/items.ts +160 -87
- package/src/components/navigation/index.ts +0 -6
- package/src/components/navigation/nav-item.ts +12 -24
- package/src/components/navigation/navigation.ts +21 -8
- package/src/components/navigation/system-types.ts +124 -0
- package/src/components/navigation/system.ts +776 -0
- package/src/components/navigation/types.ts +228 -203
- package/src/components/progress/api.ts +2 -3
- package/src/components/progress/config.ts +2 -3
- package/src/components/progress/index.ts +0 -1
- package/src/components/progress/progress.ts +1 -2
- package/src/components/progress/types.ts +186 -33
- package/src/components/radios/config.ts +1 -1
- package/src/components/radios/index.ts +0 -1
- package/src/components/radios/types.ts +0 -7
- package/src/components/search/config.ts +1 -2
- package/src/components/search/features/search.ts +14 -15
- package/src/components/search/features/states.ts +5 -1
- package/src/components/search/features/structure.ts +3 -4
- package/src/components/search/index.ts +0 -3
- package/src/components/search/types.ts +18 -6
- package/src/components/segmented-button/config.ts +20 -7
- package/src/components/segmented-button/segment.ts +6 -7
- package/src/components/segmented-button/segmented-button.ts +4 -5
- package/src/components/segmented-button/types.ts +37 -2
- package/src/components/slider/config.ts +20 -2
- package/src/components/slider/features/controller.ts +761 -0
- package/src/components/slider/features/handlers.ts +18 -15
- package/src/components/slider/features/index.ts +3 -2
- package/src/components/slider/features/range.ts +104 -0
- package/src/components/slider/slider.ts +34 -14
- package/src/components/slider/structure.ts +152 -0
- package/src/components/slider/types.ts +34 -8
- package/src/components/snackbar/config.ts +2 -3
- package/src/components/snackbar/constants.ts +0 -32
- package/src/components/snackbar/index.ts +0 -1
- package/src/components/snackbar/position.ts +9 -1
- package/src/components/snackbar/types.ts +122 -46
- package/src/components/switch/config.ts +2 -3
- package/src/components/switch/index.ts +0 -1
- package/src/components/switch/types.ts +3 -2
- package/src/components/tabs/config.ts +3 -4
- package/src/components/tabs/index.ts +0 -15
- package/src/components/tabs/tab-api.ts +12 -4
- package/src/components/tabs/tab.ts +18 -6
- package/src/components/tabs/types.ts +13 -3
- package/src/components/textfield/api.ts +53 -0
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/features.ts +322 -0
- package/src/components/textfield/index.ts +0 -1
- package/src/components/textfield/textfield.ts +8 -0
- package/src/components/textfield/types.ts +29 -6
- package/src/components/timepicker/api.ts +1 -1
- package/src/components/timepicker/clockdial.ts +2 -5
- package/src/components/timepicker/config.ts +102 -4
- package/src/components/timepicker/index.ts +1 -6
- package/src/components/timepicker/render.ts +1 -1
- package/src/components/timepicker/timepicker.ts +1 -1
- package/src/components/tooltip/api.ts +1 -1
- package/src/components/tooltip/config.ts +27 -6
- package/src/components/tooltip/index.ts +0 -1
- package/src/components/tooltip/types.ts +13 -3
- package/src/core/compose/features/textinput.ts +15 -2
- package/src/core/compose/features/textlabel.ts +0 -3
- package/src/core/composition/features/dom.ts +33 -0
- package/src/core/composition/features/icon.ts +131 -0
- package/src/core/composition/features/index.ts +11 -0
- package/src/core/composition/features/label.ts +156 -0
- package/src/core/composition/features/structure.ts +22 -0
- package/src/core/composition/index.ts +26 -0
- package/src/core/index.ts +1 -1
- package/src/core/structure.ts +288 -0
- package/src/index.ts +1 -0
- package/src/styles/components/_navigation-mobile.scss +244 -0
- package/src/styles/components/_navigation-system.scss +151 -0
- package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +1 -1
- package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +314 -72
- package/src/styles/main.scss +98 -49
- package/src/components/badge/constants.ts +0 -40
- package/src/components/button/constants.ts +0 -11
- package/src/components/card/constants.ts +0 -84
- package/src/components/datepicker/constants.ts +0 -98
- package/src/components/dialog/constants.ts +0 -32
- package/src/components/extended-fab/constants.ts +0 -36
- package/src/components/fab/constants.ts +0 -41
- package/src/components/menu/constants.ts +0 -154
- package/src/components/navigation/constants.ts +0 -200
- package/src/components/progress/constants.ts +0 -29
- package/src/components/search/constants.ts +0 -21
- package/src/components/segmented-button/constants.ts +0 -42
- package/src/components/slider/features/slider.ts +0 -318
- package/src/components/slider/features/structure.ts +0 -181
- package/src/components/slider/features/ui.ts +0 -388
- package/src/components/switch/constants.ts +0 -80
- package/src/components/tabs/constants.ts +0 -89
- package/src/components/textfield/constants.ts +0 -100
- package/src/components/timepicker/constants.ts +0 -138
- /package/src/{components/badge/_styles.scss → styles/components/_badge.scss} +0 -0
- /package/src/{components/bottom-app-bar/_styles.scss → styles/components/_bottom-app-bar.scss} +0 -0
- /package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -0
- /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
- /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
- /package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -0
- /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
- /package/src/{components/datepicker/_styles.scss → styles/components/_datepicker.scss} +0 -0
- /package/src/{components/dialog/_styles.scss → styles/components/_dialog.scss} +0 -0
- /package/src/{components/divider/_styles.scss → styles/components/_divider.scss} +0 -0
- /package/src/{components/extended-fab/_styles.scss → styles/components/_extended-fab.scss} +0 -0
- /package/src/{components/fab/_styles.scss → styles/components/_fab.scss} +0 -0
- /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
- /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
- /package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +0 -0
- /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
- /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
- /package/src/{components/search/_styles.scss → styles/components/_search.scss} +0 -0
- /package/src/{components/segmented-button/_styles.scss → styles/components/_segmented-button.scss} +0 -0
- /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
- /package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +0 -0
- /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
- /package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -0
- /package/src/{components/timepicker/_styles.scss → styles/components/_timepicker.scss} +0 -0
- /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
- /package/src/{components/top-app-bar/_styles.scss → styles/components/_top-app-bar.scss} +0 -0
- /package/src/styles/utilities/{_color.scss → _colors.scss} +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// src/core/structure.ts
|
|
2
|
+
import { createElement } from './dom/create';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Type definitions for structure creation
|
|
6
|
+
*/
|
|
7
|
+
export interface ComponentLike {
|
|
8
|
+
element: HTMLElement;
|
|
9
|
+
destroy?: () => void;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ElementDefinition {
|
|
14
|
+
name?: string;
|
|
15
|
+
creator?: Function;
|
|
16
|
+
options?: Record<string, any>;
|
|
17
|
+
children?: Record<string, ElementDefinition>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface StructureDefinition {
|
|
21
|
+
element?: ElementDefinition;
|
|
22
|
+
[key: string]: ElementDefinition | any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Structure result interface with separated structure and utility functions
|
|
27
|
+
*/
|
|
28
|
+
export interface StructureResult {
|
|
29
|
+
// The raw structure object with all components
|
|
30
|
+
structure: Record<string, any>;
|
|
31
|
+
|
|
32
|
+
// Reference to the root element for convenience
|
|
33
|
+
element: HTMLElement | ComponentLike;
|
|
34
|
+
|
|
35
|
+
// Utility functions
|
|
36
|
+
get: (name: string) => any;
|
|
37
|
+
getAll: () => Record<string, any>;
|
|
38
|
+
destroy: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Checks if a value is a component object (has an element property)
|
|
43
|
+
* Uses a faster property check before the instanceof check
|
|
44
|
+
* @param value Value to check
|
|
45
|
+
* @returns True if the value is a component-like object
|
|
46
|
+
*/
|
|
47
|
+
function isComponentLike(value: any): value is ComponentLike {
|
|
48
|
+
return value &&
|
|
49
|
+
typeof value === 'object' &&
|
|
50
|
+
'element' in value &&
|
|
51
|
+
value.element instanceof HTMLElement;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates a document fragment for faster DOM operations when appending multiple children
|
|
56
|
+
* @returns DocumentFragment
|
|
57
|
+
*/
|
|
58
|
+
function createFragment(): DocumentFragment {
|
|
59
|
+
return document.createDocumentFragment();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a DOM or component structure based on a structure definition
|
|
64
|
+
* @param definition Structure definition object
|
|
65
|
+
* @param parentElement Optional parent element to attach structure to
|
|
66
|
+
* @returns Object containing the structure and utility functions
|
|
67
|
+
*/
|
|
68
|
+
export function createStructure(
|
|
69
|
+
definition: StructureDefinition,
|
|
70
|
+
parentElement: HTMLElement | null = null
|
|
71
|
+
): StructureResult {
|
|
72
|
+
// Use object literal instead of empty object for faster property access
|
|
73
|
+
const structure: Record<string, any> = Object.create(null);
|
|
74
|
+
|
|
75
|
+
// Special case for root component creation
|
|
76
|
+
if (definition.element && !parentElement) {
|
|
77
|
+
const elementDef = definition.element;
|
|
78
|
+
const createElementFn = elementDef.creator || createElement;
|
|
79
|
+
const rootComponent = createElementFn(elementDef.options);
|
|
80
|
+
const rootElement = isComponentLike(rootComponent) ? rootComponent.element : rootComponent;
|
|
81
|
+
|
|
82
|
+
structure.element = rootComponent;
|
|
83
|
+
|
|
84
|
+
if (elementDef.name) {
|
|
85
|
+
structure[elementDef.name] = rootComponent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (elementDef.children) {
|
|
89
|
+
// Use fragment for better performance when appending multiple children
|
|
90
|
+
const fragment = createFragment();
|
|
91
|
+
let childKeys = Object.keys(elementDef.children);
|
|
92
|
+
|
|
93
|
+
// Process all children first and collect their structures
|
|
94
|
+
const childStructures = new Array(childKeys.length);
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < childKeys.length; i++) {
|
|
97
|
+
const key = childKeys[i];
|
|
98
|
+
const childDef = elementDef.children[key];
|
|
99
|
+
|
|
100
|
+
// Create child structure and attach to fragment temporarily
|
|
101
|
+
const childResult = createStructure({ [key]: childDef }, fragment);
|
|
102
|
+
childStructures[i] = childResult.structure;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Append fragment to root element (single DOM operation)
|
|
106
|
+
rootElement.appendChild(fragment);
|
|
107
|
+
|
|
108
|
+
// Now merge all child structures into the main structure
|
|
109
|
+
for (let i = 0; i < childStructures.length; i++) {
|
|
110
|
+
Object.assign(structure, childStructures[i]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create and return the result object with utility functions
|
|
115
|
+
return createStructureResult(structure);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Use fragment if we have multiple elements to append to the parent
|
|
119
|
+
const fragment = parentElement ? createFragment() : null;
|
|
120
|
+
const keys = Object.keys(definition);
|
|
121
|
+
const keyLength = keys.length;
|
|
122
|
+
|
|
123
|
+
// Pre-allocate arrays for better performance
|
|
124
|
+
const elements = new Array(keyLength);
|
|
125
|
+
const childStructuresToMerge = [];
|
|
126
|
+
|
|
127
|
+
// First pass: create all elements
|
|
128
|
+
for (let i = 0; i < keyLength; i++) {
|
|
129
|
+
const key = keys[i];
|
|
130
|
+
const def = definition[key];
|
|
131
|
+
|
|
132
|
+
// Skip if no definition
|
|
133
|
+
if (!def) {
|
|
134
|
+
elements[i] = null;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create the element
|
|
139
|
+
const createElementFn = def.creator || createElement;
|
|
140
|
+
const created = createElementFn(def.options);
|
|
141
|
+
elements[i] = created;
|
|
142
|
+
|
|
143
|
+
// Add to structure
|
|
144
|
+
structure[key] = created;
|
|
145
|
+
|
|
146
|
+
// Add element to structure with its name if different from key
|
|
147
|
+
if (def.name && def.name !== key) {
|
|
148
|
+
structure[def.name] = created;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Second pass: handle children and append to parent
|
|
153
|
+
for (let i = 0; i < keyLength; i++) {
|
|
154
|
+
const key = keys[i];
|
|
155
|
+
const def = definition[key];
|
|
156
|
+
const created = elements[i];
|
|
157
|
+
|
|
158
|
+
if (!created || !def) continue;
|
|
159
|
+
|
|
160
|
+
// Get the actual DOM element
|
|
161
|
+
const element = isComponentLike(created) ? created.element : created;
|
|
162
|
+
|
|
163
|
+
// Append to fragment
|
|
164
|
+
if (fragment) {
|
|
165
|
+
fragment.appendChild(element);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Process children recursively
|
|
169
|
+
if (def.children) {
|
|
170
|
+
const childResult = createStructure(def.children, element);
|
|
171
|
+
childStructuresToMerge.push(childResult.structure);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Append fragment to parent (single DOM operation)
|
|
176
|
+
if (parentElement && fragment) {
|
|
177
|
+
parentElement.appendChild(fragment);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Merge all child structures at once
|
|
181
|
+
for (let i = 0; i < childStructuresToMerge.length; i++) {
|
|
182
|
+
Object.assign(structure, childStructuresToMerge[i]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Create and return the result object with utility functions
|
|
186
|
+
return createStructureResult(structure);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Creates a result object with the structure and utility functions
|
|
191
|
+
* @param structure The raw structure object
|
|
192
|
+
* @returns Result object with structure and utility functions
|
|
193
|
+
*/
|
|
194
|
+
function createStructureResult(structure: Record<string, any>): StructureResult {
|
|
195
|
+
return {
|
|
196
|
+
// Raw structure object
|
|
197
|
+
structure,
|
|
198
|
+
|
|
199
|
+
// Root element reference for convenience
|
|
200
|
+
element: structure.element,
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Gets a component by name
|
|
204
|
+
* @param name Component name
|
|
205
|
+
* @returns Component if found, null otherwise
|
|
206
|
+
*/
|
|
207
|
+
get: (name: string): any => {
|
|
208
|
+
return structure[name] || null;
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Gets all components in a flattened map
|
|
213
|
+
* @returns Object with all components
|
|
214
|
+
*/
|
|
215
|
+
getAll: (): Record<string, any> => {
|
|
216
|
+
return flattenStructure(structure);
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Destroys the structure, cleaning up all components
|
|
221
|
+
*/
|
|
222
|
+
destroy: (): void => {
|
|
223
|
+
// Clean up the root element if it exists
|
|
224
|
+
if (structure.element) {
|
|
225
|
+
// If element is a component with a destroy method, call it
|
|
226
|
+
if (isComponentLike(structure.element) && typeof structure.element.destroy === 'function') {
|
|
227
|
+
structure.element.destroy();
|
|
228
|
+
}
|
|
229
|
+
// Otherwise, if it's a DOM element or component, remove it from the DOM
|
|
230
|
+
else if (isComponentLike(structure.element) && structure.element.element.parentNode) {
|
|
231
|
+
structure.element.element.parentNode.removeChild(structure.element.element);
|
|
232
|
+
}
|
|
233
|
+
else if (structure.element instanceof HTMLElement && structure.element.parentNode) {
|
|
234
|
+
structure.element.parentNode.removeChild(structure.element);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Clean up all other components in the structure
|
|
239
|
+
Object.keys(structure).forEach(key => {
|
|
240
|
+
if (key === 'element') {
|
|
241
|
+
return; // Already handled element
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const item = structure[key];
|
|
245
|
+
|
|
246
|
+
// Handle component objects
|
|
247
|
+
if (isComponentLike(item) && typeof item.destroy === 'function') {
|
|
248
|
+
item.destroy();
|
|
249
|
+
}
|
|
250
|
+
// Handle DOM elements
|
|
251
|
+
else if (item instanceof HTMLElement && item.parentNode) {
|
|
252
|
+
item.parentNode.removeChild(item);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Flattens a nested structure into a simple object with element and component references
|
|
261
|
+
* Optimized version that avoids unnecessary type checks where possible
|
|
262
|
+
* @param structure Structure object
|
|
263
|
+
* @returns Flattened structure with all elements and components
|
|
264
|
+
*/
|
|
265
|
+
export function flattenStructure(structure: Record<string, any>): Record<string, any> {
|
|
266
|
+
const flattened: Record<string, any> = Object.create(null);
|
|
267
|
+
const keys = Object.keys(structure);
|
|
268
|
+
|
|
269
|
+
for (let i = 0; i < keys.length; i++) {
|
|
270
|
+
const key = keys[i];
|
|
271
|
+
const value = structure[key];
|
|
272
|
+
|
|
273
|
+
// Fast path for common cases
|
|
274
|
+
if (value instanceof HTMLElement ||
|
|
275
|
+
(value && typeof value === 'object' && 'element' in value)) {
|
|
276
|
+
flattened[key] = value;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Skip functions and other non-element/component objects
|
|
281
|
+
if (typeof value !== 'function' &&
|
|
282
|
+
(value instanceof Element || isComponentLike(value))) {
|
|
283
|
+
flattened[key] = value;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return flattened;
|
|
288
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { default as createDialog } from './components/dialog'
|
|
|
16
16
|
export { default as createDivider } from './components/divider'
|
|
17
17
|
export { default as createMenu } from './components/menu'
|
|
18
18
|
export { default as createNavigation } from './components/navigation'
|
|
19
|
+
export { default as createNavigationSystem } from './components/navigation'
|
|
19
20
|
export { default as createProgress } from './components/progress'
|
|
20
21
|
export { default as createRadios } from './components/radios'
|
|
21
22
|
export { default as createSearch } from './components/search'
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// src/components/navigation/_mobile.scss
|
|
2
|
+
@use '../../styles/abstract/variables' as v;
|
|
3
|
+
@use '../../styles/abstract/mixins' as m;
|
|
4
|
+
@use '../../styles/abstract/base' as base;
|
|
5
|
+
@use '../../styles/abstract/theme' as t;
|
|
6
|
+
|
|
7
|
+
$prefix: base.$prefix;
|
|
8
|
+
|
|
9
|
+
// Mobile navigation overlay
|
|
10
|
+
.#{$prefix}-nav-overlay {
|
|
11
|
+
position: fixed;
|
|
12
|
+
top: 0;
|
|
13
|
+
left: 0;
|
|
14
|
+
right: 0;
|
|
15
|
+
bottom: 0;
|
|
16
|
+
background-color: t.alpha('shadow', 0.5);
|
|
17
|
+
z-index: v.z-index('modal') - 1; // Just below modal level but above most content
|
|
18
|
+
opacity: 0;
|
|
19
|
+
visibility: hidden;
|
|
20
|
+
transition: opacity 0.3s v.motion('easing-standard'), visibility 0.3s v.motion('easing-standard');
|
|
21
|
+
|
|
22
|
+
// Add backdrop blur for a modern effect where supported
|
|
23
|
+
@supports (backdrop-filter: blur(4px)) {
|
|
24
|
+
backdrop-filter: blur(4px);
|
|
25
|
+
background-color: t.alpha('shadow', 0.4);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&.active {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
visibility: visible;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Close button for mobile drawer
|
|
35
|
+
.#{$prefix}-nav-close-btn {
|
|
36
|
+
position: absolute;
|
|
37
|
+
top: 12px;
|
|
38
|
+
right: 12px;
|
|
39
|
+
width: 40px;
|
|
40
|
+
height: 40px;
|
|
41
|
+
border-radius: 50%;
|
|
42
|
+
background-color: transparent;
|
|
43
|
+
border: none;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
display: none; // Hidden by default, shown on mobile
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
color: t.color('on-surface');
|
|
49
|
+
z-index: 1;
|
|
50
|
+
-webkit-tap-highlight-color: transparent; // Removes default mobile tap highlight
|
|
51
|
+
|
|
52
|
+
&:hover {
|
|
53
|
+
background-color: t.alpha('on-surface', 0.05);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Touch feedback state
|
|
57
|
+
&.active {
|
|
58
|
+
background-color: t.alpha('on-surface', 0.12);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&:focus-visible {
|
|
62
|
+
outline: 2px solid t.color('primary');
|
|
63
|
+
outline-offset: 2px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
svg {
|
|
67
|
+
width: 24px;
|
|
68
|
+
height: 24px;
|
|
69
|
+
stroke: currentColor;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Ensure WCAG-compliant touch target size
|
|
73
|
+
@media (pointer: coarse) {
|
|
74
|
+
min-width: 48px;
|
|
75
|
+
min-height: 48px;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Body class to prevent scrolling when drawer is open
|
|
80
|
+
.#{$prefix}-body-drawer-open {
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
|
|
83
|
+
// On iOS we need to handle the body position differently
|
|
84
|
+
@supports (-webkit-overflow-scrolling: touch) {
|
|
85
|
+
position: fixed;
|
|
86
|
+
width: 100%;
|
|
87
|
+
height: 100%;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Responsive behavior for navigation system - apply to small screens or touch devices
|
|
92
|
+
@media (max-width: 960px), (pointer: coarse) {
|
|
93
|
+
// Rail navigation
|
|
94
|
+
.#{$prefix}-nav--rail {
|
|
95
|
+
width: 56px;
|
|
96
|
+
z-index: v.z-index('modal') - 1;
|
|
97
|
+
border-right: 1px solid t.color('outline-variant');
|
|
98
|
+
|
|
99
|
+
// Hide labels on mobile
|
|
100
|
+
.#{$prefix}-nav-label {
|
|
101
|
+
display: none;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Center align items
|
|
105
|
+
.#{$prefix}-nav-item {
|
|
106
|
+
justify-content: center;
|
|
107
|
+
height: 56px;
|
|
108
|
+
-webkit-tap-highlight-color: transparent; // Remove default mobile highlight
|
|
109
|
+
|
|
110
|
+
// Increase touch targets for better accessibility
|
|
111
|
+
@media (pointer: coarse) {
|
|
112
|
+
min-height: 48px;
|
|
113
|
+
padding: 12px 8px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Touch-friendly active state
|
|
117
|
+
&:active {
|
|
118
|
+
background-color: t.alpha('on-surface', 0.12);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Larger icons on mobile
|
|
123
|
+
.#{$prefix}-nav-icon {
|
|
124
|
+
font-size: 1.25rem;
|
|
125
|
+
|
|
126
|
+
// Ensure icon touch target meets WCAG requirements
|
|
127
|
+
@media (pointer: coarse) {
|
|
128
|
+
min-width: 44px;
|
|
129
|
+
min-height: 44px;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Drawer navigation
|
|
135
|
+
.#{$prefix}-nav--drawer {
|
|
136
|
+
position: fixed;
|
|
137
|
+
top: 0;
|
|
138
|
+
bottom: 0;
|
|
139
|
+
left: 0;
|
|
140
|
+
width: 280px;
|
|
141
|
+
max-width: 85vw;
|
|
142
|
+
z-index: v.z-index('modal');
|
|
143
|
+
background-color: t.color('surface');
|
|
144
|
+
box-shadow: v.elevation('level-3');
|
|
145
|
+
transform: translateX(-100%);
|
|
146
|
+
transition: transform 0.3s v.motion('easing-standard');
|
|
147
|
+
overflow-y: auto;
|
|
148
|
+
-webkit-overflow-scrolling: touch; // Smooth scrolling on iOS
|
|
149
|
+
|
|
150
|
+
&:not(.#{$prefix}-nav--hidden) {
|
|
151
|
+
transform: translateX(0);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Optimize drawer items for touch
|
|
155
|
+
.#{$prefix}-nav-item {
|
|
156
|
+
-webkit-tap-highlight-color: transparent;
|
|
157
|
+
|
|
158
|
+
@media (pointer: coarse) {
|
|
159
|
+
min-height: 48px;
|
|
160
|
+
padding: 12px 16px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Touch feedback
|
|
164
|
+
&:active {
|
|
165
|
+
background-color: t.alpha('on-surface', 0.12);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Support RTL languages
|
|
170
|
+
@include m.rtl {
|
|
171
|
+
left: auto;
|
|
172
|
+
right: 0;
|
|
173
|
+
transform: translateX(100%);
|
|
174
|
+
|
|
175
|
+
&:not(.#{$prefix}-nav--hidden) {
|
|
176
|
+
transform: translateX(0);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Desktop behavior
|
|
183
|
+
@media (min-width: 961px) {
|
|
184
|
+
.#{$prefix}-nav--drawer {
|
|
185
|
+
// For desktop, transition by width instead of transform
|
|
186
|
+
transition: width 0.3s v.motion('easing-standard'), opacity 0.3s v.motion('easing-standard');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.#{$prefix}-nav-close-btn {
|
|
190
|
+
display: none !important; // Always hidden on desktop
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Hide overlay on desktop
|
|
194
|
+
.#{$prefix}-nav-overlay.active {
|
|
195
|
+
opacity: 0;
|
|
196
|
+
visibility: hidden;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Safe area insets for notched devices like iPhone X and newer
|
|
201
|
+
@supports (padding-bottom: env(safe-area-inset-bottom)) {
|
|
202
|
+
.#{$prefix}-nav--drawer {
|
|
203
|
+
padding-top: env(safe-area-inset-top);
|
|
204
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.#{$prefix}-nav--rail {
|
|
208
|
+
padding-top: env(safe-area-inset-top);
|
|
209
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.#{$prefix}-nav--bar {
|
|
213
|
+
// For bottom navigation bars
|
|
214
|
+
&.#{$prefix}-nav--bottom {
|
|
215
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Gesture hints for touch interfaces
|
|
221
|
+
@media (pointer: coarse) {
|
|
222
|
+
// Add visual affordance for swipe gesture
|
|
223
|
+
.#{$prefix}-nav--drawer:not(.#{$prefix}-nav--hidden) {
|
|
224
|
+
&::after {
|
|
225
|
+
content: '';
|
|
226
|
+
position: absolute;
|
|
227
|
+
top: 50%;
|
|
228
|
+
right: 0;
|
|
229
|
+
width: 4px;
|
|
230
|
+
height: 40px;
|
|
231
|
+
border-radius: 4px 0 0 4px;
|
|
232
|
+
background-color: t.alpha('on-surface', 0.1);
|
|
233
|
+
transform: translateY(-50%);
|
|
234
|
+
opacity: 0.5;
|
|
235
|
+
// Hide after interaction to avoid cognitive load
|
|
236
|
+
animation: fadeOutAfterDelay 4s forwards;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@keyframes fadeOutAfterDelay {
|
|
241
|
+
0%, 50% { opacity: 0.5; }
|
|
242
|
+
100% { opacity: 0; }
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/components/navigation/system.scss
|
|
2
|
+
|
|
3
|
+
// Navigation system specific styles
|
|
4
|
+
// These complement the existing navigation component styles
|
|
5
|
+
|
|
6
|
+
$nav-animation-duration: 0.2s;
|
|
7
|
+
$nav-animation-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
8
|
+
|
|
9
|
+
// Drawer animation states
|
|
10
|
+
.mtrl-nav--drawer {
|
|
11
|
+
transition: transform $nav-animation-duration $nav-animation-function,
|
|
12
|
+
opacity $nav-animation-duration $nav-animation-function;
|
|
13
|
+
|
|
14
|
+
// Hidden state (transform off-screen)
|
|
15
|
+
&.mtrl-nav--hidden {
|
|
16
|
+
transform: translateX(-100%);
|
|
17
|
+
opacity: 0;
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Right positioned drawer
|
|
22
|
+
&.mtrl-nav--right {
|
|
23
|
+
&.mtrl-nav--hidden {
|
|
24
|
+
transform: translateX(100%);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Animation classes
|
|
29
|
+
&.mtrl-nav--animate-in {
|
|
30
|
+
animation: nav-drawer-in $nav-animation-duration $nav-animation-function;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&.mtrl-nav--animate-out {
|
|
34
|
+
animation: nav-drawer-out $nav-animation-duration $nav-animation-function;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Navigation system container (optional - for wrapping rail + drawer)
|
|
39
|
+
.mtrl-nav-system {
|
|
40
|
+
display: flex;
|
|
41
|
+
position: relative;
|
|
42
|
+
|
|
43
|
+
// Rail is fixed width
|
|
44
|
+
.mtrl-nav--rail {
|
|
45
|
+
flex: 0 0 auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Drawer expands
|
|
49
|
+
.mtrl-nav--drawer {
|
|
50
|
+
flex: 1 0 auto;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Drawer entry animation
|
|
55
|
+
@keyframes nav-drawer-in {
|
|
56
|
+
0% {
|
|
57
|
+
transform: translateX(-20px);
|
|
58
|
+
opacity: 0;
|
|
59
|
+
}
|
|
60
|
+
100% {
|
|
61
|
+
transform: translateX(0);
|
|
62
|
+
opacity: 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Drawer exit animation
|
|
67
|
+
@keyframes nav-drawer-out {
|
|
68
|
+
0% {
|
|
69
|
+
transform: translateX(0);
|
|
70
|
+
opacity: 1;
|
|
71
|
+
}
|
|
72
|
+
100% {
|
|
73
|
+
transform: translateX(-20px);
|
|
74
|
+
opacity: 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Right-side drawer animations
|
|
79
|
+
.mtrl-nav--drawer.mtrl-nav--right {
|
|
80
|
+
&.mtrl-nav--animate-in {
|
|
81
|
+
animation: nav-drawer-right-in $nav-animation-duration $nav-animation-function;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&.mtrl-nav--animate-out {
|
|
85
|
+
animation: nav-drawer-right-out $nav-animation-duration $nav-animation-function;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes nav-drawer-right-in {
|
|
90
|
+
0% {
|
|
91
|
+
transform: translateX(20px);
|
|
92
|
+
opacity: 0;
|
|
93
|
+
}
|
|
94
|
+
100% {
|
|
95
|
+
transform: translateX(0);
|
|
96
|
+
opacity: 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes nav-drawer-right-out {
|
|
101
|
+
0% {
|
|
102
|
+
transform: translateX(0);
|
|
103
|
+
opacity: 1;
|
|
104
|
+
}
|
|
105
|
+
100% {
|
|
106
|
+
transform: translateX(20px);
|
|
107
|
+
opacity: 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Mobile optimizations
|
|
112
|
+
@media (max-width: 960px) {
|
|
113
|
+
.mtrl-nav-system {
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
|
|
116
|
+
.mtrl-nav--rail {
|
|
117
|
+
// Mobile rail can be top or bottom
|
|
118
|
+
&.mtrl-nav--top, &.mtrl-nav--bottom {
|
|
119
|
+
width: 100%;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.mtrl-nav--drawer {
|
|
124
|
+
// Mobile drawer takes full width
|
|
125
|
+
width: 100%;
|
|
126
|
+
max-width: 100%;
|
|
127
|
+
|
|
128
|
+
&.mtrl-nav--hidden {
|
|
129
|
+
transform: translateY(-100%);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&.mtrl-nav--bottom {
|
|
133
|
+
&.mtrl-nav--hidden {
|
|
134
|
+
transform: translateY(100%);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Focus and accessibility enhancements
|
|
142
|
+
.mtrl-nav-system {
|
|
143
|
+
[role="navigation"] {
|
|
144
|
+
&:focus-within {
|
|
145
|
+
outline: 2px solid var(--mtrl-primary-color, #6200ee);
|
|
146
|
+
outline-offset: -2px;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@use 'navigation-mobile'
|