mtrl 0.0.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/LICENSE +21 -0
- package/README.md +251 -0
- package/index.js +10 -0
- package/package.json +17 -0
- package/src/components/button/api.js +54 -0
- package/src/components/button/button.js +81 -0
- package/src/components/button/config.js +8 -0
- package/src/components/button/constants.js +63 -0
- package/src/components/button/index.js +2 -0
- package/src/components/button/styles.scss +231 -0
- package/src/components/checkbox/api.js +45 -0
- package/src/components/checkbox/checkbox.js +95 -0
- package/src/components/checkbox/constants.js +88 -0
- package/src/components/checkbox/index.js +2 -0
- package/src/components/checkbox/styles.scss +183 -0
- package/src/components/container/api.js +42 -0
- package/src/components/container/container.js +45 -0
- package/src/components/container/index.js +2 -0
- package/src/components/container/styles.scss +59 -0
- package/src/components/list/constants.js +89 -0
- package/src/components/list/index.js +2 -0
- package/src/components/list/list-item.js +147 -0
- package/src/components/list/list.js +267 -0
- package/src/components/list/styles/_list-item.scss +142 -0
- package/src/components/list/styles/_list.scss +89 -0
- package/src/components/list/styles/_variables.scss +13 -0
- package/src/components/list/styles.scss +19 -0
- package/src/components/navigation/api.js +43 -0
- package/src/components/navigation/constants.js +235 -0
- package/src/components/navigation/features/items.js +192 -0
- package/src/components/navigation/index.js +2 -0
- package/src/components/navigation/nav-item.js +137 -0
- package/src/components/navigation/navigation.js +55 -0
- package/src/components/navigation/styles/_bar.scss +51 -0
- package/src/components/navigation/styles/_base.scss +129 -0
- package/src/components/navigation/styles/_drawer.scss +169 -0
- package/src/components/navigation/styles/_rail.scss +65 -0
- package/src/components/navigation/styles.scss +6 -0
- package/src/components/snackbar/api.js +125 -0
- package/src/components/snackbar/constants.js +41 -0
- package/src/components/snackbar/features.js +69 -0
- package/src/components/snackbar/index.js +2 -0
- package/src/components/snackbar/position.js +63 -0
- package/src/components/snackbar/queue.js +74 -0
- package/src/components/snackbar/snackbar.js +70 -0
- package/src/components/snackbar/styles.scss +182 -0
- package/src/components/switch/api.js +44 -0
- package/src/components/switch/constants.js +80 -0
- package/src/components/switch/index.js +2 -0
- package/src/components/switch/styles.scss +172 -0
- package/src/components/switch/switch.js +71 -0
- package/src/components/textfield/api.js +49 -0
- package/src/components/textfield/constants.js +81 -0
- package/src/components/textfield/index.js +2 -0
- package/src/components/textfield/styles/base.scss +107 -0
- package/src/components/textfield/styles/filled.scss +58 -0
- package/src/components/textfield/styles/outlined.scss +66 -0
- package/src/components/textfield/styles.scss +6 -0
- package/src/components/textfield/textfield.js +68 -0
- package/src/core/build/constants.js +51 -0
- package/src/core/build/icon.js +78 -0
- package/src/core/build/ripple.js +92 -0
- package/src/core/build/text.js +54 -0
- package/src/core/collection/adapters/base.js +26 -0
- package/src/core/collection/adapters/mongodb.js +232 -0
- package/src/core/collection/adapters/route.js +201 -0
- package/src/core/collection/collection.js +259 -0
- package/src/core/collection/list-manager.js +157 -0
- package/src/core/compose/base.js +8 -0
- package/src/core/compose/component.js +225 -0
- package/src/core/compose/features/checkable.js +114 -0
- package/src/core/compose/features/disabled.js +25 -0
- package/src/core/compose/features/events.js +48 -0
- package/src/core/compose/features/icon.js +33 -0
- package/src/core/compose/features/index.js +20 -0
- package/src/core/compose/features/input.js +92 -0
- package/src/core/compose/features/lifecycle.js +69 -0
- package/src/core/compose/features/position.js +60 -0
- package/src/core/compose/features/ripple.js +32 -0
- package/src/core/compose/features/size.js +9 -0
- package/src/core/compose/features/style.js +12 -0
- package/src/core/compose/features/text.js +17 -0
- package/src/core/compose/features/textinput.js +118 -0
- package/src/core/compose/features/textlabel.js +28 -0
- package/src/core/compose/features/track.js +49 -0
- package/src/core/compose/features/variant.js +9 -0
- package/src/core/compose/features/withEvents.js +67 -0
- package/src/core/compose/index.js +16 -0
- package/src/core/compose/pipe.js +69 -0
- package/src/core/config.js +140 -0
- package/src/core/dom/attributes.js +33 -0
- package/src/core/dom/classes.js +70 -0
- package/src/core/dom/create.js +133 -0
- package/src/core/dom/events.js +175 -0
- package/src/core/dom/index.js +5 -0
- package/src/core/dom/utils.js +22 -0
- package/src/core/index.js +23 -0
- package/src/core/layout/index.js +93 -0
- package/src/core/state/disabled.js +14 -0
- package/src/core/state/emitter.js +63 -0
- package/src/core/state/events.js +29 -0
- package/src/core/state/index.js +6 -0
- package/src/core/state/lifecycle.js +64 -0
- package/src/core/state/store.js +112 -0
- package/src/core/utils/index.js +39 -0
- package/src/core/utils/mobile.js +74 -0
- package/src/core/utils/object.js +22 -0
- package/src/core/utils/validate.js +37 -0
- package/src/index.js +11 -0
- package/src/styles/abstract/_base.scss +2 -0
- package/src/styles/abstract/_config.scss +28 -0
- package/src/styles/abstract/_functions.scss +124 -0
- package/src/styles/abstract/_mixins.scss +261 -0
- package/src/styles/abstract/_variables.scss +158 -0
- package/src/styles/main.scss +78 -0
- package/src/styles/themes/_base-theme.scss +49 -0
- package/src/styles/themes/_baseline.scss +90 -0
- package/src/styles/themes/_forest.scss +71 -0
- package/src/styles/themes/_index.scss +6 -0
- package/src/styles/themes/_ocean.scss +71 -0
- package/src/styles/themes/_sunset.scss +55 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/components/textfield/styles/_base.scss
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
@use '../../../styles/abstract/config' as c;
|
|
4
|
+
|
|
5
|
+
.#{c.$prefix}-textfield {
|
|
6
|
+
position: relative;
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
min-width: 210px;
|
|
10
|
+
|
|
11
|
+
&--small {
|
|
12
|
+
.#{c.$prefix}-textfield-input {
|
|
13
|
+
height: 48px;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&--large {
|
|
18
|
+
.#{c.$prefix}-textfield-input {
|
|
19
|
+
height: 64px;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&-label {
|
|
24
|
+
@include c.typography('body-large');
|
|
25
|
+
user-select: none;
|
|
26
|
+
position: absolute;
|
|
27
|
+
left: 16px;
|
|
28
|
+
top: 50%;
|
|
29
|
+
transform: translateY(-50%);
|
|
30
|
+
transform-origin: left top;
|
|
31
|
+
pointer-events: none;
|
|
32
|
+
border-radius: 2px;
|
|
33
|
+
color: var(--mtrl-sys-color-on-surface-variant);
|
|
34
|
+
transition: transform map.get(c.$motion, 'duration-short4') map.get(c.$motion, 'easing-emphasized'),
|
|
35
|
+
color map.get(c.$motion, 'duration-short2') map.get(c.$motion, 'easing-standard');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&-input {
|
|
39
|
+
@include c.typography('body-large');
|
|
40
|
+
@include c.shape('extra-small');
|
|
41
|
+
padding: 13px 16px;
|
|
42
|
+
width: 100%;
|
|
43
|
+
color: var(--mtrl-sys-color-on-surface);
|
|
44
|
+
border: 0;
|
|
45
|
+
appearance: none;
|
|
46
|
+
outline: none;
|
|
47
|
+
&::placeholder {
|
|
48
|
+
color: transparent;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Autofill styles
|
|
52
|
+
&:-webkit-autofill {
|
|
53
|
+
// font: inherit;
|
|
54
|
+
-webkit-text-fill-color: var(--mtrl-sys-color-on-surface);
|
|
55
|
+
transition: background-color 5000s ease-in-out 0s; // Long transition to keep the background
|
|
56
|
+
|
|
57
|
+
& ~ .#{c.$prefix}-textfield-label {
|
|
58
|
+
transform: translateY(-95%) scale(0.75);
|
|
59
|
+
background-color: var(--mtrl-sys-color-surface);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Firefox autofill
|
|
64
|
+
&:autofill {
|
|
65
|
+
color: var(--mtrl-sys-color-on-surface);
|
|
66
|
+
|
|
67
|
+
& ~ .#{c.$prefix}-textfield-label {
|
|
68
|
+
transform: translateY(-95%) scale(0.75);
|
|
69
|
+
background-color: var(--mtrl-sys-color-surface);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&--error {
|
|
75
|
+
border-color: var(--mtrl-sys-color-error);
|
|
76
|
+
.#{c.$prefix}-textfield-label {
|
|
77
|
+
color: var(--mtrl-sys-color-error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&-input:disabled {
|
|
82
|
+
opacity: 0.38;
|
|
83
|
+
border-color: var(--mtrl-sys-color-on-surface);
|
|
84
|
+
background-color: rgba(var(--mtrl-sys-color-on-surface-rgb), 0.04);
|
|
85
|
+
pointer-events: none;
|
|
86
|
+
|
|
87
|
+
& ~ .#{c.$prefix}-textfield-label {
|
|
88
|
+
color: var(--mtrl-sys-color-on-surface);
|
|
89
|
+
opacity: 0.38;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&-helper {
|
|
94
|
+
@include c.typography('body-small');
|
|
95
|
+
margin-top: 4px;
|
|
96
|
+
color: var(--mtrl-sys-color-on-surface-variant);
|
|
97
|
+
|
|
98
|
+
&--error {
|
|
99
|
+
color: var(--mtrl-sys-color-error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&-required {
|
|
104
|
+
color: var(--mtrl-sys-color-error);
|
|
105
|
+
margin-left: 4px;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/components/textfield/styles/_filled.scss
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
@use '../../../styles/abstract/config' as c;
|
|
4
|
+
|
|
5
|
+
.#{c.$prefix}-textfield {
|
|
6
|
+
&--filled {
|
|
7
|
+
border-bottom: 1px solid var(--mtrl-sys-color-outline);
|
|
8
|
+
|
|
9
|
+
.#{c.$prefix}-textfield-input {
|
|
10
|
+
background-color: var(--mtrl-sys-color-surface-container-highest);
|
|
11
|
+
padding: 20px 16px 7px;
|
|
12
|
+
border-radius: 4px 4px 0 0;
|
|
13
|
+
|
|
14
|
+
&:focus {
|
|
15
|
+
padding-bottom: 6px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Autofill styles for filled variant
|
|
19
|
+
&:-webkit-autofill {
|
|
20
|
+
border-radius: 4px 4px 0 0;
|
|
21
|
+
|
|
22
|
+
& ~ .#{c.$prefix}-textfield-label {
|
|
23
|
+
transform: translateY(-95%) scale(0.75);
|
|
24
|
+
color: var(--mtrl-sys-color-on-surface-variant);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&:autofill {
|
|
29
|
+
& ~ .#{c.$prefix}-textfield-label {
|
|
30
|
+
transform: translateY(-95%) scale(0.75);
|
|
31
|
+
color: var(--mtrl-sys-color-on-surface-variant);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&:not(.#{c.$prefix}-textfield--empty) .#{c.$prefix}-textfield-label,
|
|
37
|
+
&.#{c.$prefix}-textfield--focused .#{c.$prefix}-textfield-label {
|
|
38
|
+
transform: translateY(-95%) scale(0.75);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&.#{c.$prefix}-textfield--focused {
|
|
42
|
+
border-bottom: 2px solid var(--mtrl-sys-color-primary);
|
|
43
|
+
.#{c.$prefix}-textfield-label {
|
|
44
|
+
color: var(--mtrl-sys-color-primary);
|
|
45
|
+
}
|
|
46
|
+
&:hover {
|
|
47
|
+
border-bottom: 2px solid var(--mtrl-sys-color-primary);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&:hover {
|
|
52
|
+
border-bottom: 1px solid var(--mtrl-sys-color-primary);
|
|
53
|
+
.#{c.$prefix}-textfield-label {
|
|
54
|
+
color: var(--mtrl-sys-color-primary);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/components/textfield/styles/_outlined.scss
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
@use '../../../styles/abstract/config' as c;
|
|
4
|
+
|
|
5
|
+
.#{c.$prefix}-textfield {
|
|
6
|
+
&--outlined {
|
|
7
|
+
border: 1px solid var(--mtrl-sys-color-outline);
|
|
8
|
+
border-radius: 4px;
|
|
9
|
+
|
|
10
|
+
.#{c.$prefix}-textfield-input {
|
|
11
|
+
background-color: transparent;
|
|
12
|
+
padding: 13px 16px 14px;
|
|
13
|
+
|
|
14
|
+
// Autofill styles for outlined variant
|
|
15
|
+
&:-webkit-autofill {
|
|
16
|
+
border-radius: 4px 4px 0 0;
|
|
17
|
+
|
|
18
|
+
& ~ .#{c.$prefix}-textfield-label {
|
|
19
|
+
background-color: var(--mtrl-sys-color-surface);
|
|
20
|
+
transform: translateY(-145%) scale(0.75);
|
|
21
|
+
left: 13px;
|
|
22
|
+
padding: 0 4px;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:autofill {
|
|
27
|
+
& ~ .#{c.$prefix}-textfield-label {
|
|
28
|
+
background-color: var(--mtrl-sys-color-surface);
|
|
29
|
+
transform: translateY(-145%) scale(0.75);
|
|
30
|
+
left: 13px;
|
|
31
|
+
padding: 0 4px;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&:not(.#{c.$prefix}-textfield--empty) .#{c.$prefix}-textfield-label,
|
|
37
|
+
&.#{c.$prefix}-textfield--focused .#{c.$prefix}-textfield-label {
|
|
38
|
+
background-color: var(--mtrl-sys-color-surface);
|
|
39
|
+
transform: translateY(-145%) scale(0.75);
|
|
40
|
+
left: 13px;
|
|
41
|
+
padding: 0 4px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&.#{c.$prefix}-textfield--focused {
|
|
45
|
+
border: 2px solid var(--mtrl-sys-color-primary);
|
|
46
|
+
.#{c.$prefix}-textfield-label {
|
|
47
|
+
color: var(--mtrl-sys-color-primary);
|
|
48
|
+
border-radius: 2px;
|
|
49
|
+
left: 12px;
|
|
50
|
+
}
|
|
51
|
+
.#{c.$prefix}-textfield-input {
|
|
52
|
+
padding: 12px 15px 13px;
|
|
53
|
+
}
|
|
54
|
+
&:hover {
|
|
55
|
+
border: 2px solid var(--mtrl-sys-color-primary);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&:hover {
|
|
60
|
+
border: 1px solid var(--mtrl-sys-color-primary);
|
|
61
|
+
.#{c.$prefix}-textfield-label {
|
|
62
|
+
color: var(--mtrl-sys-color-primary);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/components/textfield/textfield.js
|
|
2
|
+
import { PREFIX } from '../../core/config'
|
|
3
|
+
import { pipe } from '../../core/compose'
|
|
4
|
+
import { createBase, withElement } from '../../core/compose/component'
|
|
5
|
+
import {
|
|
6
|
+
withEvents,
|
|
7
|
+
withDisabled,
|
|
8
|
+
withLifecycle,
|
|
9
|
+
withVariant,
|
|
10
|
+
withSize,
|
|
11
|
+
withTextInput,
|
|
12
|
+
withTextLabel
|
|
13
|
+
} from '../../core/compose/features'
|
|
14
|
+
import { withAPI } from './api'
|
|
15
|
+
import { TEXTFIELD_VARIANTS } from './constants'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new Textfield component
|
|
19
|
+
* @param {Object} config - Textfield configuration
|
|
20
|
+
* @param {string} [config.type] - Input type (text, password, email, etc.)
|
|
21
|
+
* @param {string} [config.variant] - Visual variant (filled, outlined)
|
|
22
|
+
* @param {string} [config.size] - Size variant (small, medium, large)
|
|
23
|
+
* @param {string} [config.name] - Input name attribute
|
|
24
|
+
* @param {string} [config.label] - Label text
|
|
25
|
+
* @param {string} [config.placeholder] - Placeholder text
|
|
26
|
+
* @param {string} [config.value] - Initial value
|
|
27
|
+
* @param {boolean} [config.required] - Whether the input is required
|
|
28
|
+
* @param {boolean} [config.disabled] - Whether the input is disabled
|
|
29
|
+
* @param {number} [config.maxLength] - Maximum input length
|
|
30
|
+
* @param {string} [config.pattern] - Input pattern for validation
|
|
31
|
+
* @param {string} [config.autocomplete] - Autocomplete attribute
|
|
32
|
+
* @param {string} [config.class] - Additional CSS classes
|
|
33
|
+
* @returns {Object} Textfield component instance
|
|
34
|
+
*/
|
|
35
|
+
const createTextfield = (config = {}) => {
|
|
36
|
+
const baseConfig = {
|
|
37
|
+
...config,
|
|
38
|
+
componentName: 'textfield',
|
|
39
|
+
prefix: PREFIX,
|
|
40
|
+
variant: config.variant || TEXTFIELD_VARIANTS.FILLED
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return pipe(
|
|
45
|
+
createBase,
|
|
46
|
+
withEvents(),
|
|
47
|
+
withElement({
|
|
48
|
+
tag: 'div',
|
|
49
|
+
componentName: 'textfield',
|
|
50
|
+
className: config.class
|
|
51
|
+
}),
|
|
52
|
+
withVariant(baseConfig),
|
|
53
|
+
withSize(baseConfig),
|
|
54
|
+
withTextInput(baseConfig),
|
|
55
|
+
withTextLabel(baseConfig),
|
|
56
|
+
withDisabled(baseConfig),
|
|
57
|
+
withLifecycle(),
|
|
58
|
+
comp => withAPI({
|
|
59
|
+
disabled: comp.disabled,
|
|
60
|
+
lifecycle: comp.lifecycle
|
|
61
|
+
})(comp)
|
|
62
|
+
)(baseConfig)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error(`Failed to create textfield: ${error.message}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default createTextfield
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/core/build/constants.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Animation timing functions for ripple effect
|
|
5
|
+
* @enum {string}
|
|
6
|
+
*/
|
|
7
|
+
export const RIPPLE_TIMING = {
|
|
8
|
+
LINEAR: 'linear',
|
|
9
|
+
EASE: 'ease',
|
|
10
|
+
EASE_IN: 'ease-in',
|
|
11
|
+
EASE_OUT: 'ease-out',
|
|
12
|
+
EASE_IN_OUT: 'ease-in-out',
|
|
13
|
+
MATERIAL: 'cubic-bezier(0.4, 0.0, 0.2, 1)'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default configuration for ripple effect
|
|
18
|
+
* @type {Object}
|
|
19
|
+
*/
|
|
20
|
+
export const RIPPLE_CONFIG = {
|
|
21
|
+
duration: 375,
|
|
22
|
+
timing: RIPPLE_TIMING.LINEAR,
|
|
23
|
+
opacity: ['1', '0.3']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validation schema for ripple configuration
|
|
28
|
+
* @type {Object}
|
|
29
|
+
*/
|
|
30
|
+
export const RIPPLE_SCHEMA = {
|
|
31
|
+
duration: {
|
|
32
|
+
type: 'number',
|
|
33
|
+
minimum: 0,
|
|
34
|
+
default: RIPPLE_CONFIG.duration
|
|
35
|
+
},
|
|
36
|
+
timing: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: Object.values(RIPPLE_TIMING),
|
|
39
|
+
default: RIPPLE_CONFIG.timing
|
|
40
|
+
},
|
|
41
|
+
opacity: {
|
|
42
|
+
type: 'array',
|
|
43
|
+
items: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
pattern: '^[0-1](\\.\\d+)?$'
|
|
46
|
+
},
|
|
47
|
+
minItems: 2,
|
|
48
|
+
maxItems: 2,
|
|
49
|
+
default: RIPPLE_CONFIG.opacity
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/core/build/icon.js
|
|
2
|
+
/**
|
|
3
|
+
* @module core/build
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates an icon DOM element
|
|
8
|
+
* @memberof module:core/build
|
|
9
|
+
* @private
|
|
10
|
+
* @param {string} html - Icon HTML content
|
|
11
|
+
* @param {Object} [options] - Icon options
|
|
12
|
+
* @param {string} [options.prefix='mtrl'] - Class prefix
|
|
13
|
+
* @param {string} [options.class] - Additional CSS class
|
|
14
|
+
* @param {string} [options.size] - Icon size variant
|
|
15
|
+
* @returns {HTMLElement} Icon element
|
|
16
|
+
*/
|
|
17
|
+
const createIconElement = (html, options = {}) => {
|
|
18
|
+
const PREFIX = options.prefix || 'mtrl'
|
|
19
|
+
const element = document.createElement('span')
|
|
20
|
+
element.className = `${PREFIX}-icon`
|
|
21
|
+
|
|
22
|
+
if (options.class) {
|
|
23
|
+
element.classList.add(options.class)
|
|
24
|
+
}
|
|
25
|
+
if (options.size) {
|
|
26
|
+
element.classList.add(`${PREFIX}-icon--${options.size}`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
element.innerHTML = html
|
|
30
|
+
return element
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates an icon manager for a component
|
|
35
|
+
* @memberof module:core/build
|
|
36
|
+
* @function createIcon
|
|
37
|
+
* @param {HTMLElement} element - Parent element
|
|
38
|
+
* @param {Object} [config] - Icon configuration
|
|
39
|
+
* @param {string} [config.prefix='mtrl'] - Class prefix
|
|
40
|
+
* @param {string} [config.type='component'] - Component type
|
|
41
|
+
* @param {string} [config.position] - Icon position ('start' or 'end')
|
|
42
|
+
* @returns {Object} Icon manager interface
|
|
43
|
+
* @property {Function} setIcon - Sets icon content
|
|
44
|
+
* @property {Function} getIcon - Gets current icon content
|
|
45
|
+
* @property {Function} getElement - Gets icon element
|
|
46
|
+
*/
|
|
47
|
+
export const createIcon = (element, config = {}) => {
|
|
48
|
+
let iconElement = null
|
|
49
|
+
const PREFIX = config.prefix || 'mtrl'
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
setIcon (html) {
|
|
53
|
+
if (!iconElement && html) {
|
|
54
|
+
iconElement = createIconElement(html, {
|
|
55
|
+
prefix: PREFIX,
|
|
56
|
+
class: `${PREFIX}-${config.type || 'component'}-icon`,
|
|
57
|
+
size: config.iconSize
|
|
58
|
+
})
|
|
59
|
+
if (config.position === 'end') {
|
|
60
|
+
element.appendChild(iconElement)
|
|
61
|
+
} else {
|
|
62
|
+
element.insertBefore(iconElement, element.firstChild)
|
|
63
|
+
}
|
|
64
|
+
} else if (iconElement && html) {
|
|
65
|
+
iconElement.innerHTML = html
|
|
66
|
+
}
|
|
67
|
+
return this
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
getIcon () {
|
|
71
|
+
return iconElement ? iconElement.innerHTML : ''
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
getElement () {
|
|
75
|
+
return iconElement
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/core/build/ripple.js
|
|
2
|
+
|
|
3
|
+
import { RIPPLE_CONFIG } from './constants'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CONFIG = RIPPLE_CONFIG
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a ripple effect instance
|
|
9
|
+
* @param {Object} [config] - Ripple configuration
|
|
10
|
+
* @param {number} [config.duration] - Animation duration in ms
|
|
11
|
+
* @param {string} [config.timing] - Animation timing function
|
|
12
|
+
* @param {string[]} [config.opacity] - Start and end opacity values
|
|
13
|
+
* @returns {Object} Ripple controller instance
|
|
14
|
+
*/
|
|
15
|
+
export const createRipple = (config = {}) => {
|
|
16
|
+
const options = { ...DEFAULT_CONFIG, ...config }
|
|
17
|
+
|
|
18
|
+
const getEndCoordinates = (bounds) => {
|
|
19
|
+
const size = Math.max(bounds.width, bounds.height)
|
|
20
|
+
const top = bounds.height > bounds.width
|
|
21
|
+
? -bounds.height / 2
|
|
22
|
+
: -(bounds.width - bounds.height / 2)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
size: `${size * 2}px`,
|
|
26
|
+
top: `${top}px`,
|
|
27
|
+
left: `${size / -2}px`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const createRippleElement = () => {
|
|
32
|
+
const ripple = document.createElement('div')
|
|
33
|
+
ripple.className = 'ripple'
|
|
34
|
+
// Initial styles already set in CSS
|
|
35
|
+
ripple.style.transition = `all ${options.duration}ms ${options.timing}`
|
|
36
|
+
return ripple
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const animate = (event, container) => {
|
|
40
|
+
const bounds = container.getBoundingClientRect()
|
|
41
|
+
const ripple = createRippleElement()
|
|
42
|
+
|
|
43
|
+
// Set initial position and state
|
|
44
|
+
Object.assign(ripple.style, {
|
|
45
|
+
left: `${event.offsetX || bounds.width / 2}px`,
|
|
46
|
+
top: `${event.offsetY || bounds.height / 2}px`,
|
|
47
|
+
transform: 'scale(0)',
|
|
48
|
+
opacity: options.opacity[0]
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
container.appendChild(ripple)
|
|
52
|
+
ripple.offsetHeight // Force reflow
|
|
53
|
+
|
|
54
|
+
// Animate to end position
|
|
55
|
+
const end = getEndCoordinates(bounds)
|
|
56
|
+
Object.assign(ripple.style, {
|
|
57
|
+
...end,
|
|
58
|
+
transform: 'scale(1)',
|
|
59
|
+
opacity: options.opacity[1]
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const cleanup = () => {
|
|
63
|
+
ripple.style.opacity = '0'
|
|
64
|
+
setTimeout(() => ripple.remove(), options.duration)
|
|
65
|
+
document.removeEventListener('mouseup', cleanup)
|
|
66
|
+
document.removeEventListener('mouseleave', cleanup)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
document.addEventListener('mouseup', cleanup)
|
|
70
|
+
document.addEventListener('mouseleave', cleanup)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
mount: (element) => {
|
|
75
|
+
if (!element) return
|
|
76
|
+
|
|
77
|
+
// Ensure proper positioning context
|
|
78
|
+
const currentPosition = window.getComputedStyle(element).position
|
|
79
|
+
if (currentPosition === 'static') {
|
|
80
|
+
element.style.position = 'relative'
|
|
81
|
+
}
|
|
82
|
+
element.style.overflow = 'hidden'
|
|
83
|
+
|
|
84
|
+
element.addEventListener('mousedown', (e) => animate(e, element))
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
unmount: (element) => {
|
|
88
|
+
if (!element) return
|
|
89
|
+
element.querySelectorAll('.ripple').forEach(ripple => ripple.remove())
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/core/build/text.js
|
|
2
|
+
/**
|
|
3
|
+
* @module core/build
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a text manager for a component
|
|
8
|
+
* @memberof module:core/build
|
|
9
|
+
* @function createText
|
|
10
|
+
* @param {HTMLElement} element - Parent element
|
|
11
|
+
* @param {Object} [config] - Text configuration
|
|
12
|
+
* @param {string} [config.prefix='mtrl'] - Class prefix
|
|
13
|
+
* @param {string} [config.type='component'] - Component type
|
|
14
|
+
* @param {HTMLElement} [config.beforeElement] - Element to insert before
|
|
15
|
+
* @returns {Object} Text manager interface
|
|
16
|
+
* @property {Function} setText - Sets text content
|
|
17
|
+
* @property {Function} getText - Gets current text
|
|
18
|
+
* @property {Function} getElement - Gets text element
|
|
19
|
+
*/
|
|
20
|
+
export const createText = (element, config = {}) => {
|
|
21
|
+
let textElement = null
|
|
22
|
+
const PREFIX = config.prefix || 'mtrl'
|
|
23
|
+
|
|
24
|
+
const createElement = (content) => {
|
|
25
|
+
const span = document.createElement('span')
|
|
26
|
+
span.className = `${PREFIX}-${config.type || 'component'}-text`
|
|
27
|
+
span.textContent = content
|
|
28
|
+
return span
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
setText (text) {
|
|
33
|
+
if (!textElement && text) {
|
|
34
|
+
textElement = createElement(text)
|
|
35
|
+
if (config.beforeElement) {
|
|
36
|
+
element.insertBefore(textElement, config.beforeElement)
|
|
37
|
+
} else {
|
|
38
|
+
element.appendChild(textElement)
|
|
39
|
+
}
|
|
40
|
+
} else if (textElement) {
|
|
41
|
+
textElement.textContent = text
|
|
42
|
+
}
|
|
43
|
+
return this
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
getText () {
|
|
47
|
+
return textElement ? textElement.textContent : ''
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
getElement () {
|
|
51
|
+
return textElement
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/core/collection/adapters/base.js
|
|
2
|
+
|
|
3
|
+
export const OPERATORS = {
|
|
4
|
+
EQ: 'eq',
|
|
5
|
+
NE: 'ne',
|
|
6
|
+
GT: 'gt',
|
|
7
|
+
GTE: 'gte',
|
|
8
|
+
LT: 'lt',
|
|
9
|
+
LTE: 'lte',
|
|
10
|
+
IN: 'in',
|
|
11
|
+
NIN: 'nin',
|
|
12
|
+
CONTAINS: 'contains',
|
|
13
|
+
STARTS_WITH: 'startsWith',
|
|
14
|
+
ENDS_WITH: 'endsWith'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const createBaseAdapter = ({ onError } = {}) => {
|
|
18
|
+
const handleError = (error, context) => {
|
|
19
|
+
onError?.(error, context)
|
|
20
|
+
throw error
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
handleError
|
|
25
|
+
}
|
|
26
|
+
}
|