zhimo-ui 0.1.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/src/base.js ADDED
@@ -0,0 +1,585 @@
1
+ /* ZhiMo UI 基础组件:zhimo-button / zhimo-button-group / zhimo-input / zhimo-switch / zhimo-checkbox / zhimo-slider */
2
+
3
+ class ZhimoButton extends HTMLElement {
4
+ static observedAttributes = ['disabled', 'loading'];
5
+
6
+ constructor() {
7
+ super();
8
+ this.attachShadow({ mode: 'open' });
9
+ this.shadowRoot.innerHTML = `
10
+ <style>
11
+ :host { display: inline-block; }
12
+ :host([block]) { display: block; }
13
+ :host([disabled]), :host([loading]) { pointer-events: none; }
14
+
15
+ button {
16
+ font-family: var(--zhimo-font);
17
+ font-size: 14px;
18
+ font-weight: 500;
19
+ line-height: 1;
20
+ display: inline-flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ gap: 8px;
24
+ width: 100%;
25
+ box-sizing: border-box;
26
+ height: 36px;
27
+ padding: 0 16px;
28
+ border: 1px solid var(--zhimo-accent);
29
+ border-radius: var(--zhimo-radius-sm);
30
+ background: var(--zhimo-accent);
31
+ color: var(--zhimo-accent-fg);
32
+ cursor: pointer;
33
+ transition: background var(--zhimo-transition), border-color var(--zhimo-transition),
34
+ color var(--zhimo-transition), box-shadow var(--zhimo-transition);
35
+ }
36
+ button:hover { background: var(--zhimo-accent-hover); border-color: var(--zhimo-accent-hover); }
37
+ button:focus-visible { outline: none; box-shadow: var(--zhimo-focus-ring); }
38
+ button:active { transform: translateY(0.5px); }
39
+
40
+ :host([variant="secondary"]) button {
41
+ background: transparent;
42
+ border-color: var(--zhimo-fg);
43
+ color: var(--zhimo-fg);
44
+ }
45
+ :host([variant="secondary"]) button:hover {
46
+ background: var(--zhimo-bg-hover);
47
+ }
48
+
49
+ /* 幽灵按钮:hover 时朱砂下划线从左游走出来 */
50
+ :host([variant="ghost"]) button {
51
+ background: linear-gradient(var(--zhimo-seal), var(--zhimo-seal)) no-repeat;
52
+ background-size: 0% 1px;
53
+ background-position: 16px calc(100% - 7px);
54
+ border-color: transparent;
55
+ color: var(--zhimo-fg-muted);
56
+ transition: background-size var(--zhimo-transition), color var(--zhimo-transition);
57
+ }
58
+ :host([variant="ghost"]) button:hover {
59
+ background-size: calc(100% - 32px) 1px;
60
+ color: var(--zhimo-fg);
61
+ }
62
+
63
+ :host([variant="danger"]) button {
64
+ background: var(--zhimo-danger);
65
+ border-color: var(--zhimo-danger);
66
+ color: #fff;
67
+ }
68
+ :host([variant="danger"]) button:hover { filter: brightness(1.08); }
69
+
70
+ :host([size="sm"]) button { height: 30px; padding: 0 12px; font-size: 13px; }
71
+ :host([size="lg"]) button { height: 44px; padding: 0 22px; font-size: 15px; border-radius: var(--zhimo-radius); }
72
+
73
+ :host([disabled]) button { opacity: 0.5; cursor: not-allowed; }
74
+
75
+ .spinner {
76
+ display: none;
77
+ width: 14px;
78
+ height: 14px;
79
+ box-sizing: border-box;
80
+ border: 2px solid currentColor;
81
+ border-top-color: transparent;
82
+ border-radius: 50%;
83
+ animation: zhimo-spin 0.6s linear infinite;
84
+ }
85
+ :host([loading]) .spinner { display: inline-block; }
86
+ @keyframes zhimo-spin { to { transform: rotate(360deg); } }
87
+ </style>
88
+ <button part="button"><span class="spinner"></span><slot></slot></button>
89
+ `;
90
+ this._btn = this.shadowRoot.querySelector('button');
91
+ }
92
+
93
+ attributeChangedCallback() {
94
+ this._btn.disabled = this.hasAttribute('disabled') || this.hasAttribute('loading');
95
+ }
96
+ }
97
+
98
+ class ZhimoInput extends HTMLElement {
99
+ static observedAttributes = [
100
+ 'label', 'placeholder', 'type', 'disabled', 'error', 'value', 'rows',
101
+ 'min', 'max', 'step', 'maxlength',
102
+ ];
103
+
104
+ constructor() {
105
+ super();
106
+ this.attachShadow({ mode: 'open' });
107
+ this.shadowRoot.innerHTML = `
108
+ <style>
109
+ :host { display: block; font-family: var(--zhimo-font); }
110
+ label {
111
+ display: block;
112
+ font-family: var(--zhimo-font-serif);
113
+ font-size: 13px;
114
+ font-weight: 600;
115
+ color: var(--zhimo-fg);
116
+ margin-bottom: 4px;
117
+ }
118
+ label:empty { display: none; }
119
+ /* 稿纸式输入框:只有一条底线,聚焦时变朱砂色 */
120
+ input, textarea {
121
+ font-family: inherit;
122
+ font-size: 14px;
123
+ width: 100%;
124
+ box-sizing: border-box;
125
+ height: 34px;
126
+ padding: 0 2px;
127
+ color: var(--zhimo-fg);
128
+ background: transparent;
129
+ border: none;
130
+ border-bottom: 1px solid var(--zhimo-border-strong);
131
+ border-radius: 0;
132
+ transition: border-color var(--zhimo-transition), box-shadow var(--zhimo-transition);
133
+ }
134
+ textarea {
135
+ height: auto;
136
+ min-height: 68px;
137
+ padding: 6px 2px;
138
+ line-height: 1.5;
139
+ resize: vertical;
140
+ }
141
+ input::placeholder, textarea::placeholder { color: var(--zhimo-fg-muted); }
142
+ input:hover:not(:disabled), textarea:hover:not(:disabled) { border-bottom-color: var(--zhimo-fg); }
143
+ input:focus, textarea:focus {
144
+ outline: none;
145
+ border-bottom-color: var(--zhimo-seal);
146
+ box-shadow: 0 1px 0 var(--zhimo-seal);
147
+ }
148
+ input:disabled, textarea:disabled { color: var(--zhimo-fg-muted); border-bottom-style: dashed; cursor: not-allowed; }
149
+ :host([error]) input, :host([error]) textarea { border-bottom-color: var(--zhimo-danger); }
150
+ :host([error]) input:focus, :host([error]) textarea:focus { box-shadow: 0 1px 0 var(--zhimo-danger); }
151
+ .error { font-size: 12px; color: var(--zhimo-danger); margin-top: 6px; }
152
+ .error:empty { display: none; }
153
+ </style>
154
+ <label part="label"></label>
155
+ <input part="input">
156
+ <div class="error" part="error"></div>
157
+ `;
158
+ this._label = this.shadowRoot.querySelector('label');
159
+ this._input = this.shadowRoot.querySelector('input');
160
+ this._error = this.shadowRoot.querySelector('.error');
161
+ }
162
+
163
+ attributeChangedCallback(name, _old, val) {
164
+ switch (name) {
165
+ case 'label': this._label.textContent = val ?? ''; break;
166
+ case 'placeholder': this._input.placeholder = val ?? ''; break;
167
+ case 'type': this._setFieldKind(val); break;
168
+ case 'disabled': this._input.disabled = val !== null; break;
169
+ case 'error': this._error.textContent = val ?? ''; break;
170
+ case 'value': if (this._input.value !== val) this._input.value = val ?? ''; break;
171
+ case 'rows': if (this._input.tagName === 'TEXTAREA') this._input.rows = Number(val) || 3; break;
172
+ default: // min / max / step / maxlength fall through to the native field
173
+ val == null ? this._input.removeAttribute(name) : this._input.setAttribute(name, val);
174
+ }
175
+ }
176
+
177
+ /* type="textarea" 时换成多行稿纸,其余值落到原生 input type 上 */
178
+ _setFieldKind(type) {
179
+ const wantTextarea = type === 'textarea';
180
+ if (wantTextarea !== (this._input.tagName === 'TEXTAREA')) {
181
+ const next = document.createElement(wantTextarea ? 'textarea' : 'input');
182
+ next.setAttribute('part', 'input');
183
+ next.value = this._input.value;
184
+ next.placeholder = this._input.placeholder;
185
+ next.disabled = this._input.disabled;
186
+ for (const attr of ['min', 'max', 'step', 'maxlength']) {
187
+ const v = this.getAttribute(attr);
188
+ if (v != null) next.setAttribute(attr, v);
189
+ }
190
+ this._input.replaceWith(next);
191
+ this._input = next;
192
+ }
193
+ if (wantTextarea) this._input.rows = Number(this.getAttribute('rows')) || 3;
194
+ else this._input.type = type ?? 'text';
195
+ }
196
+
197
+ get value() { return this._input.value; }
198
+ set value(v) { this._input.value = v; }
199
+
200
+ focus() { this._input.focus(); }
201
+ }
202
+
203
+ class ZhimoSwitch extends HTMLElement {
204
+ static observedAttributes = ['checked', 'disabled'];
205
+
206
+ constructor() {
207
+ super();
208
+ this.attachShadow({ mode: 'open' });
209
+ this.shadowRoot.innerHTML = `
210
+ <style>
211
+ :host {
212
+ display: inline-flex;
213
+ align-items: center;
214
+ gap: 8px;
215
+ font-family: var(--zhimo-font);
216
+ font-size: 14px;
217
+ color: var(--zhimo-fg);
218
+ cursor: pointer;
219
+ user-select: none;
220
+ }
221
+ :host([disabled]) { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
222
+ .track {
223
+ position: relative;
224
+ flex: none;
225
+ width: 34px;
226
+ height: 18px;
227
+ box-sizing: border-box;
228
+ border-radius: var(--zhimo-radius-sm);
229
+ border: 1px solid var(--zhimo-border-strong);
230
+ background: var(--zhimo-bg-subtle);
231
+ transition: background var(--zhimo-transition), border-color var(--zhimo-transition);
232
+ }
233
+ .thumb {
234
+ position: absolute;
235
+ top: 2px;
236
+ left: 2px;
237
+ width: 12px;
238
+ height: 12px;
239
+ border-radius: 1px;
240
+ background: var(--zhimo-border-strong);
241
+ transition: transform var(--zhimo-transition), background var(--zhimo-transition);
242
+ }
243
+ :host([checked]) .track { border-color: var(--zhimo-seal); background: var(--zhimo-seal); }
244
+ :host([checked]) .thumb { transform: translateX(16px); background: var(--zhimo-bg); }
245
+ :host(:focus-visible) { outline: none; }
246
+ :host(:focus-visible) .track { box-shadow: var(--zhimo-focus-ring); }
247
+ </style>
248
+ <span class="track" part="track"><span class="thumb" part="thumb"></span></span><slot></slot>
249
+ `;
250
+ }
251
+
252
+ connectedCallback() {
253
+ this.setAttribute('role', 'switch');
254
+ if (!this.hasAttribute('tabindex')) this.tabIndex = 0;
255
+ this._syncAria();
256
+ this.addEventListener('click', this._onToggle);
257
+ this.addEventListener('keydown', this._onKeydown);
258
+ }
259
+
260
+ disconnectedCallback() {
261
+ this.removeEventListener('click', this._onToggle);
262
+ this.removeEventListener('keydown', this._onKeydown);
263
+ }
264
+
265
+ attributeChangedCallback() { this._syncAria(); }
266
+
267
+ _syncAria() { this.setAttribute('aria-checked', String(this.checked)); }
268
+
269
+ _onToggle = () => {
270
+ this.checked = !this.checked;
271
+ this.dispatchEvent(new CustomEvent('change', {
272
+ detail: { checked: this.checked }, bubbles: true, composed: true,
273
+ }));
274
+ };
275
+
276
+ _onKeydown = (e) => {
277
+ if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._onToggle(); }
278
+ };
279
+
280
+ get checked() { return this.hasAttribute('checked'); }
281
+ set checked(v) { this.toggleAttribute('checked', Boolean(v)); }
282
+ }
283
+
284
+ class ZhimoCheckbox extends HTMLElement {
285
+ static observedAttributes = ['checked', 'disabled'];
286
+
287
+ constructor() {
288
+ super();
289
+ this.attachShadow({ mode: 'open' });
290
+ this.shadowRoot.innerHTML = `
291
+ <style>
292
+ :host {
293
+ display: inline-flex;
294
+ align-items: center;
295
+ gap: 8px;
296
+ font-family: var(--zhimo-font);
297
+ font-size: 14px;
298
+ color: var(--zhimo-fg);
299
+ cursor: pointer;
300
+ user-select: none;
301
+ }
302
+ :host([disabled]) { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
303
+ .box {
304
+ flex: none;
305
+ display: inline-flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ width: 16px;
309
+ height: 16px;
310
+ box-sizing: border-box;
311
+ border: 1px solid var(--zhimo-border-strong);
312
+ border-radius: var(--zhimo-radius-sm);
313
+ background: transparent;
314
+ transition: background var(--zhimo-transition), border-color var(--zhimo-transition);
315
+ }
316
+ svg { width: 10px; height: 10px; stroke: #fff; stroke-width: 3; fill: none; opacity: 0; transition: opacity var(--zhimo-transition); }
317
+ :host([checked]) .box { background: var(--zhimo-seal); border-color: var(--zhimo-seal); }
318
+ :host([checked]) svg { opacity: 1; }
319
+ :host(:focus-visible) { outline: none; }
320
+ :host(:focus-visible) .box { box-shadow: var(--zhimo-focus-ring); }
321
+ </style>
322
+ <span class="box" part="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span><slot></slot>
323
+ `;
324
+ }
325
+
326
+ connectedCallback() {
327
+ this.setAttribute('role', 'checkbox');
328
+ if (!this.hasAttribute('tabindex')) this.tabIndex = 0;
329
+ this._syncAria();
330
+ this.addEventListener('click', this._onToggle);
331
+ this.addEventListener('keydown', this._onKeydown);
332
+ }
333
+
334
+ disconnectedCallback() {
335
+ this.removeEventListener('click', this._onToggle);
336
+ this.removeEventListener('keydown', this._onKeydown);
337
+ }
338
+
339
+ attributeChangedCallback() { this._syncAria(); }
340
+
341
+ _syncAria() { this.setAttribute('aria-checked', String(this.checked)); }
342
+
343
+ _onToggle = () => {
344
+ this.checked = !this.checked;
345
+ this.dispatchEvent(new CustomEvent('change', {
346
+ detail: { checked: this.checked }, bubbles: true, composed: true,
347
+ }));
348
+ };
349
+
350
+ _onKeydown = (e) => {
351
+ if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this._onToggle(); }
352
+ };
353
+
354
+ get checked() { return this.hasAttribute('checked'); }
355
+ set checked(v) { this.toggleAttribute('checked', Boolean(v)); }
356
+ }
357
+
358
+ class ZhimoSlider extends HTMLElement {
359
+ static observedAttributes = ['min', 'max', 'step', 'value', 'disabled'];
360
+
361
+ constructor() {
362
+ super();
363
+ this.attachShadow({ mode: 'open' });
364
+ this.shadowRoot.innerHTML = `
365
+ <style>
366
+ :host { display: block; }
367
+ :host([disabled]) { opacity: 0.5; pointer-events: none; }
368
+ /* 墨线轨道 + 朱砂方印滑块头 */
369
+ input {
370
+ -webkit-appearance: none;
371
+ appearance: none;
372
+ display: block;
373
+ width: 100%;
374
+ height: 14px;
375
+ margin: 0;
376
+ background: transparent;
377
+ cursor: pointer;
378
+ }
379
+ input::-webkit-slider-runnable-track {
380
+ height: 1.5px;
381
+ border-radius: 0;
382
+ background: linear-gradient(var(--zhimo-seal), var(--zhimo-seal)) no-repeat var(--zhimo-border-strong);
383
+ background-size: var(--fill, 0%) 100%;
384
+ }
385
+ input::-moz-range-track {
386
+ height: 1.5px;
387
+ border-radius: 0;
388
+ background: var(--zhimo-border-strong);
389
+ border: none;
390
+ }
391
+ input::-moz-range-progress {
392
+ height: 1.5px;
393
+ border-radius: 0;
394
+ background: var(--zhimo-seal);
395
+ }
396
+ input::-webkit-slider-thumb {
397
+ -webkit-appearance: none;
398
+ width: 10px;
399
+ height: 10px;
400
+ margin-top: -4.5px;
401
+ border-radius: var(--zhimo-radius-sm);
402
+ background: var(--zhimo-seal);
403
+ border: none;
404
+ transition: background var(--zhimo-transition);
405
+ }
406
+ input::-moz-range-thumb {
407
+ width: 10px;
408
+ height: 10px;
409
+ border-radius: var(--zhimo-radius-sm);
410
+ background: var(--zhimo-seal);
411
+ border: none;
412
+ }
413
+ input:hover::-webkit-slider-thumb { background: var(--zhimo-seal-hover); }
414
+ input:hover::-moz-range-thumb { background: var(--zhimo-seal-hover); }
415
+ input:focus-visible { outline: none; }
416
+ input:focus-visible::-webkit-slider-thumb { box-shadow: var(--zhimo-focus-ring); }
417
+ input:focus-visible::-moz-range-thumb { box-shadow: var(--zhimo-focus-ring); }
418
+ </style>
419
+ <input type="range" part="input">
420
+ `;
421
+ this._input = this.shadowRoot.querySelector('input');
422
+ this._input.addEventListener('input', (e) => {
423
+ e.stopPropagation();
424
+ this._syncFill();
425
+ this.dispatchEvent(new CustomEvent('input', {
426
+ detail: { value: Number(this._input.value) }, bubbles: true, composed: true,
427
+ }));
428
+ });
429
+ this._input.addEventListener('change', (e) => {
430
+ e.stopPropagation();
431
+ this.dispatchEvent(new CustomEvent('change', {
432
+ detail: { value: Number(this._input.value) }, bubbles: true, composed: true,
433
+ }));
434
+ });
435
+ }
436
+
437
+ connectedCallback() { this._syncFill(); }
438
+
439
+ attributeChangedCallback(name, _old, val) {
440
+ if (name === 'disabled') this._input.disabled = val !== null;
441
+ else if (name === 'value') { if (this._input.value !== val) this._input.value = val ?? '0'; }
442
+ else this._input[name] = val ?? '';
443
+ this._syncFill();
444
+ }
445
+
446
+ _syncFill() {
447
+ const min = Number(this._input.min) || 0;
448
+ const max = Number(this._input.max || 100);
449
+ const pct = ((Number(this._input.value) - min) / (max - min || 1)) * 100;
450
+ this._input.style.setProperty('--fill', `${Math.max(0, Math.min(100, pct))}%`);
451
+ }
452
+
453
+ get value() { return Number(this._input.value); }
454
+ set value(v) { this._input.value = String(v); this._syncFill(); }
455
+ }
456
+
457
+ class ZhimoButtonGroup extends HTMLElement {
458
+ static observedAttributes = ['value', 'disabled'];
459
+
460
+ constructor() {
461
+ super();
462
+ this.attachShadow({ mode: 'open' });
463
+ this.shadowRoot.innerHTML = `
464
+ <style>
465
+ :host { display: inline-flex; }
466
+ :host([block]) { display: flex; }
467
+ :host([vertical]) { flex-direction: column; }
468
+ :host([disabled]) { opacity: 0.5; pointer-events: none; }
469
+ ::slotted(button) {
470
+ font-family: var(--zhimo-font);
471
+ font-size: 14px;
472
+ font-weight: 500;
473
+ line-height: 1;
474
+ height: 34px;
475
+ padding: 0 16px;
476
+ border: 1px solid var(--zhimo-border-strong);
477
+ margin-left: -1px;
478
+ margin-top: 0;
479
+ border-radius: 0;
480
+ background: transparent;
481
+ color: var(--zhimo-fg);
482
+ cursor: pointer;
483
+ position: relative;
484
+ transition: background var(--zhimo-transition), color var(--zhimo-transition),
485
+ border-color var(--zhimo-transition);
486
+ }
487
+ :host([block]) ::slotted(button) { flex: 1; }
488
+ :host([vertical]) ::slotted(button) { margin-left: 0; margin-top: -1px; }
489
+ /* horizontal corners */
490
+ :host(:not([vertical])) ::slotted(button:first-child) {
491
+ margin-left: 0;
492
+ border-radius: var(--zhimo-radius-sm) 0 0 var(--zhimo-radius-sm);
493
+ }
494
+ :host(:not([vertical])) ::slotted(button:last-child) {
495
+ border-radius: 0 var(--zhimo-radius-sm) var(--zhimo-radius-sm) 0;
496
+ }
497
+ /* vertical corners */
498
+ :host([vertical]) ::slotted(button:first-child) {
499
+ margin-top: 0;
500
+ border-radius: var(--zhimo-radius-sm) var(--zhimo-radius-sm) 0 0;
501
+ }
502
+ :host([vertical]) ::slotted(button:last-child) {
503
+ border-radius: 0 0 var(--zhimo-radius-sm) var(--zhimo-radius-sm);
504
+ }
505
+ ::slotted(button:only-child) { border-radius: var(--zhimo-radius-sm); }
506
+ ::slotted(button:hover) { background: var(--zhimo-bg-hover); }
507
+ ::slotted(button[aria-pressed="true"]) {
508
+ background: var(--zhimo-seal);
509
+ color: #fff;
510
+ border-color: var(--zhimo-seal);
511
+ z-index: 1;
512
+ }
513
+ ::slotted(button[aria-pressed="true"]:hover) {
514
+ background: var(--zhimo-seal-hover);
515
+ border-color: var(--zhimo-seal-hover);
516
+ }
517
+ ::slotted(button:disabled) { color: var(--zhimo-fg-muted); cursor: not-allowed; }
518
+ ::slotted(button:focus-visible) { outline: none; box-shadow: var(--zhimo-focus-ring); z-index: 2; }
519
+ </style>
520
+ <slot></slot>
521
+ `;
522
+
523
+ this.addEventListener('click', (e) => {
524
+ const btn = e.target.closest('button');
525
+ if (!btn || btn.disabled) return;
526
+ const val = btn.getAttribute('value');
527
+ if (val === this.getAttribute('value')) return;
528
+ this.setAttribute('value', val);
529
+ this.dispatchEvent(new CustomEvent('change', {
530
+ detail: { value: val }, bubbles: true, composed: true,
531
+ }));
532
+ });
533
+ }
534
+
535
+ connectedCallback() {
536
+ this.setAttribute('role', 'group');
537
+ if (!this.hasAttribute('tabindex')) this.tabIndex = 0;
538
+ this.addEventListener('keydown', this._onKeydown);
539
+ this._syncSelection();
540
+ }
541
+
542
+ disconnectedCallback() {
543
+ this.removeEventListener('keydown', this._onKeydown);
544
+ }
545
+
546
+ attributeChangedCallback(name) {
547
+ if (name === 'value') this._syncSelection();
548
+ if (name === 'disabled') this._buttons().forEach((b) => { b.disabled = this.hasAttribute('disabled'); });
549
+ }
550
+
551
+ _buttons() { return [...this.querySelectorAll('button')]; }
552
+
553
+ _syncSelection() {
554
+ const val = this.getAttribute('value');
555
+ this._buttons().forEach((b) => {
556
+ b.setAttribute('aria-pressed', String(b.getAttribute('value') === val));
557
+ });
558
+ }
559
+
560
+ _onKeydown = (e) => {
561
+ const vert = this.hasAttribute('vertical');
562
+ const fwd = vert ? 'ArrowDown' : 'ArrowRight';
563
+ const bwd = vert ? 'ArrowUp' : 'ArrowLeft';
564
+ if (e.key !== fwd && e.key !== bwd) return;
565
+ e.preventDefault();
566
+ const btns = this._buttons().filter((b) => !b.disabled);
567
+ if (!btns.length) return;
568
+ const cur = btns.findIndex((b) => b.getAttribute('value') === this.getAttribute('value'));
569
+ const next = btns[Math.max(0, Math.min(btns.length - 1, cur + (e.key === fwd ? 1 : -1)))];
570
+ this.setAttribute('value', next.getAttribute('value'));
571
+ this.dispatchEvent(new CustomEvent('change', {
572
+ detail: { value: next.getAttribute('value') }, bubbles: true, composed: true,
573
+ }));
574
+ };
575
+
576
+ get value() { return this.getAttribute('value'); }
577
+ set value(v) { v == null ? this.removeAttribute('value') : this.setAttribute('value', String(v)); }
578
+ }
579
+
580
+ customElements.define('zhimo-button', ZhimoButton);
581
+ customElements.define('zhimo-input', ZhimoInput);
582
+ customElements.define('zhimo-switch', ZhimoSwitch);
583
+ customElements.define('zhimo-checkbox', ZhimoCheckbox);
584
+ customElements.define('zhimo-slider', ZhimoSlider);
585
+ customElements.define('zhimo-button-group', ZhimoButtonGroup);