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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/index.js +10 -0
  4. package/package.json +17 -0
  5. package/src/components/button/api.js +54 -0
  6. package/src/components/button/button.js +81 -0
  7. package/src/components/button/config.js +8 -0
  8. package/src/components/button/constants.js +63 -0
  9. package/src/components/button/index.js +2 -0
  10. package/src/components/button/styles.scss +231 -0
  11. package/src/components/checkbox/api.js +45 -0
  12. package/src/components/checkbox/checkbox.js +95 -0
  13. package/src/components/checkbox/constants.js +88 -0
  14. package/src/components/checkbox/index.js +2 -0
  15. package/src/components/checkbox/styles.scss +183 -0
  16. package/src/components/container/api.js +42 -0
  17. package/src/components/container/container.js +45 -0
  18. package/src/components/container/index.js +2 -0
  19. package/src/components/container/styles.scss +59 -0
  20. package/src/components/list/constants.js +89 -0
  21. package/src/components/list/index.js +2 -0
  22. package/src/components/list/list-item.js +147 -0
  23. package/src/components/list/list.js +267 -0
  24. package/src/components/list/styles/_list-item.scss +142 -0
  25. package/src/components/list/styles/_list.scss +89 -0
  26. package/src/components/list/styles/_variables.scss +13 -0
  27. package/src/components/list/styles.scss +19 -0
  28. package/src/components/navigation/api.js +43 -0
  29. package/src/components/navigation/constants.js +235 -0
  30. package/src/components/navigation/features/items.js +192 -0
  31. package/src/components/navigation/index.js +2 -0
  32. package/src/components/navigation/nav-item.js +137 -0
  33. package/src/components/navigation/navigation.js +55 -0
  34. package/src/components/navigation/styles/_bar.scss +51 -0
  35. package/src/components/navigation/styles/_base.scss +129 -0
  36. package/src/components/navigation/styles/_drawer.scss +169 -0
  37. package/src/components/navigation/styles/_rail.scss +65 -0
  38. package/src/components/navigation/styles.scss +6 -0
  39. package/src/components/snackbar/api.js +125 -0
  40. package/src/components/snackbar/constants.js +41 -0
  41. package/src/components/snackbar/features.js +69 -0
  42. package/src/components/snackbar/index.js +2 -0
  43. package/src/components/snackbar/position.js +63 -0
  44. package/src/components/snackbar/queue.js +74 -0
  45. package/src/components/snackbar/snackbar.js +70 -0
  46. package/src/components/snackbar/styles.scss +182 -0
  47. package/src/components/switch/api.js +44 -0
  48. package/src/components/switch/constants.js +80 -0
  49. package/src/components/switch/index.js +2 -0
  50. package/src/components/switch/styles.scss +172 -0
  51. package/src/components/switch/switch.js +71 -0
  52. package/src/components/textfield/api.js +49 -0
  53. package/src/components/textfield/constants.js +81 -0
  54. package/src/components/textfield/index.js +2 -0
  55. package/src/components/textfield/styles/base.scss +107 -0
  56. package/src/components/textfield/styles/filled.scss +58 -0
  57. package/src/components/textfield/styles/outlined.scss +66 -0
  58. package/src/components/textfield/styles.scss +6 -0
  59. package/src/components/textfield/textfield.js +68 -0
  60. package/src/core/build/constants.js +51 -0
  61. package/src/core/build/icon.js +78 -0
  62. package/src/core/build/ripple.js +92 -0
  63. package/src/core/build/text.js +54 -0
  64. package/src/core/collection/adapters/base.js +26 -0
  65. package/src/core/collection/adapters/mongodb.js +232 -0
  66. package/src/core/collection/adapters/route.js +201 -0
  67. package/src/core/collection/collection.js +259 -0
  68. package/src/core/collection/list-manager.js +157 -0
  69. package/src/core/compose/base.js +8 -0
  70. package/src/core/compose/component.js +225 -0
  71. package/src/core/compose/features/checkable.js +114 -0
  72. package/src/core/compose/features/disabled.js +25 -0
  73. package/src/core/compose/features/events.js +48 -0
  74. package/src/core/compose/features/icon.js +33 -0
  75. package/src/core/compose/features/index.js +20 -0
  76. package/src/core/compose/features/input.js +92 -0
  77. package/src/core/compose/features/lifecycle.js +69 -0
  78. package/src/core/compose/features/position.js +60 -0
  79. package/src/core/compose/features/ripple.js +32 -0
  80. package/src/core/compose/features/size.js +9 -0
  81. package/src/core/compose/features/style.js +12 -0
  82. package/src/core/compose/features/text.js +17 -0
  83. package/src/core/compose/features/textinput.js +118 -0
  84. package/src/core/compose/features/textlabel.js +28 -0
  85. package/src/core/compose/features/track.js +49 -0
  86. package/src/core/compose/features/variant.js +9 -0
  87. package/src/core/compose/features/withEvents.js +67 -0
  88. package/src/core/compose/index.js +16 -0
  89. package/src/core/compose/pipe.js +69 -0
  90. package/src/core/config.js +140 -0
  91. package/src/core/dom/attributes.js +33 -0
  92. package/src/core/dom/classes.js +70 -0
  93. package/src/core/dom/create.js +133 -0
  94. package/src/core/dom/events.js +175 -0
  95. package/src/core/dom/index.js +5 -0
  96. package/src/core/dom/utils.js +22 -0
  97. package/src/core/index.js +23 -0
  98. package/src/core/layout/index.js +93 -0
  99. package/src/core/state/disabled.js +14 -0
  100. package/src/core/state/emitter.js +63 -0
  101. package/src/core/state/events.js +29 -0
  102. package/src/core/state/index.js +6 -0
  103. package/src/core/state/lifecycle.js +64 -0
  104. package/src/core/state/store.js +112 -0
  105. package/src/core/utils/index.js +39 -0
  106. package/src/core/utils/mobile.js +74 -0
  107. package/src/core/utils/object.js +22 -0
  108. package/src/core/utils/validate.js +37 -0
  109. package/src/index.js +11 -0
  110. package/src/styles/abstract/_base.scss +2 -0
  111. package/src/styles/abstract/_config.scss +28 -0
  112. package/src/styles/abstract/_functions.scss +124 -0
  113. package/src/styles/abstract/_mixins.scss +261 -0
  114. package/src/styles/abstract/_variables.scss +158 -0
  115. package/src/styles/main.scss +78 -0
  116. package/src/styles/themes/_base-theme.scss +49 -0
  117. package/src/styles/themes/_baseline.scss +90 -0
  118. package/src/styles/themes/_forest.scss +71 -0
  119. package/src/styles/themes/_index.scss +6 -0
  120. package/src/styles/themes/_ocean.scss +71 -0
  121. 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,6 @@
1
+ // src/components/textfield/styles.scss
2
+ @use 'sass:map';
3
+ @use '../../styles/abstract/config' as c;
4
+ @use 'styles/base';
5
+ @use 'styles/filled';
6
+ @use 'styles/outlined';
@@ -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
+ }