mtrl 0.2.9 → 0.3.0

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 (44) hide show
  1. package/index.ts +2 -0
  2. package/package.json +1 -1
  3. package/src/components/button/button.ts +34 -5
  4. package/src/components/navigation/system/core.ts +302 -0
  5. package/src/components/navigation/system/events.ts +240 -0
  6. package/src/components/navigation/system/index.ts +184 -0
  7. package/src/components/navigation/system/mobile.ts +278 -0
  8. package/src/components/navigation/system/state.ts +77 -0
  9. package/src/components/navigation/system/types.ts +364 -0
  10. package/src/components/slider/config.ts +2 -2
  11. package/src/components/slider/features/controller.ts +1 -25
  12. package/src/components/slider/features/handlers.ts +0 -1
  13. package/src/components/slider/features/range.ts +7 -7
  14. package/src/components/slider/{structure.ts → schema.ts} +2 -13
  15. package/src/components/slider/slider.ts +3 -2
  16. package/src/components/switch/api.ts +16 -0
  17. package/src/components/switch/config.ts +1 -18
  18. package/src/components/switch/features.ts +198 -0
  19. package/src/components/switch/index.ts +1 -0
  20. package/src/components/switch/switch.ts +3 -3
  21. package/src/components/switch/types.ts +14 -2
  22. package/src/core/composition/features/dom.ts +26 -14
  23. package/src/core/composition/features/icon.ts +18 -18
  24. package/src/core/composition/features/index.ts +3 -2
  25. package/src/core/composition/features/label.ts +16 -17
  26. package/src/core/composition/features/layout.ts +47 -0
  27. package/src/core/composition/index.ts +4 -4
  28. package/src/core/layout/README.md +350 -0
  29. package/src/core/layout/array.ts +181 -0
  30. package/src/core/layout/create.ts +55 -0
  31. package/src/core/layout/index.ts +26 -0
  32. package/src/core/layout/object.ts +124 -0
  33. package/src/core/layout/processor.ts +58 -0
  34. package/src/core/layout/result.ts +85 -0
  35. package/src/core/layout/types.ts +125 -0
  36. package/src/core/layout/utils.ts +136 -0
  37. package/src/styles/abstract/_variables.scss +28 -0
  38. package/src/styles/components/_switch.scss +133 -69
  39. package/src/styles/components/_textfield.scss +9 -16
  40. package/src/components/navigation/system-types.ts +0 -124
  41. package/src/components/navigation/system.ts +0 -776
  42. package/src/core/composition/features/structure.ts +0 -22
  43. package/src/core/layout/index.js +0 -95
  44. package/src/core/structure.ts +0 -288
