@luanlu/mk-motion 1.1.0 → 1.2.1

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 (131) hide show
  1. package/package.json +14 -2
  2. package/src/a11y/focus-trap.ts +64 -0
  3. package/src/a11y/keyboard.ts +43 -0
  4. package/src/components/alert/alert.css +111 -0
  5. package/src/components/alert/alert.ts +107 -0
  6. package/src/components/avatar/avatar.css +112 -0
  7. package/src/components/avatar/avatar.ts +175 -0
  8. package/src/components/breadcrumb/breadcrumb.css +31 -0
  9. package/src/components/breadcrumb/breadcrumb.ts +71 -0
  10. package/src/components/button/button.css +108 -0
  11. package/src/components/button/button.ts +140 -0
  12. package/src/components/card/card.css +52 -0
  13. package/src/components/card/card.ts +87 -0
  14. package/src/components/collapse/collapse.css +76 -0
  15. package/src/components/collapse/collapse.ts +168 -0
  16. package/src/components/dialog/dialog.css +78 -0
  17. package/src/components/dialog/dialog.ts +164 -0
  18. package/src/components/drawer/drawer.css +73 -0
  19. package/src/components/drawer/drawer.ts +131 -0
  20. package/src/components/empty/empty.css +36 -0
  21. package/src/components/empty/empty.ts +85 -0
  22. package/src/components/form/checkbox.css +56 -0
  23. package/src/components/form/checkbox.ts +119 -0
  24. package/src/components/form/radio.css +57 -0
  25. package/src/components/form/radio.ts +153 -0
  26. package/src/components/form/select.css +91 -0
  27. package/src/components/form/select.ts +174 -0
  28. package/src/components/form/slider.css +56 -0
  29. package/src/components/form/slider.ts +148 -0
  30. package/src/components/input/input.css +92 -0
  31. package/src/components/input/input.ts +162 -0
  32. package/src/components/layout/divider.css +32 -0
  33. package/src/components/layout/divider.ts +42 -0
  34. package/src/components/layout/row.css +64 -0
  35. package/src/components/layout/row.ts +57 -0
  36. package/src/components/layout/space.css +14 -0
  37. package/src/components/layout/space.ts +48 -0
  38. package/src/components/loading/loading.css +37 -0
  39. package/src/components/loading/loading.ts +46 -0
  40. package/src/components/menu/menu.css +121 -0
  41. package/src/components/menu/menu.ts +187 -0
  42. package/src/components/message/message.css +64 -0
  43. package/src/components/message/message.ts +96 -0
  44. package/src/components/popover/popover.css +73 -0
  45. package/src/components/popover/popover.ts +279 -0
  46. package/src/components/progress/progress.css +112 -0
  47. package/src/components/progress/progress.ts +171 -0
  48. package/src/components/steps/steps.css +127 -0
  49. package/src/components/steps/steps.ts +102 -0
  50. package/src/components/styles/components.css +28 -0
  51. package/src/components/styles/reset.css +24 -0
  52. package/src/components/styles/tokens.css +248 -0
  53. package/src/components/styles/variables.css +24 -0
  54. package/src/components/switch/switch.css +53 -0
  55. package/src/components/switch/switch.ts +103 -0
  56. package/src/components/table/table.css +192 -0
  57. package/src/components/table/table.ts +370 -0
  58. package/src/components/tabs/tabs.css +138 -0
  59. package/src/components/tabs/tabs.ts +211 -0
  60. package/src/components/tag/tag.css +123 -0
  61. package/src/components/tag/tag.ts +112 -0
  62. package/src/components/tooltip/tooltip.css +66 -0
  63. package/src/components/tooltip/tooltip.ts +185 -0
  64. package/src/core/animator.ts +124 -0
  65. package/src/core/timeline.ts +128 -0
  66. package/src/core/utils.ts +47 -0
  67. package/src/effects/glitch.ts +99 -0
  68. package/src/effects/particle.ts +134 -0
  69. package/src/effects/text-split.ts +95 -0
  70. package/src/effects/wave-text.ts +88 -0
  71. package/src/gesture/draggable.ts +130 -0
  72. package/src/gesture/spring.ts +152 -0
  73. package/src/index.ts +162 -0
  74. package/src/interactive/coverflow.ts +100 -0
  75. package/src/interactive/cursor-trail.ts +113 -0
  76. package/src/interactive/flip-card.ts +114 -0
  77. package/src/interactive/magnetic.ts +121 -0
  78. package/src/micro/hover-lift.ts +94 -0
  79. package/src/micro/ripple.ts +130 -0
  80. package/src/motion/component-motion.ts +177 -0
  81. package/src/nuxt/module.ts +46 -0
  82. package/src/presets/index.ts +69 -0
  83. package/src/scroll/scroll-trigger.ts +104 -0
  84. package/src/styles/animations.css +135 -0
  85. package/src/styles/element-plus.css +174 -0
  86. package/src/text/count-up.ts +108 -0
  87. package/src/text/typewriter.ts +109 -0
  88. package/src/theme/dark.css +19 -0
  89. package/src/theme/light.css +19 -0
  90. package/src/theme/theme.ts +65 -0
  91. package/src/transitions/blur-reveal.ts +92 -0
  92. package/src/transitions/collapse.ts +112 -0
  93. package/src/transitions/lazy-image.ts +87 -0
  94. package/src/transitions/list.ts +75 -0
  95. package/src/transitions/loading.ts +95 -0
  96. package/src/transitions/parallax.ts +60 -0
  97. package/src/transitions/shimmer.ts +105 -0
  98. package/src/transitions/toast.ts +151 -0
  99. package/src/types.d.ts +4 -0
  100. package/src/vite/plugin.ts +45 -0
  101. package/src/vue/button.ts +28 -9
  102. package/src/vue/card.ts +28 -8
  103. package/src/vue/composables/index.ts +4 -0
  104. package/src/vue/composables/useLoading.ts +12 -0
  105. package/src/vue/composables/useMessage.ts +16 -0
  106. package/src/vue/composables/useMotion.ts +19 -0
  107. package/src/vue/composables/useTheme.ts +12 -0
  108. package/src/vue/dialog.ts +69 -17
  109. package/src/vue/index.ts +4 -21
  110. package/src/vue/input.ts +35 -11
  111. package/src/vue/slider.ts +22 -4
  112. package/src/vue/switch.ts +16 -9
  113. package/src/vue/alert.ts +0 -32
  114. package/src/vue/avatar.ts +0 -34
  115. package/src/vue/breadcrumb.ts +0 -32
  116. package/src/vue/checkbox.ts +0 -32
  117. package/src/vue/collapse.ts +0 -33
  118. package/src/vue/divider.ts +0 -32
  119. package/src/vue/drawer.ts +0 -33
  120. package/src/vue/empty.ts +0 -33
  121. package/src/vue/menu.ts +0 -33
  122. package/src/vue/popover.ts +0 -34
  123. package/src/vue/progress.ts +0 -33
  124. package/src/vue/row.ts +0 -32
  125. package/src/vue/select.ts +0 -33
  126. package/src/vue/space.ts +0 -32
  127. package/src/vue/steps.ts +0 -33
  128. package/src/vue/table.ts +0 -33
  129. package/src/vue/tabs.ts +0 -33
  130. package/src/vue/tag.ts +0 -33
  131. package/src/vue/tooltip.ts +0 -34
