mtrl 0.2.8 → 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.
Files changed (42) hide show
  1. package/index.ts +2 -0
  2. package/package.json +1 -1
  3. package/src/components/navigation/api.ts +131 -96
  4. package/src/components/navigation/features/controller.ts +273 -0
  5. package/src/components/navigation/features/items.ts +133 -64
  6. package/src/components/navigation/navigation.ts +17 -2
  7. package/src/components/navigation/system-types.ts +124 -0
  8. package/src/components/navigation/system.ts +776 -0
  9. package/src/components/slider/config.ts +20 -2
  10. package/src/components/slider/features/controller.ts +761 -0
  11. package/src/components/slider/features/handlers.ts +18 -15
  12. package/src/components/slider/features/index.ts +3 -2
  13. package/src/components/slider/features/range.ts +104 -0
  14. package/src/components/slider/slider.ts +34 -14
  15. package/src/components/slider/structure.ts +152 -0
  16. package/src/components/textfield/api.ts +53 -0
  17. package/src/components/textfield/features.ts +322 -0
  18. package/src/components/textfield/textfield.ts +8 -0
  19. package/src/components/textfield/types.ts +12 -3
  20. package/src/components/timepicker/clockdial.ts +1 -4
  21. package/src/core/compose/features/textinput.ts +15 -2
  22. package/src/core/composition/features/dom.ts +33 -0
  23. package/src/core/composition/features/icon.ts +131 -0
  24. package/src/core/composition/features/index.ts +11 -0
  25. package/src/core/composition/features/label.ts +156 -0
  26. package/src/core/composition/features/structure.ts +22 -0
  27. package/src/core/composition/index.ts +26 -0
  28. package/src/core/index.ts +1 -1
  29. package/src/core/structure.ts +288 -0
  30. package/src/index.ts +1 -0
  31. package/src/styles/components/_navigation-mobile.scss +244 -0
  32. package/src/styles/components/_navigation-system.scss +151 -0
  33. package/src/styles/components/_textfield.scss +250 -11
  34. package/demo/build.ts +0 -349
  35. package/demo/index.html +0 -110
  36. package/demo/main.js +0 -448
  37. package/demo/styles.css +0 -239
  38. package/server.ts +0 -86
  39. package/src/components/slider/features/slider.ts +0 -318
  40. package/src/components/slider/features/structure.ts +0 -181
  41. package/src/components/slider/features/ui.ts +0 -388
  42. package/src/components/textfield/constants.ts +0 -100
