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
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// src/components/container/container.js
|
|
2
|
-
import { PREFIX } from '../../core/config'
|
|
3
|
-
import { pipe } from '../../core/compose'
|
|
4
|
-
import { createBase, withElement } from '../../core/compose/component'
|
|
5
|
-
import { withEvents, withLifecycle } from '../../core/compose/features'
|
|
6
|
-
import { withAPI } from './api'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Creates a new Container component
|
|
10
|
-
* @param {Object} config - Container configuration
|
|
11
|
-
* @param {string} [config.variant] - Visual variant
|
|
12
|
-
* @param {number} [config.elevation] - Elevation level
|
|
13
|
-
* @param {string} [config.class] - Additional CSS classes
|
|
14
|
-
*/
|
|
15
|
-
const createContainer = (config = {}) => {
|
|
16
|
-
const baseConfig = {
|
|
17
|
-
...config,
|
|
18
|
-
componentName: 'container',
|
|
19
|
-
prefix: PREFIX
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
return pipe(
|
|
24
|
-
createBase,
|
|
25
|
-
withElement({
|
|
26
|
-
tag: 'div',
|
|
27
|
-
componentName: 'container',
|
|
28
|
-
className: [
|
|
29
|
-
config.variant && `${PREFIX}-container--${config.variant}`,
|
|
30
|
-
config.elevation && `${PREFIX}-container--elevation-${config.elevation}`,
|
|
31
|
-
config.class
|
|
32
|
-
]
|
|
33
|
-
}),
|
|
34
|
-
withEvents(),
|
|
35
|
-
withLifecycle(),
|
|
36
|
-
comp => withAPI({
|
|
37
|
-
lifecycle: comp.lifecycle
|
|
38
|
-
})(comp)
|
|
39
|
-
)(baseConfig)
|
|
40
|
-
} catch (error) {
|
|
41
|
-
throw new Error(`Failed to create container: ${error.message}`)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export default createContainer
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// src/components/container/_container.scss
|
|
2
|
-
@use '../../styles/abstract/base' as base;
|
|
3
|
-
@use '../../styles/abstract/variables' as v;
|
|
4
|
-
@use '../../styles/abstract/functions' as f;
|
|
5
|
-
@use '../../styles/abstract/mixins' as m;
|
|
6
|
-
@use '../../styles/abstract/theme' as t;
|
|
7
|
-
|
|
8
|
-
$component: '#{base.$prefix}-container';
|
|
9
|
-
|
|
10
|
-
.#{$component} {
|
|
11
|
-
@include m.shape('medium');
|
|
12
|
-
@include m.motion-transition(
|
|
13
|
-
background-color,
|
|
14
|
-
box-shadow
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
padding: 16px;
|
|
18
|
-
background-color: t.color('surface-container');
|
|
19
|
-
|
|
20
|
-
// Surface container variants
|
|
21
|
-
&--low {
|
|
22
|
-
background-color: t.color('surface-container-low');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
&--lowest {
|
|
26
|
-
background-color: t.color('surface-container-lowest');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
&--high {
|
|
30
|
-
background-color: t.color('surface-container-high');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
&--highest {
|
|
34
|
-
background-color: t.color('surface-container-highest');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Elevation variants
|
|
38
|
-
&--elevation-0 {
|
|
39
|
-
@include m.elevation(0);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
&--elevation-1 {
|
|
43
|
-
@include m.elevation(1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
&--elevation-2 {
|
|
47
|
-
@include m.elevation(2);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
&--elevation-3 {
|
|
51
|
-
@include m.elevation(3);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
&--elevation-4 {
|
|
55
|
-
@include m.elevation(4);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Accessibility
|
|
59
|
-
@include m.reduced-motion {
|
|
60
|
-
transition: none;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
@include m.high-contrast {
|
|
64
|
-
border: 1px solid currentColor;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
// src/components/list/list-item.js
|
|
2
|
-
|
|
3
|
-
import { PREFIX } from '../../core/config'
|
|
4
|
-
import { pipe } from '../../core/compose'
|
|
5
|
-
import { createBase, withElement } from '../../core/compose/component'
|
|
6
|
-
import { withEvents, withDisabled } from '../../core/compose/features'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Supported list item layouts
|
|
10
|
-
*/
|
|
11
|
-
export const LIST_ITEM_LAYOUTS = {
|
|
12
|
-
HORIZONTAL: 'horizontal', // Default horizontal layout
|
|
13
|
-
VERTICAL: 'vertical' // Stacked layout with vertical alignment
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Creates a DOM element with optional class and content
|
|
18
|
-
* @param {string} tag - HTML tag name
|
|
19
|
-
* @param {string} className - CSS class name
|
|
20
|
-
* @param {string|HTMLElement} [content] - Element content or child element
|
|
21
|
-
* @returns {HTMLElement} Created element
|
|
22
|
-
*/
|
|
23
|
-
const createElement = (tag, className, content) => {
|
|
24
|
-
const element = document.createElement(tag)
|
|
25
|
-
element.className = className
|
|
26
|
-
if (content) {
|
|
27
|
-
if (typeof content === 'string') {
|
|
28
|
-
element.textContent = content
|
|
29
|
-
} else {
|
|
30
|
-
element.appendChild(content)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return element
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Creates a list item component
|
|
38
|
-
* @param {Object} config - List item configuration
|
|
39
|
-
* @param {string} [config.layout='horizontal'] - Item layout (horizontal/vertical)
|
|
40
|
-
* @param {string|HTMLElement} [config.leading] - Leading content (icon/avatar)
|
|
41
|
-
* @param {string} [config.headline] - Primary text
|
|
42
|
-
* @param {string} [config.supportingText] - Secondary text
|
|
43
|
-
* @param {string|HTMLElement} [config.trailing] - Trailing content (icon/meta)
|
|
44
|
-
* @param {string} [config.overline] - Text above headline (vertical only)
|
|
45
|
-
* @param {string|HTMLElement} [config.meta] - Meta information (vertical only)
|
|
46
|
-
* @param {boolean} [config.disabled] - Disabled state
|
|
47
|
-
* @param {boolean} [config.selected] - Selected state
|
|
48
|
-
* @param {string} [config.class] - Additional CSS classes
|
|
49
|
-
* @param {string} [config.role='listitem'] - ARIA role
|
|
50
|
-
*/
|
|
51
|
-
const createListItem = (config = {}) => {
|
|
52
|
-
const baseConfig = {
|
|
53
|
-
...config,
|
|
54
|
-
componentName: 'list-item',
|
|
55
|
-
prefix: PREFIX
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const createContent = (component) => {
|
|
59
|
-
const { element } = component
|
|
60
|
-
const { prefix } = baseConfig
|
|
61
|
-
const isVertical = config.layout === LIST_ITEM_LAYOUTS.VERTICAL
|
|
62
|
-
|
|
63
|
-
// Create content container
|
|
64
|
-
const content = createElement('div', `${prefix}-list-item-content`)
|
|
65
|
-
|
|
66
|
-
// Add leading content (icon/avatar)
|
|
67
|
-
if (config.leading) {
|
|
68
|
-
const leading = createElement('div', `${prefix}-list-item-leading`)
|
|
69
|
-
if (typeof config.leading === 'string') {
|
|
70
|
-
leading.innerHTML = config.leading
|
|
71
|
-
} else {
|
|
72
|
-
leading.appendChild(config.leading)
|
|
73
|
-
}
|
|
74
|
-
element.appendChild(leading)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Text wrapper for proper alignment
|
|
78
|
-
const textWrapper = createElement('div', `${prefix}-list-item-text`)
|
|
79
|
-
|
|
80
|
-
// Add overline text (vertical only)
|
|
81
|
-
if (isVertical && config.overline) {
|
|
82
|
-
const overline = createElement('div', `${prefix}-list-item-overline`, config.overline)
|
|
83
|
-
textWrapper.appendChild(overline)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Add headline (primary text)
|
|
87
|
-
if (config.headline) {
|
|
88
|
-
const headline = createElement('div', `${prefix}-list-item-headline`, config.headline)
|
|
89
|
-
textWrapper.appendChild(headline)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Add supporting text (secondary text)
|
|
93
|
-
if (config.supportingText) {
|
|
94
|
-
const supporting = createElement('div', `${prefix}-list-item-supporting`, config.supportingText)
|
|
95
|
-
textWrapper.appendChild(supporting)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
content.appendChild(textWrapper)
|
|
99
|
-
|
|
100
|
-
// Add meta information (vertical only)
|
|
101
|
-
if (isVertical && config.meta) {
|
|
102
|
-
const meta = createElement('div', `${prefix}-list-item-meta`)
|
|
103
|
-
if (typeof config.meta === 'string') {
|
|
104
|
-
meta.textContent = config.meta
|
|
105
|
-
} else {
|
|
106
|
-
meta.appendChild(config.meta)
|
|
107
|
-
}
|
|
108
|
-
content.appendChild(meta)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
element.appendChild(content)
|
|
112
|
-
|
|
113
|
-
// Add trailing content (icon/meta)
|
|
114
|
-
if (config.trailing) {
|
|
115
|
-
const trailing = createElement('div', `${prefix}-list-item-trailing`)
|
|
116
|
-
if (typeof config.trailing === 'string') {
|
|
117
|
-
trailing.innerHTML = config.trailing
|
|
118
|
-
} else {
|
|
119
|
-
trailing.appendChild(config.trailing)
|
|
120
|
-
}
|
|
121
|
-
element.appendChild(trailing)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Handle selected state
|
|
125
|
-
if (config.selected) {
|
|
126
|
-
element.setAttribute('aria-selected', 'true')
|
|
127
|
-
element.classList.add(`${prefix}-list-item--selected`)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return component
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return pipe(
|
|
134
|
-
createBase,
|
|
135
|
-
withEvents(),
|
|
136
|
-
withElement({
|
|
137
|
-
tag: 'div',
|
|
138
|
-
role: config.role || 'listitem',
|
|
139
|
-
componentName: 'list-item',
|
|
140
|
-
className: `${config.layout === LIST_ITEM_LAYOUTS.VERTICAL ? 'vertical' : ''} ${config.class || ''}`
|
|
141
|
-
}),
|
|
142
|
-
withDisabled(),
|
|
143
|
-
createContent
|
|
144
|
-
)(baseConfig)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export default createListItem
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
// src/components/list/list.js
|
|
2
|
-
|
|
3
|
-
import { PREFIX } from '../../core/config'
|
|
4
|
-
import { pipe } from '../../core/compose'
|
|
5
|
-
import { createBase, withElement } from '../../core/compose/component'
|
|
6
|
-
import { withEvents, withDisabled, withLifecycle } from '../../core/compose/features'
|
|
7
|
-
import createListItem from './list-item'
|
|
8
|
-
import { LIST_TYPES, LIST_LAYOUTS, LIST_CLASSES } from './constants'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Creates a divider element
|
|
12
|
-
* @param {string} prefix - CSS class prefix
|
|
13
|
-
* @returns {HTMLElement} Divider element
|
|
14
|
-
*/
|
|
15
|
-
const createDivider = (prefix) => {
|
|
16
|
-
const divider = document.createElement('div')
|
|
17
|
-
divider.className = `${prefix}-${LIST_CLASSES.DIVIDER}`
|
|
18
|
-
divider.setAttribute('role', 'separator')
|
|
19
|
-
return divider
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Creates a section title element
|
|
24
|
-
* @param {string} title - Section title text
|
|
25
|
-
* @param {string} prefix - CSS class prefix
|
|
26
|
-
* @returns {HTMLElement} Section title element
|
|
27
|
-
*/
|
|
28
|
-
const createSectionTitle = (title, prefix) => {
|
|
29
|
-
const titleEl = document.createElement('div')
|
|
30
|
-
titleEl.className = `${prefix}-${LIST_CLASSES.SECTION_TITLE}`
|
|
31
|
-
titleEl.textContent = title
|
|
32
|
-
return titleEl
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Creates a list component
|
|
37
|
-
* @param {Object} config - List configuration
|
|
38
|
-
*/
|
|
39
|
-
const createList = (config = {}) => {
|
|
40
|
-
const baseConfig = {
|
|
41
|
-
...config,
|
|
42
|
-
componentName: 'list',
|
|
43
|
-
prefix: PREFIX
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const createContent = (component) => {
|
|
47
|
-
const { element, prefix } = component
|
|
48
|
-
const items = new Map()
|
|
49
|
-
const selectedItems = new Set()
|
|
50
|
-
|
|
51
|
-
// Set list type
|
|
52
|
-
element.setAttribute('data-type', config.type || LIST_TYPES.DEFAULT)
|
|
53
|
-
|
|
54
|
-
// Handle keyboard navigation
|
|
55
|
-
const handleKeyDown = (event) => {
|
|
56
|
-
const focusedItem = document.activeElement
|
|
57
|
-
if (!focusedItem?.classList.contains(`${prefix}-list-item`)) return
|
|
58
|
-
|
|
59
|
-
const items = Array.from(element.querySelectorAll(`.${prefix}-list-item`))
|
|
60
|
-
const currentIndex = items.indexOf(focusedItem)
|
|
61
|
-
|
|
62
|
-
switch (event.key) {
|
|
63
|
-
case 'ArrowDown':
|
|
64
|
-
case 'ArrowRight':
|
|
65
|
-
event.preventDefault()
|
|
66
|
-
const nextItem = items[currentIndex + 1]
|
|
67
|
-
if (nextItem) nextItem.focus()
|
|
68
|
-
break
|
|
69
|
-
case 'ArrowUp':
|
|
70
|
-
case 'ArrowLeft':
|
|
71
|
-
event.preventDefault()
|
|
72
|
-
const prevItem = items[currentIndex - 1]
|
|
73
|
-
if (prevItem) prevItem.focus()
|
|
74
|
-
break
|
|
75
|
-
case 'Home':
|
|
76
|
-
event.preventDefault()
|
|
77
|
-
items[0]?.focus()
|
|
78
|
-
break
|
|
79
|
-
case 'End':
|
|
80
|
-
event.preventDefault()
|
|
81
|
-
items[items.length - 1]?.focus()
|
|
82
|
-
break
|
|
83
|
-
case ' ':
|
|
84
|
-
case 'Enter':
|
|
85
|
-
event.preventDefault()
|
|
86
|
-
handleItemClick(focusedItem)
|
|
87
|
-
break
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Handle item selection
|
|
92
|
-
const handleItemClick = (itemElement) => {
|
|
93
|
-
const id = itemElement.dataset.id
|
|
94
|
-
if (!id) return
|
|
95
|
-
|
|
96
|
-
const itemData = items.get(id)
|
|
97
|
-
if (!itemData || itemData.disabled) return
|
|
98
|
-
|
|
99
|
-
switch (config.type) {
|
|
100
|
-
case LIST_TYPES.SINGLE_SELECT:
|
|
101
|
-
// Deselect previously selected item
|
|
102
|
-
selectedItems.forEach(selectedId => {
|
|
103
|
-
const selected = items.get(selectedId)
|
|
104
|
-
if (selected) {
|
|
105
|
-
selected.element.classList.remove(`${prefix}-list-item--selected`)
|
|
106
|
-
selected.element.setAttribute('aria-selected', 'false')
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
selectedItems.clear()
|
|
110
|
-
|
|
111
|
-
// Select new item
|
|
112
|
-
itemElement.classList.add(`${prefix}-list-item--selected`)
|
|
113
|
-
itemElement.setAttribute('aria-selected', 'true')
|
|
114
|
-
selectedItems.add(id)
|
|
115
|
-
break
|
|
116
|
-
|
|
117
|
-
case LIST_TYPES.MULTI_SELECT:
|
|
118
|
-
const isSelected = selectedItems.has(id)
|
|
119
|
-
if (isSelected) {
|
|
120
|
-
itemElement.classList.remove(`${prefix}-list-item--selected`)
|
|
121
|
-
itemElement.setAttribute('aria-selected', 'false')
|
|
122
|
-
selectedItems.delete(id)
|
|
123
|
-
} else {
|
|
124
|
-
itemElement.classList.add(`${prefix}-list-item--selected`)
|
|
125
|
-
itemElement.setAttribute('aria-selected', 'true')
|
|
126
|
-
selectedItems.add(id)
|
|
127
|
-
}
|
|
128
|
-
break
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
component.emit('selectionChange', {
|
|
132
|
-
selected: Array.from(selectedItems),
|
|
133
|
-
item: itemData,
|
|
134
|
-
type: config.type
|
|
135
|
-
})
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Create items from configuration
|
|
139
|
-
const createItems = (itemsConfig = [], container = element) => {
|
|
140
|
-
itemsConfig.forEach((itemConfig, index) => {
|
|
141
|
-
if (itemConfig.divider) {
|
|
142
|
-
container.appendChild(createDivider(prefix))
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const item = createListItem({
|
|
147
|
-
...itemConfig,
|
|
148
|
-
layout: config.layout || LIST_LAYOUTS.HORIZONTAL,
|
|
149
|
-
role: config.type === LIST_TYPES.RADIO ? 'radio' : 'option'
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
item.element.dataset.id = itemConfig.id
|
|
153
|
-
item.element.tabIndex = index === 0 ? 0 : -1
|
|
154
|
-
items.set(itemConfig.id, item)
|
|
155
|
-
|
|
156
|
-
if (itemConfig.selected) {
|
|
157
|
-
selectedItems.add(itemConfig.id)
|
|
158
|
-
item.element.classList.add(`${prefix}-list-item--selected`)
|
|
159
|
-
item.element.setAttribute('aria-selected', 'true')
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
container.appendChild(item.element)
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Create sections if configured
|
|
167
|
-
if (config.sections?.length) {
|
|
168
|
-
config.sections.forEach(section => {
|
|
169
|
-
const sectionEl = document.createElement('div')
|
|
170
|
-
sectionEl.className = `${prefix}-${LIST_CLASSES.SECTION}`
|
|
171
|
-
sectionEl.setAttribute('role', 'group')
|
|
172
|
-
if (section.title) {
|
|
173
|
-
sectionEl.appendChild(createSectionTitle(section.title, prefix))
|
|
174
|
-
}
|
|
175
|
-
createItems(section.items, sectionEl)
|
|
176
|
-
element.appendChild(sectionEl)
|
|
177
|
-
})
|
|
178
|
-
} else {
|
|
179
|
-
createItems(config.items)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Add event listeners
|
|
183
|
-
element.addEventListener('click', (event) => {
|
|
184
|
-
const item = event.target.closest(`.${prefix}-list-item`)
|
|
185
|
-
if (item) handleItemClick(item)
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
element.addEventListener('keydown', handleKeyDown)
|
|
189
|
-
|
|
190
|
-
// Clean up
|
|
191
|
-
if (component.lifecycle) {
|
|
192
|
-
const originalDestroy = component.lifecycle.destroy
|
|
193
|
-
component.lifecycle.destroy = () => {
|
|
194
|
-
items.clear()
|
|
195
|
-
selectedItems.clear()
|
|
196
|
-
element.removeEventListener('keydown', handleKeyDown)
|
|
197
|
-
originalDestroy?.()
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
...component,
|
|
203
|
-
items,
|
|
204
|
-
selectedItems,
|
|
205
|
-
|
|
206
|
-
// Public methods
|
|
207
|
-
getSelected: () => Array.from(selectedItems),
|
|
208
|
-
|
|
209
|
-
setSelected: (ids) => {
|
|
210
|
-
selectedItems.clear()
|
|
211
|
-
items.forEach((item, id) => {
|
|
212
|
-
const isSelected = ids.includes(id)
|
|
213
|
-
item.element.classList.toggle(`${prefix}-list-item--selected`, isSelected)
|
|
214
|
-
item.element.setAttribute('aria-selected', isSelected.toString())
|
|
215
|
-
if (isSelected) selectedItems.add(id)
|
|
216
|
-
})
|
|
217
|
-
component.emit('selectionChange', {
|
|
218
|
-
selected: Array.from(selectedItems),
|
|
219
|
-
type: config.type
|
|
220
|
-
})
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
addItem: (itemConfig) => {
|
|
224
|
-
if (items.has(itemConfig.id)) return
|
|
225
|
-
|
|
226
|
-
const item = createListItem({
|
|
227
|
-
...itemConfig,
|
|
228
|
-
layout: config.layout || LIST_LAYOUTS.HORIZONTAL
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
item.element.dataset.id = itemConfig.id
|
|
232
|
-
items.set(itemConfig.id, item)
|
|
233
|
-
element.appendChild(item.element)
|
|
234
|
-
|
|
235
|
-
component.emit('itemAdded', { id: itemConfig.id, item })
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
removeItem: (id) => {
|
|
239
|
-
const item = items.get(id)
|
|
240
|
-
if (!item) return
|
|
241
|
-
|
|
242
|
-
item.element.remove()
|
|
243
|
-
items.delete(id)
|
|
244
|
-
selectedItems.delete(id)
|
|
245
|
-
|
|
246
|
-
component.emit('itemRemoved', { id, item })
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return pipe(
|
|
252
|
-
createBase,
|
|
253
|
-
withEvents(),
|
|
254
|
-
withElement({
|
|
255
|
-
tag: 'div',
|
|
256
|
-
role: config.type === LIST_TYPES.DEFAULT ? 'list' : 'listbox',
|
|
257
|
-
'aria-multiselectable': config.type === LIST_TYPES.MULTI_SELECT ? 'true' : undefined,
|
|
258
|
-
componentName: LIST_CLASSES.ROOT,
|
|
259
|
-
className: config.class
|
|
260
|
-
}),
|
|
261
|
-
withDisabled(),
|
|
262
|
-
withLifecycle(),
|
|
263
|
-
createContent
|
|
264
|
-
)(baseConfig)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export default createList
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
// src/components/menu/api.js
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Enhances menu component with API methods
|
|
5
|
-
* @param {Object} options - API configuration
|
|
6
|
-
* @param {Object} options.lifecycle - Lifecycle handlers
|
|
7
|
-
*/
|
|
8
|
-
export const withAPI = ({ lifecycle }) => (component) => ({
|
|
9
|
-
...component,
|
|
10
|
-
element: component.element,
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Shows the menu
|
|
14
|
-
* @returns {Object} Component instance
|
|
15
|
-
*/
|
|
16
|
-
show () {
|
|
17
|
-
component.show()
|
|
18
|
-
return this
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Hides the menu
|
|
23
|
-
* @returns {Object} Component instance
|
|
24
|
-
*/
|
|
25
|
-
hide () {
|
|
26
|
-
component.hide()
|
|
27
|
-
return this
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Positions the menu relative to a target
|
|
32
|
-
* @param {HTMLElement} target - Target element
|
|
33
|
-
* @param {Object} options - Position options
|
|
34
|
-
* @returns {Object} Component instance
|
|
35
|
-
*/
|
|
36
|
-
position (target, options) {
|
|
37
|
-
component.position(target, options)
|
|
38
|
-
return this
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Adds an item to the menu
|
|
43
|
-
* @param {Object} config - Item configuration
|
|
44
|
-
* @returns {Object} Component instance
|
|
45
|
-
*/
|
|
46
|
-
addItem (config) {
|
|
47
|
-
component.addItem?.(config)
|
|
48
|
-
return this
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Removes an item by name
|
|
53
|
-
* @param {string} name - Item name to remove
|
|
54
|
-
* @returns {Object} Component instance
|
|
55
|
-
*/
|
|
56
|
-
removeItem (name) {
|
|
57
|
-
component.removeItem?.(name)
|
|
58
|
-
return this
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Gets all registered items
|
|
63
|
-
* @returns {Map} Map of item names to configurations
|
|
64
|
-
*/
|
|
65
|
-
getItems () {
|
|
66
|
-
return component.getItems?.()
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Checks if the menu is currently visible
|
|
71
|
-
* @returns {boolean} Whether the menu is visible
|
|
72
|
-
*/
|
|
73
|
-
isVisible () {
|
|
74
|
-
return component.isVisible?.()
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Registers an event handler
|
|
79
|
-
* @param {string} event - Event name
|
|
80
|
-
* @param {Function} handler - Event handler
|
|
81
|
-
* @returns {Object} Component instance
|
|
82
|
-
*/
|
|
83
|
-
on (event, handler) {
|
|
84
|
-
component.on(event, handler)
|
|
85
|
-
return this
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Unregisters an event handler
|
|
90
|
-
* @param {string} event - Event name
|
|
91
|
-
* @param {Function} handler - Event handler
|
|
92
|
-
* @returns {Object} Component instance
|
|
93
|
-
*/
|
|
94
|
-
off (event, handler) {
|
|
95
|
-
component.off(event, handler)
|
|
96
|
-
return this
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Destroys the menu component and cleans up resources
|
|
101
|
-
* @returns {Object} Component instance
|
|
102
|
-
*/
|
|
103
|
-
destroy () {
|
|
104
|
-
// First close any open submenus
|
|
105
|
-
component.hide?.()
|
|
106
|
-
|
|
107
|
-
// Then destroy the component
|
|
108
|
-
lifecycle.destroy?.()
|
|
109
|
-
|
|
110
|
-
// Final cleanup - forcibly remove from DOM if still attached
|
|
111
|
-
if (component.element && component.element.parentNode) {
|
|
112
|
-
component.element.remove()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return this
|
|
116
|
-
}
|
|
117
|
-
})
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// src/components/menu/constants.js
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Menu alignment options
|
|
5
|
-
* @enum {string}
|
|
6
|
-
*/
|
|
7
|
-
export const MENU_ALIGN = {
|
|
8
|
-
LEFT: 'left',
|
|
9
|
-
RIGHT: 'right',
|
|
10
|
-
CENTER: 'center'
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Menu vertical alignment options
|
|
15
|
-
* @enum {string}
|
|
16
|
-
*/
|
|
17
|
-
export const MENU_VERTICAL_ALIGN = {
|
|
18
|
-
TOP: 'top',
|
|
19
|
-
BOTTOM: 'bottom',
|
|
20
|
-
MIDDLE: 'middle'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Menu item types
|
|
25
|
-
* @enum {string}
|
|
26
|
-
*/
|
|
27
|
-
export const MENU_ITEM_TYPES = {
|
|
28
|
-
ITEM: 'item',
|
|
29
|
-
DIVIDER: 'divider'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Menu events
|
|
34
|
-
* @enum {string}
|
|
35
|
-
*/
|
|
36
|
-
export const MENU_EVENTS = {
|
|
37
|
-
SELECT: 'select',
|
|
38
|
-
OPEN: 'open',
|
|
39
|
-
CLOSE: 'close',
|
|
40
|
-
SUBMENU_OPEN: 'submenuOpen',
|
|
41
|
-
SUBMENU_CLOSE: 'submenuClose'
|
|
42
|
-
}
|