mtrl 0.1.2 → 0.2.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.
- package/README.md +70 -22
- package/index.ts +33 -0
- package/package.json +14 -5
- package/src/components/button/{styles.scss → _styles.scss} +2 -2
- package/src/components/button/api.ts +89 -0
- package/src/components/button/button.ts +50 -0
- package/src/components/button/config.ts +75 -0
- package/src/components/button/constants.ts +17 -0
- package/src/components/button/index.ts +4 -0
- package/src/components/button/types.ts +118 -0
- package/src/components/card/_styles.scss +359 -0
- package/src/components/card/actions.ts +48 -0
- package/src/components/card/api.ts +102 -0
- package/src/components/card/card.ts +41 -0
- package/src/components/card/config.ts +99 -0
- package/src/components/card/constants.ts +69 -0
- package/src/components/card/content.ts +48 -0
- package/src/components/card/features.ts +228 -0
- package/src/components/card/header.ts +88 -0
- package/src/components/card/index.ts +19 -0
- package/src/components/card/media.ts +52 -0
- package/src/components/card/types.ts +174 -0
- package/src/components/checkbox/api.ts +82 -0
- package/src/components/checkbox/checkbox.ts +75 -0
- package/src/components/checkbox/config.ts +90 -0
- package/src/components/checkbox/index.ts +4 -0
- package/src/components/checkbox/types.ts +146 -0
- package/src/components/chip/_styles.scss +372 -0
- package/src/components/chip/api.ts +115 -0
- package/src/components/chip/chip-set.ts +225 -0
- package/src/components/chip/chip.ts +82 -0
- package/src/components/chip/config.ts +92 -0
- package/src/components/chip/constants.ts +38 -0
- package/src/components/chip/index.ts +4 -0
- package/src/components/chip/types.ts +172 -0
- package/src/components/list/api.ts +72 -0
- package/src/components/list/config.ts +43 -0
- package/src/components/list/{constants.js → constants.ts} +34 -7
- package/src/components/list/features.ts +224 -0
- package/src/components/list/index.ts +14 -0
- package/src/components/list/list-item.ts +120 -0
- package/src/components/list/list.ts +37 -0
- package/src/components/list/types.ts +179 -0
- package/src/components/list/utils.ts +47 -0
- package/src/components/menu/api.ts +119 -0
- package/src/components/menu/config.ts +54 -0
- package/src/components/menu/constants.ts +154 -0
- package/src/components/menu/features/items-manager.ts +457 -0
- package/src/components/menu/features/keyboard-navigation.ts +133 -0
- package/src/components/menu/features/positioning.ts +127 -0
- package/src/components/menu/features/{visibility.js → visibility.ts} +66 -64
- package/src/components/menu/index.ts +14 -0
- package/src/components/menu/menu-item.ts +43 -0
- package/src/components/menu/menu.ts +53 -0
- package/src/components/menu/types.ts +178 -0
- package/src/components/navigation/api.ts +79 -0
- package/src/components/navigation/config.ts +61 -0
- package/src/components/navigation/{constants.js → constants.ts} +10 -10
- package/src/components/navigation/index.ts +14 -0
- package/src/components/navigation/nav-item.ts +148 -0
- package/src/components/navigation/navigation.ts +50 -0
- package/src/components/navigation/types.ts +212 -0
- package/src/components/progress/_styles.scss +204 -0
- package/src/components/progress/api.ts +179 -0
- package/src/components/progress/config.ts +124 -0
- package/src/components/progress/constants.ts +43 -0
- package/src/components/progress/index.ts +5 -0
- package/src/components/progress/progress.ts +163 -0
- package/src/components/progress/types.ts +102 -0
- package/src/components/snackbar/api.ts +162 -0
- package/src/components/snackbar/config.ts +62 -0
- package/src/components/snackbar/{constants.js → constants.ts} +21 -4
- package/src/components/snackbar/features.ts +76 -0
- package/src/components/snackbar/index.ts +4 -0
- package/src/components/snackbar/position.ts +71 -0
- package/src/components/snackbar/queue.ts +76 -0
- package/src/components/snackbar/snackbar.ts +60 -0
- package/src/components/snackbar/types.ts +58 -0
- package/src/components/switch/api.ts +77 -0
- package/src/components/switch/config.ts +74 -0
- package/src/components/switch/index.ts +4 -0
- package/src/components/switch/switch.ts +52 -0
- package/src/components/switch/types.ts +142 -0
- package/src/components/textfield/api.ts +72 -0
- package/src/components/textfield/config.ts +54 -0
- package/src/components/textfield/{constants.js → constants.ts} +38 -5
- package/src/components/textfield/index.ts +4 -0
- package/src/components/textfield/textfield.ts +50 -0
- package/src/components/textfield/types.ts +139 -0
- package/src/core/compose/base.ts +43 -0
- package/src/core/compose/component.ts +247 -0
- package/src/core/compose/features/checkable.ts +155 -0
- package/src/core/compose/features/disabled.ts +116 -0
- package/src/core/compose/features/events.ts +65 -0
- package/src/core/compose/features/icon.ts +67 -0
- package/src/core/compose/features/index.ts +35 -0
- package/src/core/compose/features/input.ts +174 -0
- package/src/core/compose/features/lifecycle.ts +139 -0
- package/src/core/compose/features/position.ts +94 -0
- package/src/core/compose/features/ripple.ts +55 -0
- package/src/core/compose/features/size.ts +29 -0
- package/src/core/compose/features/style.ts +31 -0
- package/src/core/compose/features/text.ts +44 -0
- package/src/core/compose/features/textinput.ts +225 -0
- package/src/core/compose/features/textlabel.ts +92 -0
- package/src/core/compose/features/track.ts +84 -0
- package/src/core/compose/features/variant.ts +29 -0
- package/src/core/compose/features/withEvents.ts +137 -0
- package/src/core/compose/index.ts +54 -0
- package/src/core/compose/{pipe.js → pipe.ts} +16 -11
- package/src/core/config/component-config.ts +136 -0
- package/src/core/config.ts +211 -0
- package/src/core/dom/{attributes.js → attributes.ts} +11 -11
- package/src/core/dom/classes.ts +60 -0
- package/src/core/dom/create.ts +188 -0
- package/src/core/dom/events.ts +209 -0
- package/src/core/dom/index.ts +10 -0
- package/src/core/dom/utils.ts +97 -0
- package/src/core/index.ts +111 -0
- package/src/core/state/disabled.ts +81 -0
- package/src/core/state/emitter.ts +94 -0
- package/src/core/state/events.ts +88 -0
- package/src/core/state/index.ts +16 -0
- package/src/core/state/lifecycle.ts +131 -0
- package/src/core/state/store.ts +197 -0
- package/src/core/utils/index.ts +45 -0
- package/src/core/utils/{mobile.js → mobile.ts} +48 -24
- package/src/core/utils/object.ts +41 -0
- package/src/core/utils/validate.ts +234 -0
- package/src/{index.js → index.ts} +4 -2
- package/index.js +0 -11
- package/src/components/button/api.js +0 -54
- package/src/components/button/button.js +0 -81
- package/src/components/button/config.js +0 -10
- package/src/components/button/constants.js +0 -63
- package/src/components/button/index.js +0 -2
- package/src/components/checkbox/api.js +0 -45
- package/src/components/checkbox/checkbox.js +0 -96
- package/src/components/checkbox/index.js +0 -2
- package/src/components/container/api.js +0 -42
- package/src/components/container/container.js +0 -45
- package/src/components/container/index.js +0 -2
- package/src/components/container/styles.scss +0 -66
- package/src/components/list/index.js +0 -2
- package/src/components/list/list-item.js +0 -147
- package/src/components/list/list.js +0 -267
- package/src/components/menu/api.js +0 -117
- package/src/components/menu/constants.js +0 -42
- package/src/components/menu/features/items-manager.js +0 -375
- package/src/components/menu/features/keyboard-navigation.js +0 -129
- package/src/components/menu/features/positioning.js +0 -125
- package/src/components/menu/index.js +0 -2
- package/src/components/menu/menu-item.js +0 -41
- package/src/components/menu/menu.js +0 -54
- package/src/components/navigation/api.js +0 -43
- package/src/components/navigation/index.js +0 -2
- package/src/components/navigation/nav-item.js +0 -137
- package/src/components/navigation/navigation.js +0 -55
- package/src/components/snackbar/api.js +0 -125
- package/src/components/snackbar/features.js +0 -69
- package/src/components/snackbar/index.js +0 -2
- package/src/components/snackbar/position.js +0 -63
- package/src/components/snackbar/queue.js +0 -74
- package/src/components/snackbar/snackbar.js +0 -70
- package/src/components/switch/api.js +0 -44
- package/src/components/switch/index.js +0 -2
- package/src/components/switch/switch.js +0 -71
- package/src/components/textfield/api.js +0 -49
- package/src/components/textfield/index.js +0 -2
- package/src/components/textfield/textfield.js +0 -68
- package/src/core/build/_ripple.scss +0 -79
- package/src/core/build/constants.js +0 -51
- package/src/core/build/icon.js +0 -78
- package/src/core/build/ripple.js +0 -159
- package/src/core/build/text.js +0 -54
- package/src/core/compose/base.js +0 -8
- package/src/core/compose/component.js +0 -225
- package/src/core/compose/features/checkable.js +0 -114
- package/src/core/compose/features/disabled.js +0 -64
- package/src/core/compose/features/events.js +0 -48
- package/src/core/compose/features/icon.js +0 -33
- package/src/core/compose/features/index.js +0 -20
- package/src/core/compose/features/input.js +0 -100
- package/src/core/compose/features/lifecycle.js +0 -69
- package/src/core/compose/features/position.js +0 -60
- package/src/core/compose/features/ripple.js +0 -32
- package/src/core/compose/features/size.js +0 -9
- package/src/core/compose/features/style.js +0 -12
- package/src/core/compose/features/text.js +0 -17
- package/src/core/compose/features/textinput.js +0 -114
- package/src/core/compose/features/textlabel.js +0 -28
- package/src/core/compose/features/track.js +0 -49
- package/src/core/compose/features/variant.js +0 -9
- package/src/core/compose/features/withEvents.js +0 -67
- package/src/core/compose/index.js +0 -16
- package/src/core/config.js +0 -140
- package/src/core/dom/classes.js +0 -70
- package/src/core/dom/create.js +0 -132
- package/src/core/dom/events.js +0 -175
- package/src/core/dom/index.js +0 -5
- package/src/core/dom/utils.js +0 -22
- package/src/core/index.js +0 -23
- package/src/core/state/disabled.js +0 -51
- package/src/core/state/emitter.js +0 -63
- package/src/core/state/events.js +0 -29
- package/src/core/state/index.js +0 -6
- package/src/core/state/lifecycle.js +0 -64
- package/src/core/state/store.js +0 -112
- package/src/core/utils/index.js +0 -39
- package/src/core/utils/object.js +0 -22
- package/src/core/utils/validate.js +0 -37
- /package/src/components/checkbox/{styles.scss → _styles.scss} +0 -0
- /package/src/components/checkbox/{constants.js → constants.ts} +0 -0
- /package/src/components/list/{styles.scss → _styles.scss} +0 -0
- /package/src/components/menu/{styles.scss → _styles.scss} +0 -0
- /package/src/components/navigation/{styles.scss → _styles.scss} +0 -0
- /package/src/components/snackbar/{styles.scss → _styles.scss} +0 -0
- /package/src/components/switch/{styles.scss → _styles.scss} +0 -0
- /package/src/components/switch/{constants.js → constants.ts} +0 -0
- /package/src/components/textfield/{styles.scss → _styles.scss} +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/components/list/list-item.ts
|
|
2
|
+
import { PREFIX } from '../../core/config';
|
|
3
|
+
import { pipe } from '../../core/compose';
|
|
4
|
+
import { createBase, withElement } from '../../core/compose/component';
|
|
5
|
+
import { withEvents, withDisabled } from '../../core/compose/features';
|
|
6
|
+
import { ListItemConfig } from './types';
|
|
7
|
+
import { LIST_ITEM_LAYOUTS, LIST_CLASSES } from './constants';
|
|
8
|
+
import { createElement } from './utils';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates list item content based on configuration
|
|
12
|
+
* @param {BaseComponent} component - Component to enhance
|
|
13
|
+
* @param {ListItemConfig} config - List item configuration
|
|
14
|
+
* @returns {BaseComponent} Enhanced component
|
|
15
|
+
*/
|
|
16
|
+
const withItemContent = (config: ListItemConfig) => (component: any): any => {
|
|
17
|
+
const { element } = component;
|
|
18
|
+
const prefix = config.prefix || PREFIX;
|
|
19
|
+
const isVertical = config.layout === LIST_ITEM_LAYOUTS.VERTICAL;
|
|
20
|
+
|
|
21
|
+
// Create content container
|
|
22
|
+
const content = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_CONTENT}`);
|
|
23
|
+
|
|
24
|
+
// Add leading content (icon/avatar)
|
|
25
|
+
if (config.leading) {
|
|
26
|
+
const leading = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_LEADING}`);
|
|
27
|
+
if (typeof config.leading === 'string') {
|
|
28
|
+
leading.innerHTML = config.leading;
|
|
29
|
+
} else {
|
|
30
|
+
leading.appendChild(config.leading);
|
|
31
|
+
}
|
|
32
|
+
element.appendChild(leading);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Text wrapper for proper alignment
|
|
36
|
+
const textWrapper = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_TEXT}`);
|
|
37
|
+
|
|
38
|
+
// Add overline text (vertical only)
|
|
39
|
+
if (isVertical && config.overline) {
|
|
40
|
+
const overline = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_OVERLINE}`, config.overline);
|
|
41
|
+
textWrapper.appendChild(overline);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Add headline (primary text)
|
|
45
|
+
if (config.headline) {
|
|
46
|
+
const headline = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_HEADLINE}`, config.headline);
|
|
47
|
+
textWrapper.appendChild(headline);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add supporting text (secondary text)
|
|
51
|
+
if (config.supportingText) {
|
|
52
|
+
const supporting = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_SUPPORTING}`, config.supportingText);
|
|
53
|
+
textWrapper.appendChild(supporting);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
content.appendChild(textWrapper);
|
|
57
|
+
|
|
58
|
+
// Add meta information (vertical only)
|
|
59
|
+
if (isVertical && config.meta) {
|
|
60
|
+
const meta = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_META}`);
|
|
61
|
+
if (typeof config.meta === 'string') {
|
|
62
|
+
meta.textContent = config.meta;
|
|
63
|
+
} else {
|
|
64
|
+
meta.appendChild(config.meta);
|
|
65
|
+
}
|
|
66
|
+
content.appendChild(meta);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
element.appendChild(content);
|
|
70
|
+
|
|
71
|
+
// Add trailing content (icon/meta)
|
|
72
|
+
if (config.trailing) {
|
|
73
|
+
const trailing = createElement('div', `${prefix}-${LIST_CLASSES.ITEM_TRAILING}`);
|
|
74
|
+
if (typeof config.trailing === 'string') {
|
|
75
|
+
trailing.innerHTML = config.trailing;
|
|
76
|
+
} else {
|
|
77
|
+
trailing.appendChild(config.trailing);
|
|
78
|
+
}
|
|
79
|
+
element.appendChild(trailing);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Handle selected state
|
|
83
|
+
if (config.selected) {
|
|
84
|
+
element.setAttribute('aria-selected', 'true');
|
|
85
|
+
element.classList.add(`${prefix}-${LIST_CLASSES.ITEM}--selected`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return component;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Creates a list item component
|
|
93
|
+
* @param {ListItemConfig} config - List item configuration
|
|
94
|
+
* @returns {Object} List item component instance
|
|
95
|
+
*/
|
|
96
|
+
const createListItem = (config: ListItemConfig): any => {
|
|
97
|
+
const baseConfig = {
|
|
98
|
+
...config,
|
|
99
|
+
componentName: 'list-item',
|
|
100
|
+
prefix: PREFIX
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const layoutClass = config.layout === LIST_ITEM_LAYOUTS.VERTICAL ? 'vertical' : '';
|
|
104
|
+
const combinedClass = `${layoutClass} ${config.class || ''}`.trim();
|
|
105
|
+
|
|
106
|
+
return pipe(
|
|
107
|
+
createBase,
|
|
108
|
+
withEvents(),
|
|
109
|
+
withElement({
|
|
110
|
+
tag: 'div',
|
|
111
|
+
role: config.role || 'listitem',
|
|
112
|
+
componentName: 'list-item',
|
|
113
|
+
className: combinedClass
|
|
114
|
+
}),
|
|
115
|
+
withDisabled(baseConfig),
|
|
116
|
+
withItemContent(baseConfig)
|
|
117
|
+
)(baseConfig);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export default createListItem;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/components/list/list.ts
|
|
2
|
+
import { pipe } from '../../core/compose';
|
|
3
|
+
import { createBase, withElement } from '../../core/compose/component';
|
|
4
|
+
import { withEvents, withDisabled, withLifecycle } from '../../core/compose/features';
|
|
5
|
+
import { withAPI } from './api';
|
|
6
|
+
import { withListContent } from './features';
|
|
7
|
+
import { ListConfig } from './types';
|
|
8
|
+
import { createBaseConfig, getElementConfig } from './config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a List component
|
|
12
|
+
* @param {ListConfig} config - List configuration
|
|
13
|
+
* @returns {Object} List component instance
|
|
14
|
+
*/
|
|
15
|
+
const createList = (config: ListConfig = {}): any => {
|
|
16
|
+
const baseConfig = createBaseConfig(config);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
return pipe(
|
|
20
|
+
createBase,
|
|
21
|
+
withEvents(),
|
|
22
|
+
withElement(getElementConfig(baseConfig)),
|
|
23
|
+
withDisabled(baseConfig),
|
|
24
|
+
withLifecycle(),
|
|
25
|
+
withListContent(baseConfig),
|
|
26
|
+
comp => withAPI({
|
|
27
|
+
disabled: comp.disabled,
|
|
28
|
+
lifecycle: comp.lifecycle
|
|
29
|
+
})(comp)
|
|
30
|
+
)(baseConfig);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('List creation error:', error instanceof Error ? error.message : String(error));
|
|
33
|
+
throw new Error(`Failed to create list: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default createList;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/components/list/types.ts
|
|
2
|
+
import { LIST_TYPES, LIST_LAYOUTS } from './constants';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* List item configuration
|
|
6
|
+
*/
|
|
7
|
+
export interface ListItemConfig {
|
|
8
|
+
/** Unique identifier for the item */
|
|
9
|
+
id: string;
|
|
10
|
+
|
|
11
|
+
/** Item layout (horizontal/vertical) */
|
|
12
|
+
layout?: keyof typeof LIST_LAYOUTS | string;
|
|
13
|
+
|
|
14
|
+
/** Leading content (icon/avatar) */
|
|
15
|
+
leading?: string | HTMLElement;
|
|
16
|
+
|
|
17
|
+
/** Primary text */
|
|
18
|
+
headline?: string;
|
|
19
|
+
|
|
20
|
+
/** Secondary text */
|
|
21
|
+
supportingText?: string;
|
|
22
|
+
|
|
23
|
+
/** Trailing content (icon/meta) */
|
|
24
|
+
trailing?: string | HTMLElement;
|
|
25
|
+
|
|
26
|
+
/** Text above headline (vertical only) */
|
|
27
|
+
overline?: string;
|
|
28
|
+
|
|
29
|
+
/** Meta information (vertical only) */
|
|
30
|
+
meta?: string | HTMLElement;
|
|
31
|
+
|
|
32
|
+
/** Whether the item is disabled */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
|
|
35
|
+
/** Whether the item is selected */
|
|
36
|
+
selected?: boolean;
|
|
37
|
+
|
|
38
|
+
/** Additional CSS classes */
|
|
39
|
+
class?: string;
|
|
40
|
+
|
|
41
|
+
/** ARIA role */
|
|
42
|
+
role?: string;
|
|
43
|
+
|
|
44
|
+
/** Whether this is a divider instead of an item */
|
|
45
|
+
divider?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* List section configuration
|
|
50
|
+
*/
|
|
51
|
+
export interface ListSectionConfig {
|
|
52
|
+
/** Unique identifier for the section */
|
|
53
|
+
id: string;
|
|
54
|
+
|
|
55
|
+
/** Section title text */
|
|
56
|
+
title: string;
|
|
57
|
+
|
|
58
|
+
/** Items in this section */
|
|
59
|
+
items: ListItemConfig[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Selection change event data
|
|
64
|
+
*/
|
|
65
|
+
export interface SelectionChangeEvent {
|
|
66
|
+
/** Array of selected item IDs */
|
|
67
|
+
selected: string[];
|
|
68
|
+
|
|
69
|
+
/** The item that was clicked */
|
|
70
|
+
item?: ListItemData;
|
|
71
|
+
|
|
72
|
+
/** List selection type */
|
|
73
|
+
type: keyof typeof LIST_TYPES | string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* List item data
|
|
78
|
+
*/
|
|
79
|
+
export interface ListItemData {
|
|
80
|
+
/** The item's DOM element */
|
|
81
|
+
element: HTMLElement;
|
|
82
|
+
|
|
83
|
+
/** Whether the item is disabled */
|
|
84
|
+
disabled?: boolean;
|
|
85
|
+
|
|
86
|
+
/** Internal properties */
|
|
87
|
+
[key: string]: any;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Configuration interface for the List component
|
|
92
|
+
*/
|
|
93
|
+
export interface ListConfig {
|
|
94
|
+
/** List selection type */
|
|
95
|
+
type?: keyof typeof LIST_TYPES | string;
|
|
96
|
+
|
|
97
|
+
/** List layout */
|
|
98
|
+
layout?: keyof typeof LIST_LAYOUTS | string;
|
|
99
|
+
|
|
100
|
+
/** List items */
|
|
101
|
+
items?: ListItemConfig[];
|
|
102
|
+
|
|
103
|
+
/** List sections */
|
|
104
|
+
sections?: ListSectionConfig[];
|
|
105
|
+
|
|
106
|
+
/** Whether the list is disabled */
|
|
107
|
+
disabled?: boolean;
|
|
108
|
+
|
|
109
|
+
/** Additional CSS classes */
|
|
110
|
+
class?: string;
|
|
111
|
+
|
|
112
|
+
/** Prefix for class names */
|
|
113
|
+
prefix?: string;
|
|
114
|
+
|
|
115
|
+
/** Component name */
|
|
116
|
+
componentName?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* List component interface
|
|
121
|
+
*/
|
|
122
|
+
export interface ListComponent {
|
|
123
|
+
/** The root element of the list */
|
|
124
|
+
element: HTMLElement;
|
|
125
|
+
|
|
126
|
+
/** Map of list items */
|
|
127
|
+
items: Map<string, ListItemData>;
|
|
128
|
+
|
|
129
|
+
/** Set of selected item IDs */
|
|
130
|
+
selectedItems: Set<string>;
|
|
131
|
+
|
|
132
|
+
/** Gets the currently selected items */
|
|
133
|
+
getSelected: () => string[];
|
|
134
|
+
|
|
135
|
+
/** Sets the selected items */
|
|
136
|
+
setSelected: (ids: string[]) => void;
|
|
137
|
+
|
|
138
|
+
/** Adds a new item to the list */
|
|
139
|
+
addItem: (itemConfig: ListItemConfig) => void;
|
|
140
|
+
|
|
141
|
+
/** Removes an item from the list */
|
|
142
|
+
removeItem: (id: string) => void;
|
|
143
|
+
|
|
144
|
+
/** Adds event listener */
|
|
145
|
+
on: (event: string, handler: Function) => ListComponent;
|
|
146
|
+
|
|
147
|
+
/** Removes event listener */
|
|
148
|
+
off: (event: string, handler: Function) => ListComponent;
|
|
149
|
+
|
|
150
|
+
/** Enables the list */
|
|
151
|
+
enable: () => ListComponent;
|
|
152
|
+
|
|
153
|
+
/** Disables the list */
|
|
154
|
+
disable: () => ListComponent;
|
|
155
|
+
|
|
156
|
+
/** Destroys the list component and cleans up resources */
|
|
157
|
+
destroy: () => void;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Base component interface
|
|
162
|
+
*/
|
|
163
|
+
export interface BaseComponent {
|
|
164
|
+
element: HTMLElement;
|
|
165
|
+
prefix?: string;
|
|
166
|
+
items?: Map<string, ListItemData>;
|
|
167
|
+
selectedItems?: Set<string>;
|
|
168
|
+
emit?: (event: string, data: any) => void;
|
|
169
|
+
on?: (event: string, handler: Function) => any;
|
|
170
|
+
off?: (event: string, handler: Function) => any;
|
|
171
|
+
lifecycle?: {
|
|
172
|
+
destroy: () => void;
|
|
173
|
+
};
|
|
174
|
+
disabled?: {
|
|
175
|
+
enable: () => any;
|
|
176
|
+
disable: () => any;
|
|
177
|
+
};
|
|
178
|
+
[key: string]: any;
|
|
179
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/components/list/utils.ts
|
|
2
|
+
import { LIST_CLASSES } from './constants';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a divider element
|
|
6
|
+
* @param {string} prefix - CSS class prefix
|
|
7
|
+
* @returns {HTMLElement} Divider element
|
|
8
|
+
*/
|
|
9
|
+
export const createDivider = (prefix: string): HTMLElement => {
|
|
10
|
+
const divider = document.createElement('div');
|
|
11
|
+
divider.className = `${prefix}-${LIST_CLASSES.DIVIDER}`;
|
|
12
|
+
divider.setAttribute('role', 'separator');
|
|
13
|
+
return divider;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a section title element
|
|
18
|
+
* @param {string} title - Section title text
|
|
19
|
+
* @param {string} prefix - CSS class prefix
|
|
20
|
+
* @returns {HTMLElement} Section title element
|
|
21
|
+
*/
|
|
22
|
+
export const createSectionTitle = (title: string, prefix: string): HTMLElement => {
|
|
23
|
+
const titleEl = document.createElement('div');
|
|
24
|
+
titleEl.className = `${prefix}-${LIST_CLASSES.SECTION_TITLE}`;
|
|
25
|
+
titleEl.textContent = title;
|
|
26
|
+
return titleEl;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a DOM element with optional class and content
|
|
31
|
+
* @param {string} tag - HTML tag name
|
|
32
|
+
* @param {string} className - CSS class name
|
|
33
|
+
* @param {string|HTMLElement} [content] - Element content or child element
|
|
34
|
+
* @returns {HTMLElement} Created element
|
|
35
|
+
*/
|
|
36
|
+
export const createElement = (tag: string, className: string, content?: string | HTMLElement): HTMLElement => {
|
|
37
|
+
const element = document.createElement(tag);
|
|
38
|
+
element.className = className;
|
|
39
|
+
if (content) {
|
|
40
|
+
if (typeof content === 'string') {
|
|
41
|
+
element.textContent = content;
|
|
42
|
+
} else {
|
|
43
|
+
element.appendChild(content);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return element;
|
|
47
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/components/menu/api.ts
|
|
2
|
+
import { ApiOptions, BaseComponent, MenuComponent, MenuItemConfig, MenuPositionConfig } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhances menu component with API methods
|
|
6
|
+
* @param {ApiOptions} options - API configuration
|
|
7
|
+
* @returns {Function} Higher-order function that adds API methods to component
|
|
8
|
+
*/
|
|
9
|
+
export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
10
|
+
(component: BaseComponent): MenuComponent => ({
|
|
11
|
+
...component as any,
|
|
12
|
+
element: component.element,
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Shows the menu
|
|
16
|
+
* @returns {MenuComponent} Component instance
|
|
17
|
+
*/
|
|
18
|
+
show(): MenuComponent {
|
|
19
|
+
component.show?.();
|
|
20
|
+
return this;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hides the menu
|
|
25
|
+
* @returns {MenuComponent} Component instance
|
|
26
|
+
*/
|
|
27
|
+
hide(): MenuComponent {
|
|
28
|
+
component.hide?.();
|
|
29
|
+
return this;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Positions the menu relative to a target
|
|
34
|
+
* @param {HTMLElement} target - Target element
|
|
35
|
+
* @param {MenuPositionConfig} options - Position options
|
|
36
|
+
* @returns {MenuComponent} Component instance
|
|
37
|
+
*/
|
|
38
|
+
position(target: HTMLElement, options?: MenuPositionConfig): MenuComponent {
|
|
39
|
+
component.position?.(target, options);
|
|
40
|
+
return this;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Adds an item to the menu
|
|
45
|
+
* @param {MenuItemConfig} config - Item configuration
|
|
46
|
+
* @returns {MenuComponent} Component instance
|
|
47
|
+
*/
|
|
48
|
+
addItem(config: MenuItemConfig): MenuComponent {
|
|
49
|
+
component.addItem?.(config);
|
|
50
|
+
return this;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Removes an item by name
|
|
55
|
+
* @param {string} name - Item name to remove
|
|
56
|
+
* @returns {MenuComponent} Component instance
|
|
57
|
+
*/
|
|
58
|
+
removeItem(name: string): MenuComponent {
|
|
59
|
+
component.removeItem?.(name);
|
|
60
|
+
return this;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets all registered items
|
|
65
|
+
* @returns {Map<string, any>} Map of item names to configurations
|
|
66
|
+
*/
|
|
67
|
+
getItems() {
|
|
68
|
+
return component.getItems?.() || new Map();
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Checks if the menu is currently visible
|
|
73
|
+
* @returns {boolean} Whether the menu is visible
|
|
74
|
+
*/
|
|
75
|
+
isVisible(): boolean {
|
|
76
|
+
return component.isVisible?.() || false;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Registers an event handler
|
|
81
|
+
* @param {string} event - Event name
|
|
82
|
+
* @param {Function} handler - Event handler
|
|
83
|
+
* @returns {MenuComponent} Component instance
|
|
84
|
+
*/
|
|
85
|
+
on(event: string, handler: Function): MenuComponent {
|
|
86
|
+
component.on?.(event, handler);
|
|
87
|
+
return this;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Unregisters an event handler
|
|
92
|
+
* @param {string} event - Event name
|
|
93
|
+
* @param {Function} handler - Event handler
|
|
94
|
+
* @returns {MenuComponent} Component instance
|
|
95
|
+
*/
|
|
96
|
+
off(event: string, handler: Function): MenuComponent {
|
|
97
|
+
component.off?.(event, handler);
|
|
98
|
+
return this;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Destroys the menu component and cleans up resources
|
|
103
|
+
* @returns {MenuComponent} Component instance
|
|
104
|
+
*/
|
|
105
|
+
destroy(): MenuComponent {
|
|
106
|
+
// First close any open submenus
|
|
107
|
+
component.hide?.();
|
|
108
|
+
|
|
109
|
+
// Then destroy the component
|
|
110
|
+
lifecycle.destroy?.();
|
|
111
|
+
|
|
112
|
+
// Final cleanup - forcibly remove from DOM if still attached
|
|
113
|
+
if (component.element && component.element.parentNode) {
|
|
114
|
+
component.element.remove();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/components/menu/config.ts
|
|
2
|
+
import { PREFIX } from '../../core/config';
|
|
3
|
+
import {
|
|
4
|
+
createComponentConfig,
|
|
5
|
+
createElementConfig,
|
|
6
|
+
BaseComponentConfig
|
|
7
|
+
} from '../../core/config/component-config';
|
|
8
|
+
import { MenuConfig, BaseComponent, ApiOptions } from './types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default configuration for the Menu component
|
|
12
|
+
*/
|
|
13
|
+
export const defaultConfig: MenuConfig = {
|
|
14
|
+
items: [],
|
|
15
|
+
stayOpenOnSelect: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates the base configuration for Menu component
|
|
20
|
+
* @param {MenuConfig} config - User provided configuration
|
|
21
|
+
* @returns {MenuConfig} Complete configuration with defaults applied
|
|
22
|
+
*/
|
|
23
|
+
export const createBaseConfig = (config: MenuConfig = {}): MenuConfig =>
|
|
24
|
+
createComponentConfig(defaultConfig, config, 'menu') as MenuConfig;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generates element configuration for the Menu component
|
|
28
|
+
* @param {MenuConfig} config - Menu configuration
|
|
29
|
+
* @returns {Object} Element configuration object for withElement
|
|
30
|
+
*/
|
|
31
|
+
export const getElementConfig = (config: MenuConfig) =>
|
|
32
|
+
createElementConfig(config, {
|
|
33
|
+
tag: 'div',
|
|
34
|
+
componentName: 'menu',
|
|
35
|
+
attrs: {
|
|
36
|
+
role: 'menu',
|
|
37
|
+
tabindex: '-1',
|
|
38
|
+
'aria-hidden': 'true'
|
|
39
|
+
},
|
|
40
|
+
className: config.class
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates API configuration for the Menu component
|
|
45
|
+
* @param {BaseComponent} comp - Component with lifecycle feature
|
|
46
|
+
* @returns {ApiOptions} API configuration object
|
|
47
|
+
*/
|
|
48
|
+
export const getApiConfig = (comp: BaseComponent): ApiOptions => ({
|
|
49
|
+
lifecycle: {
|
|
50
|
+
destroy: comp.lifecycle?.destroy || (() => {})
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export default defaultConfig;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/components/menu/constants.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Menu alignment options
|
|
5
|
+
*/
|
|
6
|
+
export const MENU_ALIGN = {
|
|
7
|
+
LEFT: 'left',
|
|
8
|
+
RIGHT: 'right',
|
|
9
|
+
CENTER: 'center'
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Menu vertical alignment options
|
|
14
|
+
*/
|
|
15
|
+
export const MENU_VERTICAL_ALIGN = {
|
|
16
|
+
TOP: 'top',
|
|
17
|
+
BOTTOM: 'bottom',
|
|
18
|
+
MIDDLE: 'middle'
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Menu item types
|
|
23
|
+
*/
|
|
24
|
+
export const MENU_ITEM_TYPES = {
|
|
25
|
+
ITEM: 'item',
|
|
26
|
+
DIVIDER: 'divider'
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Menu events
|
|
31
|
+
*/
|
|
32
|
+
export const MENU_EVENTS = {
|
|
33
|
+
SELECT: 'select',
|
|
34
|
+
OPEN: 'open',
|
|
35
|
+
CLOSE: 'close',
|
|
36
|
+
SUBMENU_OPEN: 'submenuOpen',
|
|
37
|
+
SUBMENU_CLOSE: 'submenuClose'
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Menu states
|
|
42
|
+
*/
|
|
43
|
+
export const MENU_STATES = {
|
|
44
|
+
VISIBLE: 'visible',
|
|
45
|
+
HIDDEN: 'hidden',
|
|
46
|
+
DISABLED: 'disabled'
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Menu element classes
|
|
51
|
+
*/
|
|
52
|
+
export const MENU_CLASSES = {
|
|
53
|
+
ROOT: 'menu',
|
|
54
|
+
ITEM: 'menu-item',
|
|
55
|
+
ITEM_CONTAINER: 'menu-item-container',
|
|
56
|
+
LIST: 'menu-list',
|
|
57
|
+
DIVIDER: 'menu-divider',
|
|
58
|
+
SUBMENU: 'menu--submenu'
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Menu positioning schema
|
|
63
|
+
* Used for validation in component creation
|
|
64
|
+
*/
|
|
65
|
+
export const MENU_POSITION_SCHEMA = {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
align: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
enum: Object.values(MENU_ALIGN),
|
|
71
|
+
optional: true,
|
|
72
|
+
default: MENU_ALIGN.LEFT
|
|
73
|
+
},
|
|
74
|
+
vAlign: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
enum: Object.values(MENU_VERTICAL_ALIGN),
|
|
77
|
+
optional: true,
|
|
78
|
+
default: MENU_VERTICAL_ALIGN.BOTTOM
|
|
79
|
+
},
|
|
80
|
+
offsetX: {
|
|
81
|
+
type: 'number',
|
|
82
|
+
optional: true,
|
|
83
|
+
default: 0
|
|
84
|
+
},
|
|
85
|
+
offsetY: {
|
|
86
|
+
type: 'number',
|
|
87
|
+
optional: true,
|
|
88
|
+
default: 0
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} as const;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Validation schema for menu configuration
|
|
95
|
+
*/
|
|
96
|
+
export const MENU_SCHEMA = {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {
|
|
99
|
+
items: {
|
|
100
|
+
type: 'array',
|
|
101
|
+
optional: true,
|
|
102
|
+
default: []
|
|
103
|
+
},
|
|
104
|
+
class: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
optional: true
|
|
107
|
+
},
|
|
108
|
+
stayOpenOnSelect: {
|
|
109
|
+
type: 'boolean',
|
|
110
|
+
optional: true,
|
|
111
|
+
default: false
|
|
112
|
+
},
|
|
113
|
+
openingButton: {
|
|
114
|
+
optional: true
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} as const;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Menu item schema
|
|
121
|
+
*/
|
|
122
|
+
export const MENU_ITEM_SCHEMA = {
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: {
|
|
125
|
+
name: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
required: true
|
|
128
|
+
},
|
|
129
|
+
text: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
required: true
|
|
132
|
+
},
|
|
133
|
+
type: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
enum: Object.values(MENU_ITEM_TYPES),
|
|
136
|
+
optional: true,
|
|
137
|
+
default: MENU_ITEM_TYPES.ITEM
|
|
138
|
+
},
|
|
139
|
+
disabled: {
|
|
140
|
+
type: 'boolean',
|
|
141
|
+
optional: true,
|
|
142
|
+
default: false
|
|
143
|
+
},
|
|
144
|
+
class: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
optional: true
|
|
147
|
+
},
|
|
148
|
+
items: {
|
|
149
|
+
type: 'array',
|
|
150
|
+
optional: true,
|
|
151
|
+
description: 'Submenu items'
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} as const;
|