@@ -0,0 +1,156 @@
1
+ // src/core/composition/features/label.ts
2
+ import { createElement } from '../../dom/create';
3
+
4
+ /**
5
+ * Configuration for label feature
6
+ */
7
+ export interface LabelConfig {
8
+ /**
9
+ * Label text content
10
+ */
11
+ label?: string;
12
+
13
+ /**
14
+ * Position of the label ('start', 'end', 'top', or 'bottom')
15
+ */
16
+ labelPosition?: 'start' | 'end' | 'top' | 'bottom';
17
+
18
+ /**
19
+ * CSS class prefix
20
+ */
21
+ prefix?: string;
22
+
23
+ /**
24
+ * Component name for class generation
25
+ */
26
+ componentName?: string;
27
+
28
+ /**
29
+ * ID for the input element this label is associated with
30
+ */
31
+ id?: string;
32
+
33
+ /**
34
+ * Whether the labeled element is required
35
+ */
36
+ required?: boolean;
37
+
38
+ [key: string]: any;
39
+ }
40
+
41
+ /**
42
+ * Enhances structure definition with a label element
43
+ * Unlike the traditional withLabel, this modifies the structure definition
44
+ * without creating actual DOM elements.
45
+ *
46
+ * @param config Configuration containing label information
47
+ * @returns Component enhancer that adds label to structure definition
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * // Add label to a structure definition
52
+ * const component = pipe(
53
+ * createBase,
54
+ * withStructure(config),
55
+ * withLabel(config)
56
+ * )(config);
57
+ * ```
58
+ */
59
+ export const withLabel = (config: LabelConfig) => component => {
60
+ // If no label or missing structure definition, return unmodified
61
+
62
+ if (!config.label || !component.structureDefinition) {
63
+ return component;
64
+ }
65
+
66
+ try {
67
+ // Get class name generator
68
+ const getClass = (className) => {
69
+ if (typeof component.getClass === 'function') {
70
+ return component.getClass(className);
71
+ }
72
+ const prefix = config.prefix || 'mtrl';
73
+ return `${prefix}-${className}`;
74
+ };
75
+
76
+ // Get component details
77
+ const prefix = config.prefix || component.config?.prefix || 'mtrl';
78
+ const componentName = config.componentName || component.componentName || 'component';
79
+
80
+ // Clone the structure definition
81
+ const structureDefinition = JSON.parse(JSON.stringify(component.structureDefinition));
82
+
83
+ // Determine position (default to 'start')
84
+ const position = config.labelPosition || 'start';
85
+
86
+ // Create the label element definition
87
+ let labelContent = config.label;
88
+ let labelChildren = null;
89
+
90
+ // Add required indicator if specified
91
+ if (config.required) {
92
+ // For structure definition we need to define a child element
93
+ labelChildren = {
94
+ requiredIndicator: {
95
+ name: 'requiredIndicator',
96
+ creator: createElement,
97
+ options: {
98
+ tag: 'span',
99
+ className: `${prefix}-${componentName}-label-required`,
100
+ text: '*',
101
+ attrs: {
102
+ 'aria-hidden': 'true'
103
+ }
104
+ }
105
+ }
106
+ };
107
+
108
+ // Clear the content since we'll use children instead
109
+ labelContent = config.label;
110
+ }
111
+
112
+ // Create label element definition
113
+ const labelDef = {
114
+ name: 'label',
115
+ creator: createElement,
116
+ options: {
117
+ tag: 'label',
118
+ className: [
119
+ `${prefix}-${componentName}-label`,
120
+ `${prefix}-${componentName}-label--${position}`
121
+ ],
122
+ attrs: {
123
+ 'for': config.id // Optional connection to input by ID
124
+ },
125
+ text: labelContent
126
+ }
127
+ };
128
+
129
+ // Add children if we have them
130
+ if (labelChildren) {
131
+ labelDef.children = labelChildren;
132
+ }
133
+
134
+ // Add label to root element's children
135
+ if (position === 'end' || position === 'bottom') {
136
+ // Add at end of root element
137
+ structureDefinition.element.children.label = labelDef;
138
+ } else {
139
+ // Add at beginning of root element (start/top)
140
+ const existingChildren = { ...structureDefinition.element.children };
141
+ structureDefinition.element.children = {
142
+ label: labelDef,
143
+ ...existingChildren
144
+ };
145
+ }
146
+
147
+ // Return component with updated structure definition
148
+ return {
149
+ ...component,
150
+ structureDefinition
151
+ };
152
+ } catch (error) {
153
+ console.warn('Error enhancing structure with label:', error);
154
+ return component;
155
+ }
156
+ };
@@ -0,0 +1,22 @@
1
+ // src/core/composition/features/structure.ts
2
+
3
+ /**
4
+ * Adds structure definition to component without creating DOM
5
+ * Now uses the structure from the baseConfig
6
+ *
7
+ * @param configuration
8
+ * @returns Component enhancer with structure definition
9
+ */
10
+ export const withStructure = (config: SliderConfig) => component => {
11
+ // Use the structure definition from the config
12
+ if (!config.structureDefinition) {
13
+ console.warn('No structure definition found in slider config');
14
+ return component;
15
+ }
16
+
17
+ // Return enhanced component with structure definition
18
+ return {
19
+ ...component,
20
+ structureDefinition: config.structureDefinition
21
+ };
22
+ };
@@ -0,0 +1,26 @@
1
+ // src/core/composition/index.ts
2
+
3
+ /**
4
+ * @module core/composition
5
+ * @description Composition utilities for creating components using the structure-based approach
6
+ *
7
+ * The composition module provides features that work with the structure definition
8
+ * mechanism. Unlike traditional features that directly modify the DOM, these
9
+ * features modify a structure definition that is later used to create DOM elements.
10
+ *
11
+ * This approach provides several advantages:
12
+ * - Clearer separation between structure definition and DOM creation
13
+ * - More predictable component creation process
14
+ * - Better support for server-side rendering
15
+ * - Enhanced testability
16
+ */
17
+
18
+ // Export features
19
+ export {
20
+ withStructure
21
+ withDom
22
+ withIcon,
23
+ withLabel,
24
+ } from './features';
25
+
26
+ export type { IconConfig, LabelConfig } from './features';
package/src/core/index.ts CHANGED
@@ -11,7 +11,7 @@ export type {
11
11
  ComponentConfig,
12
12
  ThemedComponentConfig,
13
13
  VariantComponentConfig,
14
- StateComponentConfig
14
+ StateComponentConfig
15
15
  } from './config';
16
16
 
17
17
  // Build
@@ -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'