@@ -0,0 +1,119 @@
1
+ import './checkbox.css'
2
+ import { onKey, Keys } from '../../a11y/keyboard.ts'
3
+
4
+ export interface CheckboxOptions {
5
+ label?: string
6
+ checked?: boolean
7
+ disabled?: boolean
8
+ onChange?: (checked: boolean) => void
9
+ }
10
+
11
+ export class MkCheckbox {
12
+ el: HTMLLabelElement
13
+ private options: CheckboxOptions
14
+ private _checked: boolean
15
+ private _cleanupKey?: () => void
16
+
17
+ constructor(container: HTMLElement | string, options: CheckboxOptions = {}) {
18
+ const parent =
19
+ typeof container === 'string'
20
+ ? document.querySelector(container)!
21
+ : container
22
+
23
+ this.options = { checked: false, ...options }
24
+ this._checked = this.options.checked!
25
+
26
+ this.el = document.createElement('label')
27
+ this.el.className = 'mk-checkbox'
28
+ if (this._checked) this.el.classList.add('is-checked')
29
+ if (this.options.disabled) this.el.classList.add('is-disabled')
30
+ this.el.setAttribute('role', 'checkbox')
31
+ this.el.setAttribute('aria-checked', String(this._checked))
32
+ if (!this.options.disabled) {
33
+ this.el.setAttribute('tabindex', '0')
34
+ }
35
+
36
+ const box = document.createElement('span')
37
+ box.className = 'mk-checkbox__input'
38
+ const check = document.createElement('span')
39
+ check.className = 'mk-checkbox__check'
40
+ check.textContent = '✓'
41
+ box.appendChild(check)
42
+ this.el.appendChild(box)
43
+
44
+ if (this.options.label) {
45
+ const text = document.createElement('span')
46
+ text.textContent = this.options.label
47
+ this.el.appendChild(text)
48
+ }
49
+
50
+ this.el.addEventListener('click', () => {
51
+ if (this.options.disabled) return
52
+ this.toggle()
53
+ })
54
+
55
+ this._cleanupKey = onKey(this.el, [
56
+ { key: Keys.Space, handler: () => this.toggle() },
57
+ ])
58
+
59
+ parent.appendChild(this.el)
60
+ }
61
+
62
+ get checked(): boolean {
63
+ return this._checked
64
+ }
65
+
66
+ set checked(v: boolean) {
67
+ this._checked = v
68
+ this.el.classList.toggle('is-checked', v)
69
+ this.el.setAttribute('aria-checked', String(v))
70
+ this.options.onChange?.(v)
71
+ }
72
+
73
+ toggle(): void {
74
+ this.checked = !this._checked
75
+ }
76
+
77
+ destroy(): void {
78
+ this._cleanupKey?.()
79
+ this.el.remove()
80
+ }
81
+ }
82
+
83
+ export function createCheckbox(
84
+ container: HTMLElement | string,
85
+ options?: CheckboxOptions
86
+ ): MkCheckbox {
87
+ return new MkCheckbox(container, options)
88
+ }
89
+
90
+ /* ===== Checkbox Group ===== */
91
+ export class MkCheckboxGroup {
92
+ el: HTMLDivElement
93
+ private checkboxes: MkCheckbox[] = []
94
+
95
+ constructor(container: HTMLElement | string) {
96
+ const parent =
97
+ typeof container === 'string'
98
+ ? document.querySelector(container)!
99
+ : container
100
+
101
+ this.el = document.createElement('div')
102
+ this.el.className = 'mk-checkbox-group'
103
+ parent.appendChild(this.el)
104
+ }
105
+
106
+ add(options: CheckboxOptions): MkCheckbox {
107
+ const cb = new MkCheckbox(this.el, options)
108
+ this.checkboxes.push(cb)
109
+ return cb
110
+ }
111
+
112
+ getValues(): boolean[] {
113
+ return this.checkboxes.map((cb) => cb.checked)
114
+ }
115
+
116
+ destroy(): void {
117
+ this.el.remove()
118
+ }
119
+ }
@@ -0,0 +1,57 @@
1
+ .mk-radio {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 8px;
5
+ cursor: pointer;
6
+ user-select: none;
7
+ font-size: 13px;
8
+ color: var(--mk-text);
9
+ }
10
+
11
+ .mk-radio__input {
12
+ width: 16px;
13
+ height: 16px;
14
+ border: 1.5px solid var(--mk-border);
15
+ border-radius: 50%;
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ transition: var(--mk-transition);
20
+ flex-shrink: 0;
21
+ background: var(--mk-surface);
22
+ }
23
+
24
+ .mk-radio:hover .mk-radio__input {
25
+ border-color: var(--mk-border-hover);
26
+ }
27
+
28
+ .mk-radio.is-checked .mk-radio__input {
29
+ border-color: var(--mk-primary);
30
+ }
31
+
32
+ .mk-radio__dot {
33
+ width: 8px;
34
+ height: 8px;
35
+ border-radius: 50%;
36
+ background: var(--mk-primary);
37
+ opacity: 0;
38
+ transform: scale(0);
39
+ transition: all 0.15s ease;
40
+ }
41
+
42
+ .mk-radio.is-checked .mk-radio__dot {
43
+ opacity: 1;
44
+ transform: scale(1);
45
+ }
46
+
47
+ .mk-radio.is-disabled {
48
+ opacity: 0.4;
49
+ cursor: not-allowed;
50
+ }
51
+
52
+ /* Group */
53
+ .mk-radio-group {
54
+ display: flex;
55
+ flex-wrap: wrap;
56
+ gap: 16px;
57
+ }
@@ -0,0 +1,153 @@
1
+ import './radio.css'
2
+ import { onKey, Keys } from '../../a11y/keyboard.ts'
3
+
4
+ export interface RadioOptions {
5
+ label?: string
6
+ value: string | number
7
+ checked?: boolean
8
+ disabled?: boolean
9
+ onChange?: (value: string | number) => void
10
+ }
11
+
12
+ export class MkRadio {
13
+ el: HTMLLabelElement
14
+ private options: RadioOptions
15
+ private _checked: boolean
16
+
17
+ constructor(container: HTMLElement | string, options: RadioOptions) {
18
+ const parent =
19
+ typeof container === 'string'
20
+ ? document.querySelector(container)!
21
+ : container
22
+
23
+ this.options = { checked: false, ...options }
24
+ this._checked = this.options.checked!
25
+
26
+ this.el = document.createElement('label')
27
+ this.el.className = 'mk-radio'
28
+ if (this._checked) this.el.classList.add('is-checked')
29
+ if (this.options.disabled) this.el.classList.add('is-disabled')
30
+ this.el.setAttribute('role', 'radio')
31
+ this.el.setAttribute('aria-checked', String(this._checked))
32
+
33
+ const box = document.createElement('span')
34
+ box.className = 'mk-radio__input'
35
+ const dot = document.createElement('span')
36
+ dot.className = 'mk-radio__dot'
37
+ box.appendChild(dot)
38
+ this.el.appendChild(box)
39
+
40
+ if (this.options.label) {
41
+ const text = document.createElement('span')
42
+ text.textContent = this.options.label
43
+ this.el.appendChild(text)
44
+ }
45
+
46
+ this.el.addEventListener('click', () => {
47
+ if (this.options.disabled) return
48
+ this.options.onChange?.(this.options.value)
49
+ })
50
+
51
+ parent.appendChild(this.el)
52
+ }
53
+
54
+ setChecked(checked: boolean): void {
55
+ this._checked = checked
56
+ this.el.classList.toggle('is-checked', checked)
57
+ this.el.setAttribute('aria-checked', String(checked))
58
+ }
59
+
60
+ getValue(): string | number {
61
+ return this.options.value
62
+ }
63
+
64
+ destroy(): void {
65
+ this.el.remove()
66
+ }
67
+ }
68
+
69
+ /* ===== Radio Group ===== */
70
+ export class MkRadioGroup {
71
+ el: HTMLDivElement
72
+ private radios: MkRadio[] = []
73
+ private _value: string | number | undefined
74
+ private _cleanupKey?: () => void
75
+
76
+ constructor(
77
+ container: HTMLElement | string,
78
+ options: { value?: string | number; onChange?: (value: string | number) => void } = {}
79
+ ) {
80
+ const parent =
81
+ typeof container === 'string'
82
+ ? document.querySelector(container)!
83
+ : container
84
+
85
+ this._value = options.value
86
+
87
+ this.el = document.createElement('div')
88
+ this.el.className = 'mk-radio-group'
89
+ this.el.setAttribute('role', 'radiogroup')
90
+ parent.appendChild(this.el)
91
+
92
+ this._cleanupKey = onKey(this.el, [
93
+ { key: Keys.ArrowUp, handler: () => this.focusRadio(-1) },
94
+ { key: Keys.ArrowLeft, handler: () => this.focusRadio(-1) },
95
+ { key: Keys.ArrowDown, handler: () => this.focusRadio(1) },
96
+ { key: Keys.ArrowRight, handler: () => this.focusRadio(1) },
97
+ ])
98
+ }
99
+
100
+ add(radioOptions: Omit<RadioOptions, 'onChange'>): MkRadio {
101
+ const radio = new MkRadio(this.el, {
102
+ ...radioOptions,
103
+ onChange: (value) => {
104
+ this.setValue(value)
105
+ },
106
+ })
107
+ const checked = radioOptions.value === this._value
108
+ radio.setChecked(checked)
109
+ if (this._value === undefined && this.radios.length === 0) {
110
+ radio.el.setAttribute('tabindex', '0')
111
+ } else {
112
+ radio.el.setAttribute('tabindex', checked ? '0' : '-1')
113
+ }
114
+ this.radios.push(radio)
115
+ return radio
116
+ }
117
+
118
+ setValue(value: string | number): void {
119
+ this._value = value
120
+ this.radios.forEach((r) => {
121
+ const checked = r.getValue() === value
122
+ r.setChecked(checked)
123
+ r.el.setAttribute('tabindex', checked ? '0' : '-1')
124
+ })
125
+ }
126
+
127
+ getValue(): string | number | undefined {
128
+ return this._value
129
+ }
130
+
131
+ private focusRadio(dir: number): void {
132
+ let current = this.radios.findIndex((r) => r.el.getAttribute('tabindex') === '0')
133
+ if (current === -1) current = this.radios.findIndex((r) => r.el.classList.contains('is-checked'))
134
+ if (current === -1) current = 0
135
+ let next = current
136
+ for (let i = 0; i < this.radios.length; i++) {
137
+ next = (next + dir + this.radios.length) % this.radios.length
138
+ if (!this.radios[next].el.classList.contains('is-disabled')) {
139
+ break
140
+ }
141
+ }
142
+ if (next !== current || this.radios.length === 1) {
143
+ this.radios.forEach((r, i) => r.el.setAttribute('tabindex', i === next ? '0' : '-1'))
144
+ this.radios[next].el.focus()
145
+ this.setValue(this.radios[next].getValue())
146
+ }
147
+ }
148
+
149
+ destroy(): void {
150
+ this._cleanupKey?.()
151
+ this.el.remove()
152
+ }
153
+ }
@@ -0,0 +1,91 @@
1
+ .mk-select {
2
+ position: relative;
3
+ display: inline-flex;
4
+ width: 100%;
5
+ max-width: 320px;
6
+ }
7
+
8
+ .mk-select__trigger {
9
+ width: 100%;
10
+ height: 36px;
11
+ padding: 0 32px 0 12px;
12
+ font-size: 13px;
13
+ color: var(--mk-text);
14
+ background: var(--mk-surface);
15
+ border: 1px solid var(--mk-border);
16
+ border-radius: var(--mk-radius);
17
+ cursor: pointer;
18
+ display: flex;
19
+ align-items: center;
20
+ outline: none;
21
+ transition: var(--mk-transition);
22
+ user-select: none;
23
+ }
24
+
25
+ .mk-select__trigger:hover {
26
+ border-color: var(--mk-border-hover);
27
+ }
28
+
29
+ .mk-select__trigger.is-open {
30
+ border-color: var(--mk-primary);
31
+ }
32
+
33
+ .mk-select__arrow {
34
+ position: absolute;
35
+ right: 10px;
36
+ color: var(--mk-text-tertiary);
37
+ font-size: 10px;
38
+ transition: transform 0.2s;
39
+ }
40
+
41
+ .mk-select__trigger.is-open .mk-select__arrow {
42
+ transform: rotate(180deg);
43
+ }
44
+
45
+ .mk-select__dropdown {
46
+ position: absolute;
47
+ top: calc(100% + 4px);
48
+ left: 0;
49
+ right: 0;
50
+ background: var(--mk-surface);
51
+ border: 1px solid var(--mk-border);
52
+ border-radius: var(--mk-radius);
53
+ z-index: 100;
54
+ max-height: 240px;
55
+ overflow-y: auto;
56
+ opacity: 0;
57
+ transform: translateY(-4px);
58
+ pointer-events: none;
59
+ transition: all 0.15s ease;
60
+ box-shadow: var(--mk-shadow-lg);
61
+ }
62
+
63
+ .mk-select__dropdown.is-open {
64
+ opacity: 1;
65
+ transform: translateY(0);
66
+ pointer-events: auto;
67
+ }
68
+
69
+ .mk-select__option {
70
+ padding: 8px 12px;
71
+ font-size: 13px;
72
+ color: var(--mk-text);
73
+ cursor: pointer;
74
+ transition: var(--mk-transition);
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 8px;
78
+ }
79
+
80
+ .mk-select__option:hover {
81
+ background: var(--mk-surface-hover);
82
+ }
83
+
84
+ .mk-select__option.is-selected {
85
+ background: rgba(99,102,241,0.1);
86
+ color: var(--mk-primary);
87
+ }
88
+
89
+ .mk-select__placeholder {
90
+ color: var(--mk-text-tertiary);
91
+ }
@@ -0,0 +1,174 @@
1
+ import './select.css'
2
+ import { onKey, Keys } from '../../a11y/keyboard.ts'
3
+
4
+ export interface SelectOption {
5
+ label: string
6
+ value: string | number
7
+ disabled?: boolean
8
+ }
9
+
10
+ export interface SelectOptions {
11
+ placeholder?: string
12
+ options: SelectOption[]
13
+ value?: string | number
14
+ onChange?: (value: string | number) => void
15
+ }
16
+
17
+ export class MkSelect {
18
+ el: HTMLDivElement
19
+ private trigger: HTMLDivElement
20
+ private dropdown: HTMLDivElement
21
+ private options: SelectOptions
22
+ private _value: string | number | undefined
23
+ private isOpen = false
24
+ private _cleanupKey?: () => void
25
+
26
+ constructor(container: HTMLElement | string, options: SelectOptions) {
27
+ const parent =
28
+ typeof container === 'string'
29
+ ? document.querySelector(container)!
30
+ : container
31
+
32
+ this.options = options
33
+ this._value = options.value
34
+
35
+ this.el = document.createElement('div')
36
+ this.el.className = 'mk-select'
37
+
38
+ this.trigger = document.createElement('div')
39
+ this.trigger.className = 'mk-select__trigger'
40
+ this.trigger.setAttribute('role', 'combobox')
41
+ this.trigger.setAttribute('aria-haspopup', 'listbox')
42
+ this.trigger.setAttribute('aria-expanded', 'false')
43
+ this.trigger.setAttribute('tabindex', '0')
44
+ this.trigger.addEventListener('click', () => this.toggle())
45
+
46
+ const label = document.createElement('span')
47
+ label.className = 'mk-select__label'
48
+ this.updateLabel(label)
49
+ this.trigger.appendChild(label)
50
+
51
+ const arrow = document.createElement('span')
52
+ arrow.className = 'mk-select__arrow'
53
+ arrow.textContent = '▼'
54
+ this.trigger.appendChild(arrow)
55
+
56
+ this.dropdown = document.createElement('div')
57
+ this.dropdown.className = 'mk-select__dropdown'
58
+ this.dropdown.setAttribute('role', 'listbox')
59
+
60
+ options.options.forEach((opt) => {
61
+ const item = document.createElement('div')
62
+ item.className = 'mk-select__option'
63
+ item.textContent = opt.label
64
+ item.setAttribute('role', 'option')
65
+ item.setAttribute('aria-selected', String(opt.value === this._value))
66
+ if (opt.disabled) {
67
+ item.setAttribute('aria-disabled', 'true')
68
+ item.style.opacity = '0.4'
69
+ item.style.pointerEvents = 'none'
70
+ }
71
+ item.addEventListener('click', () => {
72
+ this.setValue(opt.value)
73
+ this.close()
74
+ })
75
+ this.dropdown.appendChild(item)
76
+ })
77
+
78
+ this.el.appendChild(this.trigger)
79
+ this.el.appendChild(this.dropdown)
80
+ parent.appendChild(this.el)
81
+
82
+ this._cleanupKey = onKey(this.trigger, [
83
+ { key: Keys.ArrowDown, handler: () => this.moveSelection(1) },
84
+ { key: Keys.ArrowUp, handler: () => this.moveSelection(-1) },
85
+ { key: Keys.Enter, handler: () => {
86
+ if (this.isOpen) this.close()
87
+ else this.open()
88
+ }},
89
+ { key: Keys.Escape, handler: () => this.close() },
90
+ ])
91
+
92
+ document.addEventListener('click', (e) => {
93
+ if (!this.el.contains(e.target as Node)) this.close()
94
+ })
95
+ }
96
+
97
+ private updateLabel(labelEl: HTMLSpanElement): void {
98
+ const selected = this.options.options.find((o) => o.value === this._value)
99
+ if (selected) {
100
+ labelEl.textContent = selected.label
101
+ labelEl.classList.remove('mk-select__placeholder')
102
+ } else {
103
+ labelEl.textContent = this.options.placeholder || '请选择'
104
+ labelEl.classList.add('mk-select__placeholder')
105
+ }
106
+ }
107
+
108
+ get value(): string | number | undefined {
109
+ return this._value
110
+ }
111
+
112
+ setValue(v: string | number): void {
113
+ this._value = v
114
+ const label = this.trigger.querySelector('.mk-select__label') as HTMLSpanElement
115
+ this.updateLabel(label)
116
+ this.renderOptions()
117
+ this.options.onChange?.(v)
118
+ }
119
+
120
+ private renderOptions(): void {
121
+ const items = this.dropdown.querySelectorAll('.mk-select__option')
122
+ items.forEach((item, i) => {
123
+ const opt = this.options.options[i]
124
+ item.classList.toggle('is-selected', opt.value === this._value)
125
+ item.setAttribute('aria-selected', String(opt.value === this._value))
126
+ })
127
+ }
128
+
129
+ private toggle(): void {
130
+ this.isOpen ? this.close() : this.open()
131
+ }
132
+
133
+ open(): void {
134
+ this.isOpen = true
135
+ this.trigger.setAttribute('aria-expanded', 'true')
136
+ this.trigger.classList.add('is-open')
137
+ this.dropdown.classList.add('is-open')
138
+ this.renderOptions()
139
+ }
140
+
141
+ close(): void {
142
+ this.isOpen = false
143
+ this.trigger.setAttribute('aria-expanded', 'false')
144
+ this.trigger.classList.remove('is-open')
145
+ this.dropdown.classList.remove('is-open')
146
+ }
147
+
148
+ private moveSelection(dir: number): void {
149
+ if (!this.isOpen) {
150
+ this.open()
151
+ }
152
+ const currentIndex = this.options.options.findIndex((o) => o.value === this._value)
153
+ let next = currentIndex
154
+ for (let i = 0; i < this.options.options.length; i++) {
155
+ next = (next + dir + this.options.options.length) % this.options.options.length
156
+ if (!this.options.options[next].disabled) {
157
+ this.setValue(this.options.options[next].value)
158
+ return
159
+ }
160
+ }
161
+ }
162
+
163
+ destroy(): void {
164
+ this._cleanupKey?.()
165
+ this.el.remove()
166
+ }
167
+ }
168
+
169
+ export function createSelect(
170
+ container: HTMLElement | string,
171
+ options: SelectOptions
172
+ ): MkSelect {
173
+ return new MkSelect(container, options)
174
+ }
@@ -0,0 +1,56 @@
1
+ .mk-slider {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 12px;
5
+ width: 100%;
6
+ max-width: 320px;
7
+ }
8
+
9
+ .mk-slider__track {
10
+ position: relative;
11
+ flex: 1;
12
+ height: 4px;
13
+ background: var(--mk-border);
14
+ border-radius: 2px;
15
+ cursor: pointer;
16
+ }
17
+
18
+ .mk-slider__fill {
19
+ position: absolute;
20
+ left: 0;
21
+ top: 0;
22
+ height: 100%;
23
+ background: var(--mk-primary);
24
+ border-radius: 2px;
25
+ pointer-events: none;
26
+ }
27
+
28
+ .mk-slider__thumb {
29
+ position: absolute;
30
+ top: 50%;
31
+ width: 16px;
32
+ height: 16px;
33
+ background: var(--mk-primary);
34
+ border: 2px solid var(--mk-surface);
35
+ border-radius: 50%;
36
+ transform: translate(-50%, -50%);
37
+ cursor: grab;
38
+ transition: transform 0.15s, box-shadow 0.15s;
39
+ box-shadow: var(--mk-shadow-sm);
40
+ }
41
+
42
+ .mk-slider__thumb:hover {
43
+ transform: translate(-50%, -50%) scale(1.2);
44
+ }
45
+
46
+ .mk-slider__thumb:active {
47
+ cursor: grabbing;
48
+ }
49
+
50
+ .mk-slider__value {
51
+ font-size: 13px;
52
+ color: var(--mk-text-secondary);
53
+ min-width: 32px;
54
+ text-align: right;
55
+ font-variant-numeric: tabular-nums;
56
+ }