@@ -0,0 +1,124 @@
1
+ // src/core/layout/object.ts
2
+ /**
3
+ * @module core/layout
4
+ * @description Processor for object-based layout schemas
5
+ */
6
+
7
+ import { createElement } from '../dom/create';
8
+ import { Schema, LayoutResult, LayoutOptions } from './types';
9
+ import { isComponent, createFragment, processClassNames } from './utils';
10
+ import { createLayoutResult } from './result';
11
+ import { createComponentInstance } from './processor';
12
+ import { processArraySchema } from './array';
13
+
14
+ /**
15
+ * Processes an object-based layout definition
16
+ *
17
+ * @param schema - Object-based layout definition
18
+ * @param parentElement - Optional parent element to attach layout to
19
+ * @param options - Layout creation options
20
+ * @returns Layout result object
21
+ */
22
+ export function processObjectSchema(
23
+ schema: Schema,
24
+ parentElement: HTMLElement | null = null,
25
+ options: LayoutOptions = {}
26
+ ): LayoutResult {
27
+ const layout = {};
28
+
29
+ // Get default creator function from options or use createElement
30
+ const defaultCreator = options.creator || createElement;
31
+
32
+ // Handle root element creation
33
+ if (schema.element && !parentElement) {
34
+ const elementDef = schema.element;
35
+ const createElementFn = elementDef.creator || defaultCreator;
36
+
37
+ // Process class names based on prefix option
38
+ const elementOptions = elementDef.options || {};
39
+ const processedOptions = options.prefix !== false ?
40
+ processClassNames(elementOptions) :
41
+ { ...elementOptions };
42
+
43
+ const rootComponent = createComponentInstance(createElementFn, processedOptions, options);
44
+ const rootElement = isComponent(rootComponent) ? rootComponent.element : rootComponent;
45
+
46
+ layout.element = rootComponent;
47
+ if (elementDef.name) layout[elementDef.name] = rootComponent;
48
+
49
+ // Process children
50
+ if (elementDef.children) {
51
+ const fragment = createFragment();
52
+ const childLayouts = [];
53
+
54
+ for (const key in elementDef.children) {
55
+ const childResult = processObjectSchema(
56
+ { [key]: elementDef.children[key] },
57
+ fragment,
58
+ options
59
+ );
60
+ childLayouts.push(childResult.layout);
61
+ }
62
+
63
+ rootElement.appendChild(fragment);
64
+
65
+ // Merge child layouts
66
+ for (const childLayout of childLayouts) {
67
+ Object.assign(layout, childLayout);
68
+ }
69
+ }
70
+
71
+ return createLayoutResult(layout);
72
+ }
73
+
74
+ // Process normal schema elements
75
+ const fragment = parentElement ? createFragment() : null;
76
+ const childLayouts = [];
77
+
78
+ for (const key in schema) {
79
+ const def = schema[key];
80
+ if (!def) continue;
81
+
82
+ // Use appropriate creator
83
+ const elementCreator = def.creator || defaultCreator;
84
+
85
+ // Process class names based on prefix option
86
+ const elementOptions = def.options || {};
87
+ const shouldApplyPrefix =
88
+ 'prefix' in elementOptions ? elementOptions.prefix : options.prefix !== false;
89
+
90
+ const processedOptions = shouldApplyPrefix ?
91
+ processClassNames(elementOptions) :
92
+ { ...elementOptions };
93
+
94
+ // Create element
95
+ const created = createComponentInstance(elementCreator, processedOptions, options);
96
+
97
+ // Store in layout
98
+ layout[key] = created;
99
+ if (def.name && def.name !== key) layout[def.name] = created;
100
+
101
+ // Handle DOM operations
102
+ const element = isComponent(created) ? created.element : created;
103
+ if (fragment) fragment.appendChild(element);
104
+
105
+ // Process children
106
+ if (def.children) {
107
+ const childResult = processObjectSchema(def.children, element, options);
108
+ childLayouts.push(childResult.layout);
109
+ }
110
+ }
111
+
112
+ // Append to parent
113
+ if (parentElement && fragment) {
114
+ const parentDom = isComponent(parentElement) ? parentElement.element : parentElement;
115
+ parentDom.appendChild(fragment);
116
+ }
117
+
118
+ // Merge child layouts
119
+ for (const childLayout of childLayouts) {
120
+ Object.assign(layout, childLayout);
121
+ }
122
+
123
+ return createLayoutResult(layout);
124
+ }
@@ -0,0 +1,58 @@
1
+ // src/core/layout/processor.ts
2
+ /**
3
+ * @module core/layout
4
+ * @description Lightweight processor for layout creation, optimized for bundle size
5
+ */
6
+
7
+ import { Schema, LayoutResult, LayoutOptions } from './types';
8
+ import { isComponent } from './utils';
9
+ import { processArraySchema } from './array';
10
+ import { processObjectSchema } from './object';
11
+ import { isObject } from '../utils';
12
+
13
+ /**
14
+ * Creates a component from a constructor or factory function
15
+ *
16
+ * @param Component - Component constructor or factory function
17
+ * @param options - Component creation options
18
+ * @param layoutOptions - Global layout options
19
+ * @returns Created component instance
20
+ */
21
+ export function createComponentInstance(
22
+ Component: any,
23
+ options: Record<string, any> = {},
24
+ layoutOptions: LayoutOptions = {}
25
+ ): any {
26
+ // Check if Component is a class constructor
27
+ const isClass = typeof Component === 'function' &&
28
+ Component.prototype &&
29
+ Component.prototype.constructor === Component &&
30
+ // Exclude native constructors like Object, Array, etc.
31
+ Object.getPrototypeOf(Component) !== Function.prototype;
32
+
33
+ // Use 'new' for class constructors, call directly for function factories
34
+ return isClass
35
+ ? new Component(options)
36
+ : Component(options);
37
+ }
38
+
39
+ /**
40
+ * Processes any type of layout definition (array or object)
41
+ * This is the main entry point for schema processing
42
+ *
43
+ * @param schema - Layout schema to process
44
+ * @param parentElement - Parent element to attach to
45
+ * @param level - Current nesting level
46
+ * @param options - Layout creation options
47
+ * @returns Layout result object
48
+ */
49
+ export function processSchema(
50
+ schema: any,
51
+ parentElement: HTMLElement | null = null,
52
+ level: number = 0,
53
+ options: LayoutOptions = {}
54
+ ): LayoutResult {
55
+ return Array.isArray(schema)
56
+ ? processArraySchema(schema, parentElement, level, options)
57
+ : processObjectSchema(schema, parentElement, options);
58
+ }
@@ -0,0 +1,85 @@
1
+ // src/core/layout/result.ts
2
+ /**
3
+ * @module core/layout
4
+ * @description Simplified layout result creation and management
5
+ */
6
+
7
+ import { LayoutResult } from './types';
8
+ import { isComponent, flattenLayout } from './utils';
9
+
10
+ /**
11
+ * Creates a result object with the layout and utility functions
12
+ * Simplified API for better usability and reduced overhead
13
+ *
14
+ * @param layout - The raw layout object
15
+ * @returns Result object with layout and utility functions
16
+ */
17
+ export function createLayoutResult(layout: Record<string, any>): LayoutResult {
18
+ // Pre-compute flattened layout for better performance
19
+ const flattenedComponents = flattenLayout(layout);
20
+
21
+ // Create the result object with layout correctly exposed
22
+ const result: LayoutResult = {
23
+ // Raw layout object
24
+ layout,
25
+
26
+ // Root element reference for convenience
27
+ element: layout.element,
28
+
29
+ // Flattened component map
30
+ component: flattenedComponents,
31
+
32
+ /**
33
+ * Gets a component by name
34
+ *
35
+ * @param name - Component name
36
+ * @returns Component if found, null otherwise
37
+ */
38
+ get(name: string): any {
39
+ return layout[name] ?? null;
40
+ },
41
+
42
+ /**
43
+ * Gets all components in a flattened map
44
+ *
45
+ * @returns Object with all components
46
+ */
47
+ getAll(): Record<string, any> {
48
+ return flattenedComponents;
49
+ },
50
+
51
+ /**
52
+ * Destroys the layout, cleaning up all components
53
+ */
54
+ destroy(): void {
55
+ // Handle root element
56
+ const root = layout.element;
57
+ if (root) {
58
+ // Component with destroy method
59
+ if (isComponent(root) && typeof root.destroy === 'function') {
60
+ root.destroy();
61
+ }
62
+ // Component without destroy method
63
+ else if (isComponent(root) && root.element?.parentNode) {
64
+ root.element.parentNode.removeChild(root.element);
65
+ }
66
+ // Direct DOM element
67
+ else if (root instanceof HTMLElement && root.parentNode) {
68
+ root.parentNode.removeChild(root);
69
+ }
70
+ }
71
+
72
+ // Clean up other components that have a destroy method
73
+ for (const key in layout) {
74
+ if (key === 'element') continue;
75
+
76
+ const item = layout[key];
77
+ if (isComponent(item) && typeof item.destroy === 'function') {
78
+ item.destroy();
79
+ }
80
+ }
81
+ }
82
+ };
83
+
84
+ return result;
85
+ }
@@ -0,0 +1,125 @@
1
+ // src/core/layout/types.ts
2
+ /**
3
+ * @module core/layout
4
+ * @description Optimized type definitions for layout creation system
5
+ */
6
+
7
+ /**
8
+ * Interface for component-like objects
9
+ */
10
+ export interface ComponentLike {
11
+ /**
12
+ * DOM element reference
13
+ */
14
+ element: HTMLElement;
15
+
16
+ /**
17
+ * Optional method to clean up resources
18
+ */
19
+ destroy?: () => void;
20
+
21
+ /**
22
+ * Allow additional properties
23
+ */
24
+ [key: string]: any;
25
+ }
26
+
27
+ /**
28
+ * Definition for a single element in the layout
29
+ */
30
+ export interface ElementDefinition {
31
+ /**
32
+ * Optional name to reference the element
33
+ */
34
+ name?: string;
35
+
36
+ /**
37
+ * Creator function that produces an HTMLElement or ComponentLike
38
+ */
39
+ creator?: (options?: Record<string, any>) => HTMLElement | ComponentLike;
40
+
41
+ /**
42
+ * Options to pass to the creator function
43
+ */
44
+ options?: Record<string, any>;
45
+
46
+ /**
47
+ * Child elements to create and attach
48
+ */
49
+ children?: Record<string, ElementDefinition>;
50
+ }
51
+
52
+ /**
53
+ * Schema for layout creation
54
+ */
55
+ export interface Schema {
56
+ /**
57
+ * Root element definition
58
+ */
59
+ element?: ElementDefinition;
60
+
61
+ /**
62
+ * Additional elements
63
+ */
64
+ [key: string]: ElementDefinition | undefined;
65
+ }
66
+
67
+ /**
68
+ * Options for layout creation
69
+ */
70
+ export interface LayoutOptions {
71
+ /**
72
+ * Default creator function to use if not specified in schema
73
+ */
74
+ creator?: (options?: Record<string, any>) => HTMLElement | ComponentLike;
75
+
76
+ /**
77
+ * Whether to apply CSS class prefix
78
+ * @default true
79
+ */
80
+ prefix?: boolean;
81
+
82
+ /**
83
+ * Additional options
84
+ */
85
+ [key: string]: any;
86
+ }
87
+
88
+ /**
89
+ * Result object returned after creating a layout
90
+ * Simplified API with essential methods
91
+ */
92
+ export interface LayoutResult {
93
+ /**
94
+ * The raw layout object with all components
95
+ */
96
+ layout: Record<string, any>;
97
+
98
+ /**
99
+ * Reference to the root element for convenience
100
+ */
101
+ element: HTMLElement | ComponentLike;
102
+
103
+ /**
104
+ * Flattened component map
105
+ */
106
+ component: Record<string, any>;
107
+
108
+ /**
109
+ * Gets a component by name
110
+ * @param name - Component name
111
+ * @returns Component if found, null otherwise
112
+ */
113
+ get(name: string): any;
114
+
115
+ /**
116
+ * Gets all components in a flattened map
117
+ * @returns Object with all components
118
+ */
119
+ getAll(): Record<string, any>;
120
+
121
+ /**
122
+ * Destroys the layout, cleaning up all components
123
+ */
124
+ destroy(): void;
125
+ }
@@ -0,0 +1,136 @@
1
+ // src/core/layout/utils.ts
2
+ /**
3
+ * @module core/layout
4
+ * @description Optimized utility functions for layout creation
5
+ */
6
+
7
+ import { PREFIX } from '../config';
8
+ import { ComponentLike } from './types';
9
+
10
+ /**
11
+ * Checks if a value is a component object (has an element property)
12
+ * Optimized fast path check by only validating that element property exists
13
+ *
14
+ * @param value - Value to check
15
+ * @returns True if the value is a component-like object
16
+ */
17
+ export function isComponent(value: any): value is ComponentLike {
18
+ return value &&
19
+ typeof value === 'object' &&
20
+ 'element' in value;
21
+ }
22
+
23
+ /**
24
+ * Creates a document fragment for faster DOM operations
25
+ *
26
+ * @returns New DocumentFragment
27
+ */
28
+ export function createFragment(): DocumentFragment {
29
+ return document.createDocumentFragment();
30
+ }
31
+
32
+ /**
33
+ * Processes className options to add prefix if needed
34
+ * Supports BEM naming conventions when enabled
35
+ *
36
+ * @param options - Element options
37
+ * @param skipPrefix - Whether to skip adding prefixes
38
+ * @param useBEM - Whether to respect BEM naming conventions
39
+ * @returns Updated options with prefixed classNames
40
+ */
41
+ export function processClassNames(
42
+ options: Record<string, any>,
43
+ skipPrefix: boolean = false,
44
+ useBEM: boolean = false
45
+ ): Record<string, any> {
46
+ // Fast path - if no options or skipping prefix, return as is
47
+ if (!options || skipPrefix) return { ...options };
48
+
49
+ // Clone options to avoid mutating the original
50
+ const processed = { ...options };
51
+
52
+ /**
53
+ * Processes a single class name with optional BEM handling
54
+ *
55
+ * @param cls - Class name to process
56
+ * @returns Processed class name with prefix
57
+ */
58
+ const processClass = (cls: string): string => {
59
+ // Already prefixed - leave it as is
60
+ if (cls.startsWith(`${PREFIX}-`)) {
61
+ return cls;
62
+ }
63
+
64
+ if (useBEM) {
65
+ // For BEM classes (with __ or --), only prefix the block part
66
+ if (cls.includes('__')) {
67
+ // This is a BEM element, prefix only the block part
68
+ const [block, element] = cls.split('__');
69
+ return `${PREFIX}-${block}__${element}`;
70
+ } else if (cls.includes('--')) {
71
+ // This is a BEM modifier, prefix only the block part
72
+ const [block, modifier] = cls.split('--');
73
+ return `${PREFIX}-${block}--${modifier}`;
74
+ }
75
+ }
76
+
77
+ // Standard case - prefix the entire class name
78
+ return `${PREFIX}-${cls}`;
79
+ };
80
+
81
+ /**
82
+ * Process a class property (either 'class' or 'className')
83
+ *
84
+ * @param prop - Property name to process
85
+ */
86
+ const processProperty = (prop: string): void => {
87
+ if (!processed[prop]) return;
88
+
89
+ // Handle string class names
90
+ if (typeof processed[prop] === 'string') {
91
+ processed[prop] = processed[prop]
92
+ .split(' ')
93
+ .map(cls => cls ? processClass(cls) : '')
94
+ .filter(Boolean)
95
+ .join(' ');
96
+ }
97
+ // Handle array class names
98
+ else if (Array.isArray(processed[prop])) {
99
+ processed[prop] = processed[prop]
100
+ .map(cls => typeof cls === 'string' ? processClass(cls) : cls)
101
+ .filter(Boolean)
102
+ .join(' ');
103
+ }
104
+ };
105
+
106
+ // Process both possible class properties for compatibility
107
+ processProperty('class');
108
+ processProperty('className');
109
+
110
+ return processed;
111
+ }
112
+
113
+ /**
114
+ * Flattens a nested layout into a simple object with element and component references
115
+ * Optimized by using a direct property access loop and early exits
116
+ *
117
+ * @param layout - Layout object
118
+ * @returns Flattened layout with all elements and components
119
+ */
120
+ export function flattenLayout(layout: Record<string, any>): Record<string, any> {
121
+ const flattened: Record<string, any> = {};
122
+
123
+ for (const key in layout) {
124
+ const value = layout[key];
125
+
126
+ // Only include components, elements, and non-functions
127
+ // Fast path with fewer type checks
128
+ if (value &&
129
+ typeof value !== 'function' &&
130
+ (value instanceof HTMLElement || 'element' in value)) {
131
+ flattened[key] = value;
132
+ }
133
+ }
134
+
135
+ return flattened;
136
+ }
@@ -207,6 +207,34 @@ $button: (
207
207
  'transition-easing': map.get($motion, 'easing-standard')
208
208
  ) !default;
209
209
 
210
+
211
+ // Define spacing scale (this might already exist, if not, add it)
212
+ $spacing-scale: (
213
+ '0': 0,
214
+ '1': 4px,
215
+ '2': 8px,
216
+ '3': 12px,
217
+ '4': 16px,
218
+ '5': 20px,
219
+ '6': 24px,
220
+ '8': 32px,
221
+ '10': 40px,
222
+ '12': 48px,
223
+ '14': 56px,
224
+ '16': 64px,
225
+ '20': 80px,
226
+ '24': 96px,
227
+ '32': 128px
228
+ );
229
+
230
+ // Add spacing function
231
+ @function spacing($key) {
232
+ @if not map.has-key($spacing-scale, $key) {
233
+ @error 'Spacing key "#{$key}" not found in spacing scale.';
234
+ }
235
+ @return map.get($spacing-scale, $key);
236
+ }
237
+
210
238
  // Helper functions for easier token access
211
239
  @function state($key) {
212
240
  @return map.get($state, $key);