@duskmoon-dev/el-cascader 0.5.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.
@@ -0,0 +1,1084 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // src/el-dm-cascader.ts
30
+ var import_el_core = require("@duskmoon-dev/el-core");
31
+ var chevronDownIcon = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>`;
32
+ var chevronRightIcon = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>`;
33
+ var checkIcon = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>`;
34
+ var closeIcon = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`;
35
+ var searchIcon = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>`;
36
+ var loadingIcon = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="spinner"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>`;
37
+ var styles = import_el_core.css`
38
+ :host {
39
+ display: inline-block;
40
+ width: 100%;
41
+ }
42
+
43
+ .cascader {
44
+ position: relative;
45
+ width: 100%;
46
+ }
47
+
48
+ /* Trigger Button */
49
+ .cascader-trigger {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 0.5rem;
53
+ width: 100%;
54
+ min-height: 2.75rem;
55
+ padding: 0.5rem 0.75rem;
56
+ font-size: var(--font-size-md, 1rem);
57
+ line-height: 1.5;
58
+ color: var(--color-on-surface);
59
+ background-color: var(--color-surface);
60
+ border: 1px solid var(--color-outline);
61
+ border-radius: var(--radius-md, 0.5rem);
62
+ cursor: pointer;
63
+ transition: border-color 150ms ease, box-shadow 150ms ease;
64
+ }
65
+
66
+ .cascader-trigger:hover:not(:disabled) {
67
+ border-color: var(--color-on-surface-variant);
68
+ }
69
+
70
+ .cascader-trigger:focus {
71
+ outline: none;
72
+ border-color: var(--color-primary);
73
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-primary) 15%, transparent);
74
+ }
75
+
76
+ .cascader-trigger:disabled {
77
+ cursor: not-allowed;
78
+ opacity: 0.5;
79
+ background-color: var(--color-surface-container);
80
+ }
81
+
82
+ /* Value Display */
83
+ .cascader-value {
84
+ flex: 1;
85
+ overflow: hidden;
86
+ text-overflow: ellipsis;
87
+ white-space: nowrap;
88
+ text-align: left;
89
+ }
90
+
91
+ .cascader-placeholder {
92
+ color: var(--color-on-surface-variant);
93
+ opacity: 0.7;
94
+ }
95
+
96
+ /* Tags Container (for multiple mode) */
97
+ .cascader-tags {
98
+ display: flex;
99
+ flex-wrap: wrap;
100
+ gap: 0.25rem;
101
+ flex: 1;
102
+ min-width: 0;
103
+ }
104
+
105
+ .cascader-tag {
106
+ display: inline-flex;
107
+ align-items: center;
108
+ gap: 0.25rem;
109
+ max-width: 100%;
110
+ padding: 0.125rem 0.25rem 0.125rem 0.5rem;
111
+ font-size: var(--font-size-sm, 0.875rem);
112
+ line-height: 1.25rem;
113
+ background-color: var(--color-surface-container-high, #e8e8e8);
114
+ color: var(--color-on-surface);
115
+ border-radius: 1rem;
116
+ }
117
+
118
+ .cascader-tag-text {
119
+ overflow: hidden;
120
+ text-overflow: ellipsis;
121
+ white-space: nowrap;
122
+ }
123
+
124
+ .cascader-tag-remove {
125
+ display: inline-flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ width: 16px;
129
+ height: 16px;
130
+ padding: 0;
131
+ color: inherit;
132
+ background-color: transparent;
133
+ border-radius: 50%;
134
+ cursor: pointer;
135
+ opacity: 0.7;
136
+ transition: opacity 150ms ease, background-color 150ms ease;
137
+ }
138
+
139
+ .cascader-tag-remove svg {
140
+ width: 10px;
141
+ height: 10px;
142
+ display: block;
143
+ }
144
+
145
+ .cascader-tag-remove:hover {
146
+ opacity: 1;
147
+ background-color: color-mix(in oklch, currentColor 15%, transparent);
148
+ }
149
+
150
+ .cascader-tag-overflow {
151
+ padding: 0.125rem 0.5rem;
152
+ background-color: var(--color-surface-container);
153
+ color: var(--color-on-surface-variant);
154
+ }
155
+
156
+ /* Icons */
157
+ .cascader-arrow {
158
+ display: inline-flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ width: 20px;
162
+ height: 20px;
163
+ flex-shrink: 0;
164
+ color: var(--color-on-surface-variant);
165
+ transition: transform 150ms ease;
166
+ }
167
+
168
+ .cascader-arrow svg {
169
+ width: 16px;
170
+ height: 16px;
171
+ display: block;
172
+ }
173
+
174
+ .cascader.open .cascader-arrow {
175
+ transform: rotate(180deg);
176
+ }
177
+
178
+ .cascader-clear {
179
+ display: inline-flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ width: 20px;
183
+ height: 20px;
184
+ padding: 0;
185
+ color: var(--color-on-surface-variant);
186
+ background-color: transparent;
187
+ border-radius: 50%;
188
+ cursor: pointer;
189
+ flex-shrink: 0;
190
+ transition: background-color 150ms ease;
191
+ }
192
+
193
+ .cascader-clear svg {
194
+ width: 14px;
195
+ height: 14px;
196
+ display: block;
197
+ }
198
+
199
+ .cascader-clear:hover {
200
+ background-color: var(--color-surface-container-high);
201
+ }
202
+
203
+ /* Dropdown - uses Popover API (top-layer requires position: fixed) */
204
+ .cascader-dropdown {
205
+ position: fixed;
206
+ margin: 0;
207
+ padding: 0;
208
+ border: 1px solid var(--color-outline-variant);
209
+ border-radius: var(--radius-md, 0.5rem);
210
+ background-color: var(--color-surface);
211
+ box-shadow: var(--shadow-md, 0 4px 6px -1px rgba(0, 0, 0, 0.1));
212
+ overflow: hidden;
213
+ display: none;
214
+ flex-direction: column;
215
+ z-index: 1000;
216
+ }
217
+
218
+ .cascader-dropdown:popover-open {
219
+ display: flex;
220
+ }
221
+
222
+ /* Search */
223
+ .cascader-search {
224
+ display: flex;
225
+ align-items: center;
226
+ gap: 0.5rem;
227
+ padding: 0.5rem;
228
+ border-bottom: 1px solid var(--color-outline-variant);
229
+ }
230
+
231
+ .cascader-search-icon {
232
+ display: inline-flex;
233
+ align-items: center;
234
+ justify-content: center;
235
+ width: 16px;
236
+ height: 16px;
237
+ color: var(--color-on-surface-variant);
238
+ flex-shrink: 0;
239
+ }
240
+
241
+ .cascader-search-icon svg {
242
+ width: 14px;
243
+ height: 14px;
244
+ display: block;
245
+ }
246
+
247
+ .cascader-search-input {
248
+ flex: 1;
249
+ padding: 0.375rem 0.5rem;
250
+ font-size: var(--font-size-sm, 0.875rem);
251
+ color: var(--color-on-surface);
252
+ background-color: var(--color-surface-container);
253
+ border: none;
254
+ border-radius: var(--radius-sm, 0.25rem);
255
+ outline: none;
256
+ }
257
+
258
+ .cascader-search-input:focus {
259
+ background-color: var(--color-surface-container-high);
260
+ }
261
+
262
+ .cascader-search-input::placeholder {
263
+ color: var(--color-on-surface-variant);
264
+ opacity: 0.7;
265
+ }
266
+
267
+ /* Panels Container */
268
+ .cascader-panels {
269
+ display: flex;
270
+ max-height: 18rem;
271
+ }
272
+
273
+ /* Panel */
274
+ .cascader-panel {
275
+ display: flex;
276
+ flex-direction: column;
277
+ min-width: 10rem;
278
+ max-width: 14rem;
279
+ max-height: 18rem;
280
+ overflow-y: auto;
281
+ border-right: 1px solid var(--color-outline-variant);
282
+ }
283
+
284
+ .cascader-panel:last-child {
285
+ border-right: none;
286
+ }
287
+
288
+ .cascader-panel-options {
289
+ padding: 0.25rem;
290
+ }
291
+
292
+ /* Option */
293
+ .cascader-option {
294
+ display: flex;
295
+ align-items: center;
296
+ gap: 0.5rem;
297
+ width: 100%;
298
+ padding: 0.5rem 0.75rem;
299
+ font-size: var(--font-size-sm, 0.875rem);
300
+ color: var(--color-on-surface);
301
+ background-color: transparent;
302
+ border: none;
303
+ border-radius: var(--radius-sm, 0.25rem);
304
+ cursor: pointer;
305
+ text-align: left;
306
+ transition: background-color 150ms ease;
307
+ }
308
+
309
+ .cascader-option:hover:not(.disabled) {
310
+ background-color: var(--color-surface-container);
311
+ }
312
+
313
+ .cascader-option.active {
314
+ background-color: var(--color-surface-container-high);
315
+ }
316
+
317
+ .cascader-option.selected {
318
+ background-color: var(--color-primary-container, #e8def8);
319
+ color: var(--color-on-primary-container, #1d1b20);
320
+ }
321
+
322
+ .cascader-option.disabled {
323
+ opacity: 0.5;
324
+ cursor: not-allowed;
325
+ }
326
+
327
+ .cascader-option-checkbox {
328
+ display: flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ width: 1rem;
332
+ height: 1rem;
333
+ background-color: transparent;
334
+ border: 2px solid var(--color-on-surface-variant);
335
+ border-radius: 0.125rem;
336
+ flex-shrink: 0;
337
+ }
338
+
339
+ .cascader-option.selected .cascader-option-checkbox {
340
+ background-color: var(--color-primary);
341
+ border-color: var(--color-primary);
342
+ color: var(--color-on-primary, white);
343
+ }
344
+
345
+ .cascader-option-label {
346
+ flex: 1;
347
+ overflow: hidden;
348
+ text-overflow: ellipsis;
349
+ white-space: nowrap;
350
+ }
351
+
352
+ .cascader-option-arrow {
353
+ display: flex;
354
+ align-items: center;
355
+ justify-content: center;
356
+ color: var(--color-on-surface-variant);
357
+ flex-shrink: 0;
358
+ }
359
+
360
+ .cascader-option-loading {
361
+ display: flex;
362
+ align-items: center;
363
+ justify-content: center;
364
+ flex-shrink: 0;
365
+ }
366
+
367
+ .cascader-option-loading .spinner {
368
+ animation: spin 1s linear infinite;
369
+ }
370
+
371
+ @keyframes spin {
372
+ from { transform: rotate(0deg); }
373
+ to { transform: rotate(360deg); }
374
+ }
375
+
376
+ /* Search Results */
377
+ .cascader-search-results {
378
+ padding: 0.25rem;
379
+ max-height: 18rem;
380
+ overflow-y: auto;
381
+ }
382
+
383
+ .cascader-search-result {
384
+ display: flex;
385
+ align-items: center;
386
+ gap: 0.5rem;
387
+ width: 100%;
388
+ padding: 0.5rem 0.75rem;
389
+ font-size: var(--font-size-sm, 0.875rem);
390
+ color: var(--color-on-surface);
391
+ background-color: transparent;
392
+ border: none;
393
+ border-radius: var(--radius-sm, 0.25rem);
394
+ cursor: pointer;
395
+ text-align: left;
396
+ transition: background-color 150ms ease;
397
+ }
398
+
399
+ .cascader-search-result:hover {
400
+ background-color: var(--color-surface-container);
401
+ }
402
+
403
+ .cascader-search-result.selected {
404
+ background-color: var(--color-primary-container, #e8def8);
405
+ color: var(--color-on-primary-container, #1d1b20);
406
+ }
407
+
408
+ .cascader-search-result-path {
409
+ flex: 1;
410
+ overflow: hidden;
411
+ text-overflow: ellipsis;
412
+ white-space: nowrap;
413
+ }
414
+
415
+ .cascader-search-result-separator {
416
+ color: var(--color-on-surface-variant);
417
+ margin: 0 0.25rem;
418
+ }
419
+
420
+ /* Empty State */
421
+ .cascader-empty {
422
+ padding: 1.5rem;
423
+ text-align: center;
424
+ color: var(--color-on-surface-variant);
425
+ font-size: var(--font-size-sm, 0.875rem);
426
+ }
427
+
428
+ /* Size Variants */
429
+ :host([size="sm"]) .cascader-trigger {
430
+ min-height: 2.25rem;
431
+ padding: 0.375rem 0.5rem;
432
+ font-size: var(--font-size-sm, 0.875rem);
433
+ border-radius: var(--radius-sm, 0.375rem);
434
+ }
435
+
436
+ :host([size="lg"]) .cascader-trigger {
437
+ min-height: 3.25rem;
438
+ padding: 0.625rem 1rem;
439
+ font-size: var(--font-size-lg, 1.125rem);
440
+ border-radius: var(--radius-lg, 0.625rem);
441
+ }
442
+
443
+ /* Validation States */
444
+ :host([validation-state="invalid"]) .cascader-trigger {
445
+ border-color: var(--color-error);
446
+ }
447
+
448
+ :host([validation-state="invalid"]) .cascader-trigger:focus {
449
+ border-color: var(--color-error);
450
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-error) 15%, transparent);
451
+ }
452
+
453
+ :host([validation-state="valid"]) .cascader-trigger {
454
+ border-color: var(--color-success);
455
+ }
456
+
457
+ /* Disabled State */
458
+ :host([disabled]) {
459
+ pointer-events: none;
460
+ }
461
+
462
+ :host([disabled]) .cascader-trigger {
463
+ cursor: not-allowed;
464
+ opacity: 0.5;
465
+ background-color: var(--color-surface-container);
466
+ }
467
+ `;
468
+
469
+ class ElDmCascader extends import_el_core.BaseElement {
470
+ static properties = {
471
+ value: { type: String, reflect: true, default: "" },
472
+ placeholder: { type: String, default: "Select..." },
473
+ disabled: { type: Boolean, reflect: true, default: false },
474
+ multiple: { type: Boolean, reflect: true, default: false },
475
+ searchable: { type: Boolean, reflect: true, default: false },
476
+ clearable: { type: Boolean, reflect: true, default: false },
477
+ changeOnSelect: { type: Boolean, reflect: true, attribute: "change-on-select", default: false },
478
+ expandTrigger: { type: String, attribute: "expand-trigger", default: "click" },
479
+ separator: { type: String, default: " / " },
480
+ showAllLevels: { type: Boolean, reflect: true, attribute: "show-all-levels", default: true },
481
+ showCheckedStrategy: { type: String, attribute: "show-checked-strategy", default: "all" },
482
+ size: { type: String, reflect: true, default: "md" },
483
+ validationState: { type: String, reflect: true, attribute: "validation-state" },
484
+ options: { type: String, default: "" }
485
+ };
486
+ _isOpen = false;
487
+ _searchValue = "";
488
+ _activePath = [];
489
+ _selectedPaths = [];
490
+ _loadingKeys = new Set;
491
+ _options = [];
492
+ _loadDataFn = null;
493
+ _handleOutsideClick = this._onOutsideClick.bind(this);
494
+ _handleKeyDown = this._onKeyDown.bind(this);
495
+ _handleScroll = this._onScroll.bind(this);
496
+ _handleResize = this._onResize.bind(this);
497
+ constructor() {
498
+ super();
499
+ this.attachStyles(styles);
500
+ }
501
+ connectedCallback() {
502
+ super.connectedCallback();
503
+ document.addEventListener("click", this._handleOutsideClick);
504
+ document.addEventListener("keydown", this._handleKeyDown);
505
+ this._parseOptionsFromAttribute();
506
+ this._parseValue();
507
+ this._setupEventDelegation();
508
+ }
509
+ _parseOptionsFromAttribute() {
510
+ if (this.options) {
511
+ try {
512
+ const parsed = JSON.parse(this.options);
513
+ if (Array.isArray(parsed)) {
514
+ this._options = parsed;
515
+ }
516
+ } catch {}
517
+ }
518
+ }
519
+ disconnectedCallback() {
520
+ super.disconnectedCallback();
521
+ document.removeEventListener("click", this._handleOutsideClick);
522
+ document.removeEventListener("keydown", this._handleKeyDown);
523
+ this._removeScrollListeners();
524
+ }
525
+ _addScrollListeners() {
526
+ window.addEventListener("scroll", this._handleScroll, true);
527
+ window.addEventListener("resize", this._handleResize);
528
+ }
529
+ _removeScrollListeners() {
530
+ window.removeEventListener("scroll", this._handleScroll, true);
531
+ window.removeEventListener("resize", this._handleResize);
532
+ }
533
+ _onScroll() {
534
+ if (this._isOpen) {
535
+ const dropdown = this.shadowRoot?.querySelector(".cascader-dropdown");
536
+ const trigger = this.shadowRoot?.querySelector(".cascader-trigger");
537
+ if (dropdown && trigger) {
538
+ this._positionDropdown(dropdown, trigger);
539
+ }
540
+ }
541
+ }
542
+ _onResize() {
543
+ if (this._isOpen) {
544
+ this._close();
545
+ }
546
+ }
547
+ setOptions(options) {
548
+ this._options = options;
549
+ this.update();
550
+ }
551
+ setLoadData(fn) {
552
+ this._loadDataFn = fn;
553
+ }
554
+ _parseValue() {
555
+ if (!this.value) {
556
+ this._selectedPaths = [];
557
+ return;
558
+ }
559
+ try {
560
+ const parsed = JSON.parse(this.value);
561
+ if (this.multiple) {
562
+ this._selectedPaths = Array.isArray(parsed[0]) ? parsed : [parsed];
563
+ } else {
564
+ this._selectedPaths = Array.isArray(parsed) ? [parsed] : [];
565
+ }
566
+ } catch {
567
+ this._selectedPaths = [];
568
+ }
569
+ }
570
+ _getPanels() {
571
+ const panels = [this._options];
572
+ let currentOptions = this._options;
573
+ for (const value of this._activePath) {
574
+ const option = currentOptions.find((o) => o.value === value);
575
+ if (option?.children && option.children.length > 0) {
576
+ panels.push(option.children);
577
+ currentOptions = option.children;
578
+ } else {
579
+ break;
580
+ }
581
+ }
582
+ return panels;
583
+ }
584
+ _getDisplayLabel() {
585
+ if (this._selectedPaths.length === 0) {
586
+ return "";
587
+ }
588
+ const path = this._selectedPaths[0];
589
+ const labels = this._getPathLabels(path);
590
+ if (this.showAllLevels) {
591
+ return labels.join(this.separator);
592
+ }
593
+ return labels[labels.length - 1] || "";
594
+ }
595
+ _getPathLabels(path) {
596
+ const labels = [];
597
+ let currentOptions = this._options;
598
+ for (const value of path) {
599
+ const option = currentOptions.find((o) => o.value === value);
600
+ if (option) {
601
+ labels.push(option.label);
602
+ currentOptions = option.children || [];
603
+ }
604
+ }
605
+ return labels;
606
+ }
607
+ _findOptionByPath(path) {
608
+ let currentOptions = this._options;
609
+ let option;
610
+ for (const value of path) {
611
+ option = currentOptions.find((o) => o.value === value);
612
+ if (option?.children) {
613
+ currentOptions = option.children;
614
+ }
615
+ }
616
+ return option;
617
+ }
618
+ _isLeaf(option) {
619
+ if (option.leaf === true)
620
+ return true;
621
+ if (option.leaf === false)
622
+ return false;
623
+ return !option.children || option.children.length === 0;
624
+ }
625
+ _searchOptions() {
626
+ const results = [];
627
+ const search = this._searchValue.toLowerCase();
628
+ const searchRecursive = (options, path, pathValues) => {
629
+ for (const option of options) {
630
+ const newPath = [...path, option];
631
+ const newPathValues = [...pathValues, option.value];
632
+ if (option.label.toLowerCase().includes(search)) {
633
+ if (this._isLeaf(option) || this.changeOnSelect) {
634
+ results.push({
635
+ path: newPath,
636
+ pathLabels: newPath.map((o) => o.label),
637
+ pathValues: newPathValues
638
+ });
639
+ }
640
+ }
641
+ if (option.children) {
642
+ searchRecursive(option.children, newPath, newPathValues);
643
+ }
644
+ }
645
+ };
646
+ searchRecursive(this._options, [], []);
647
+ return results;
648
+ }
649
+ _onOutsideClick(e) {
650
+ if (!this.contains(e.target)) {
651
+ this._close();
652
+ }
653
+ }
654
+ _onKeyDown(e) {
655
+ if (!this._isOpen) {
656
+ if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
657
+ if (document.activeElement === this || this.contains(document.activeElement)) {
658
+ e.preventDefault();
659
+ this._open();
660
+ }
661
+ }
662
+ return;
663
+ }
664
+ switch (e.key) {
665
+ case "Escape":
666
+ e.preventDefault();
667
+ this._close();
668
+ break;
669
+ case "ArrowLeft":
670
+ e.preventDefault();
671
+ if (this._activePath.length > 0) {
672
+ this._activePath = this._activePath.slice(0, -1);
673
+ this.update();
674
+ }
675
+ break;
676
+ case "ArrowRight":
677
+ break;
678
+ }
679
+ }
680
+ _open() {
681
+ if (this.disabled)
682
+ return;
683
+ this._isOpen = true;
684
+ this._activePath = this._selectedPaths[0] ? [...this._selectedPaths[0]] : [];
685
+ this.update();
686
+ this._addScrollListeners();
687
+ requestAnimationFrame(() => {
688
+ const dropdown = this.shadowRoot?.querySelector(".cascader-dropdown");
689
+ const trigger = this.shadowRoot?.querySelector(".cascader-trigger");
690
+ if (dropdown && trigger) {
691
+ const triggerRect = trigger.getBoundingClientRect();
692
+ dropdown.style.top = `${triggerRect.bottom + 4}px`;
693
+ dropdown.style.left = `${triggerRect.left}px`;
694
+ dropdown.style.minWidth = `${triggerRect.width}px`;
695
+ dropdown.showPopover();
696
+ requestAnimationFrame(() => {
697
+ this._positionDropdown(dropdown, trigger);
698
+ });
699
+ }
700
+ const searchInput = this.shadowRoot?.querySelector(".cascader-search-input");
701
+ if (searchInput) {
702
+ searchInput.focus();
703
+ }
704
+ });
705
+ }
706
+ _close() {
707
+ this._isOpen = false;
708
+ this._searchValue = "";
709
+ this._activePath = [];
710
+ this._removeScrollListeners();
711
+ const dropdown = this.shadowRoot?.querySelector(".cascader-dropdown");
712
+ if (dropdown) {
713
+ try {
714
+ dropdown.hidePopover();
715
+ } catch {}
716
+ }
717
+ this.update();
718
+ }
719
+ _positionDropdown(dropdown, trigger) {
720
+ const triggerRect = trigger.getBoundingClientRect();
721
+ const viewportHeight = window.innerHeight;
722
+ const viewportWidth = window.innerWidth;
723
+ let top = triggerRect.bottom + 4;
724
+ let left = triggerRect.left;
725
+ const dropdownRect = dropdown.getBoundingClientRect();
726
+ if (top + dropdownRect.height > viewportHeight && triggerRect.top > dropdownRect.height) {
727
+ top = triggerRect.top - dropdownRect.height - 4;
728
+ }
729
+ if (left + dropdownRect.width > viewportWidth) {
730
+ left = viewportWidth - dropdownRect.width - 8;
731
+ }
732
+ if (left < 8) {
733
+ left = 8;
734
+ }
735
+ dropdown.style.top = `${top}px`;
736
+ dropdown.style.left = `${left}px`;
737
+ dropdown.style.minWidth = `${triggerRect.width}px`;
738
+ }
739
+ _toggle() {
740
+ if (this._isOpen) {
741
+ this._close();
742
+ } else {
743
+ this._open();
744
+ }
745
+ }
746
+ async _handleOptionClick(value, level) {
747
+ this._activePath = this._activePath.slice(0, level);
748
+ this._activePath.push(value);
749
+ const option = this._findOptionByPath(this._activePath);
750
+ if (!option) {
751
+ return;
752
+ }
753
+ if (this._loadDataFn && !option.children && !option.leaf) {
754
+ this._loadingKeys.add(value);
755
+ this.update();
756
+ try {
757
+ const children = await this._loadDataFn(option);
758
+ option.children = children;
759
+ } finally {
760
+ this._loadingKeys.delete(value);
761
+ }
762
+ }
763
+ this.emit("expand", { option, level });
764
+ const isLeaf = this._isLeaf(option);
765
+ if (isLeaf || this.changeOnSelect) {
766
+ this._selectPath([...this._activePath]);
767
+ if (isLeaf && !this.multiple) {
768
+ this._close();
769
+ }
770
+ }
771
+ this.update();
772
+ }
773
+ _handleOptionHover(value, level) {
774
+ if (this.expandTrigger !== "hover")
775
+ return;
776
+ this._activePath = this._activePath.slice(0, level);
777
+ this._activePath.push(value);
778
+ this.update();
779
+ const option = this._findOptionByPath(this._activePath);
780
+ if (option && this._loadDataFn && !option.children && !option.leaf) {
781
+ this._handleOptionClick(value, level);
782
+ }
783
+ }
784
+ _selectPath(path) {
785
+ if (this.multiple) {
786
+ const pathStr = JSON.stringify(path);
787
+ const index = this._selectedPaths.findIndex((p) => JSON.stringify(p) === pathStr);
788
+ if (index >= 0) {
789
+ this._selectedPaths.splice(index, 1);
790
+ } else {
791
+ this._selectedPaths.push(path);
792
+ }
793
+ this.value = JSON.stringify(this._selectedPaths);
794
+ } else {
795
+ this._selectedPaths = [path];
796
+ this.value = JSON.stringify(path);
797
+ }
798
+ this._emitChange();
799
+ }
800
+ _selectSearchResult(result) {
801
+ this._selectPath(result.pathValues);
802
+ if (!this.multiple) {
803
+ this._close();
804
+ }
805
+ this.update();
806
+ }
807
+ _removeTag(pathIndex) {
808
+ this._selectedPaths.splice(pathIndex, 1);
809
+ this.value = this._selectedPaths.length > 0 ? JSON.stringify(this._selectedPaths) : "";
810
+ this._emitChange();
811
+ this.update();
812
+ }
813
+ _handleSearch(e) {
814
+ const input = e.target;
815
+ this._searchValue = input.value;
816
+ this.emit("search", { searchValue: this._searchValue });
817
+ this.update();
818
+ }
819
+ _handleClear(e) {
820
+ e.stopPropagation();
821
+ this.value = "";
822
+ this._selectedPaths = [];
823
+ this.emit("clear", {});
824
+ this._emitChange();
825
+ this.update();
826
+ }
827
+ _emitChange() {
828
+ const selectedOptions = this._selectedPaths.map((path) => this._findOptionByPath(path)).filter((o) => o !== undefined);
829
+ this.emit("change", {
830
+ value: this.value,
831
+ selectedOptions,
832
+ path: this._selectedPaths[0] || []
833
+ });
834
+ }
835
+ render() {
836
+ return `
837
+ <div class="cascader ${this._isOpen ? "open" : ""}">
838
+ ${this._renderTrigger()}
839
+ ${this._renderDropdown()}
840
+ </div>
841
+ `;
842
+ }
843
+ _renderTrigger() {
844
+ const hasValue = this._selectedPaths.length > 0;
845
+ const showClear = this.clearable && hasValue && !this.disabled;
846
+ return `
847
+ <button
848
+ type="button"
849
+ class="cascader-trigger"
850
+ aria-haspopup="listbox"
851
+ aria-expanded="${this._isOpen}"
852
+ ${this.disabled ? "disabled" : ""}
853
+ data-action="toggle"
854
+ >
855
+ ${this.multiple && hasValue ? this._renderTags() : this._renderValue()}
856
+ ${showClear ? `<span class="cascader-clear" role="button" tabindex="-1" data-action="clear">${closeIcon}</span>` : ""}
857
+ <span class="cascader-arrow">${chevronDownIcon}</span>
858
+ </button>
859
+ `;
860
+ }
861
+ _renderValue() {
862
+ const displayLabel = this._getDisplayLabel();
863
+ if (!displayLabel) {
864
+ return `<span class="cascader-value cascader-placeholder">${this.placeholder}</span>`;
865
+ }
866
+ return `<span class="cascader-value">${this._escapeHtml(displayLabel)}</span>`;
867
+ }
868
+ _renderTags() {
869
+ const tagsHtml = this._selectedPaths.map((path, index) => {
870
+ const labels = this._getPathLabels(path);
871
+ const displayLabel = this.showAllLevels ? labels.join(this.separator) : labels[labels.length - 1];
872
+ return `
873
+ <span class="cascader-tag">
874
+ <span class="cascader-tag-text">${this._escapeHtml(displayLabel)}</span>
875
+ <span class="cascader-tag-remove" role="button" tabindex="-1" data-action="remove-tag" data-index="${index}">${closeIcon}</span>
876
+ </span>
877
+ `;
878
+ }).join("");
879
+ return `<div class="cascader-tags">${tagsHtml || `<span class="cascader-placeholder">${this.placeholder}</span>`}</div>`;
880
+ }
881
+ _renderDropdown() {
882
+ const showSearch = this.searchable && this._searchValue;
883
+ return `
884
+ <div class="cascader-dropdown" role="listbox" popover="manual">
885
+ ${this.searchable ? this._renderSearch() : ""}
886
+ ${showSearch ? this._renderSearchResults() : this._renderPanels()}
887
+ </div>
888
+ `;
889
+ }
890
+ _renderSearch() {
891
+ return `
892
+ <div class="cascader-search">
893
+ <span class="cascader-search-icon">${searchIcon}</span>
894
+ <input
895
+ type="text"
896
+ class="cascader-search-input"
897
+ placeholder="Search..."
898
+ value="${this._escapeHtml(this._searchValue)}"
899
+ data-action="search"
900
+ />
901
+ </div>
902
+ `;
903
+ }
904
+ _renderPanels() {
905
+ const panels = this._getPanels();
906
+ if (panels.length === 0 || panels[0].length === 0) {
907
+ return `<div class="cascader-empty">No options available</div>`;
908
+ }
909
+ return `
910
+ <div class="cascader-panels">
911
+ ${panels.map((options, level) => this._renderPanel(options, level)).join("")}
912
+ </div>
913
+ `;
914
+ }
915
+ _renderPanel(options, level) {
916
+ const selectedValue = this._activePath[level];
917
+ const selectedPathValues = this._selectedPaths.flatMap((p) => p);
918
+ const optionsHtml = options.map((option) => {
919
+ const isActive = option.value === selectedValue;
920
+ const isSelected = this.multiple ? selectedPathValues.includes(option.value) : JSON.stringify(this._selectedPaths[0]) === JSON.stringify([...this._activePath.slice(0, level), option.value]);
921
+ const isLoading = this._loadingKeys.has(option.value);
922
+ const hasChildren = !this._isLeaf(option);
923
+ const classes = [
924
+ "cascader-option",
925
+ isActive ? "active" : "",
926
+ isSelected ? "selected" : "",
927
+ option.disabled ? "disabled" : ""
928
+ ].filter(Boolean).join(" ");
929
+ return `
930
+ <button
931
+ type="button"
932
+ class="${classes}"
933
+ data-action="option"
934
+ data-value="${this._escapeHtml(option.value)}"
935
+ data-level="${level}"
936
+ ${option.disabled ? "disabled" : ""}
937
+ >
938
+ ${this.multiple ? `<span class="cascader-option-checkbox">${isSelected ? checkIcon : ""}</span>` : ""}
939
+ <span class="cascader-option-label">${this._escapeHtml(option.label)}</span>
940
+ ${isLoading ? `<span class="cascader-option-loading">${loadingIcon}</span>` : ""}
941
+ ${hasChildren && !isLoading ? `<span class="cascader-option-arrow">${chevronRightIcon}</span>` : ""}
942
+ </button>
943
+ `;
944
+ }).join("");
945
+ return `
946
+ <div class="cascader-panel">
947
+ <div class="cascader-panel-options">${optionsHtml}</div>
948
+ </div>
949
+ `;
950
+ }
951
+ _renderSearchResults() {
952
+ const results = this._searchOptions();
953
+ if (results.length === 0) {
954
+ return `<div class="cascader-empty">No results found</div>`;
955
+ }
956
+ const selectedPathStrs = this._selectedPaths.map((p) => JSON.stringify(p));
957
+ const resultsHtml = results.map((result) => {
958
+ const isSelected = selectedPathStrs.includes(JSON.stringify(result.pathValues));
959
+ const classes = ["cascader-search-result", isSelected ? "selected" : ""].filter(Boolean).join(" ");
960
+ const pathHtml = result.pathLabels.map((label, i) => {
961
+ const separator = i < result.pathLabels.length - 1 ? `<span class="cascader-search-result-separator">${this.separator}</span>` : "";
962
+ return `<span>${this._escapeHtml(label)}</span>${separator}`;
963
+ }).join("");
964
+ return `
965
+ <button
966
+ type="button"
967
+ class="${classes}"
968
+ data-action="search-result"
969
+ data-path="${this._escapeHtml(JSON.stringify(result.pathValues))}"
970
+ >
971
+ ${this.multiple ? `<span class="cascader-option-checkbox">${isSelected ? checkIcon : ""}</span>` : ""}
972
+ <span class="cascader-search-result-path">${pathHtml}</span>
973
+ </button>
974
+ `;
975
+ }).join("");
976
+ return `<div class="cascader-search-results">${resultsHtml}</div>`;
977
+ }
978
+ _escapeHtml(str) {
979
+ const div = document.createElement("div");
980
+ div.textContent = str;
981
+ return div.innerHTML;
982
+ }
983
+ update() {
984
+ const searchInput = this.shadowRoot?.querySelector(".cascader-search-input");
985
+ const hadFocus = searchInput && this.shadowRoot?.activeElement === searchInput;
986
+ const cursorPosition = hadFocus ? searchInput.selectionStart : null;
987
+ super.update();
988
+ if (this._isOpen) {
989
+ const dropdown = this.shadowRoot?.querySelector(".cascader-dropdown");
990
+ const trigger = this.shadowRoot?.querySelector(".cascader-trigger");
991
+ if (dropdown && trigger) {
992
+ try {
993
+ dropdown.showPopover();
994
+ this._positionDropdown(dropdown, trigger);
995
+ } catch {}
996
+ }
997
+ if (hadFocus) {
998
+ const newSearchInput = this.shadowRoot?.querySelector(".cascader-search-input");
999
+ if (newSearchInput) {
1000
+ newSearchInput.focus();
1001
+ if (cursorPosition !== null) {
1002
+ newSearchInput.setSelectionRange(cursorPosition, cursorPosition);
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+ }
1008
+ _setupEventDelegation() {
1009
+ this.shadowRoot?.addEventListener("click", (e) => {
1010
+ const target = e.target;
1011
+ const trigger = target.closest('[data-action="toggle"]');
1012
+ if (trigger && !target.closest('[data-action="clear"]') && !target.closest('[data-action="remove-tag"]')) {
1013
+ this._toggle();
1014
+ return;
1015
+ }
1016
+ if (target.closest('[data-action="clear"]')) {
1017
+ this._handleClear(e);
1018
+ return;
1019
+ }
1020
+ const removeTag = target.closest('[data-action="remove-tag"]');
1021
+ if (removeTag) {
1022
+ e.stopPropagation();
1023
+ const index = parseInt(removeTag.getAttribute("data-index") || "0", 10);
1024
+ this._removeTag(index);
1025
+ return;
1026
+ }
1027
+ const option = target.closest('[data-action="option"]');
1028
+ if (option) {
1029
+ const value = option.getAttribute("data-value");
1030
+ const level = parseInt(option.getAttribute("data-level") || "0", 10);
1031
+ if (value) {
1032
+ this._handleOptionClick(value, level);
1033
+ }
1034
+ return;
1035
+ }
1036
+ const searchResult = target.closest('[data-action="search-result"]');
1037
+ if (searchResult) {
1038
+ const pathStr = searchResult.getAttribute("data-path");
1039
+ if (pathStr) {
1040
+ try {
1041
+ const pathValues = JSON.parse(pathStr);
1042
+ const result = {
1043
+ pathValues,
1044
+ path: [],
1045
+ pathLabels: this._getPathLabels(pathValues)
1046
+ };
1047
+ this._selectSearchResult(result);
1048
+ } catch {}
1049
+ }
1050
+ return;
1051
+ }
1052
+ });
1053
+ this.shadowRoot?.addEventListener("input", (e) => {
1054
+ const target = e.target;
1055
+ if (target.matches('[data-action="search"]')) {
1056
+ this._handleSearch(e);
1057
+ }
1058
+ });
1059
+ this.shadowRoot?.addEventListener("mouseenter", (e) => {
1060
+ if (this.expandTrigger !== "hover")
1061
+ return;
1062
+ const target = e.target;
1063
+ const option = target.closest('[data-action="option"]');
1064
+ if (option) {
1065
+ const value = option.getAttribute("data-value");
1066
+ const level = parseInt(option.getAttribute("data-level") || "0", 10);
1067
+ if (value) {
1068
+ this._handleOptionHover(value, level);
1069
+ }
1070
+ }
1071
+ }, true);
1072
+ }
1073
+ }
1074
+ function register() {
1075
+ if (!customElements.get("el-dm-cascader")) {
1076
+ customElements.define("el-dm-cascader", ElDmCascader);
1077
+ }
1078
+ }
1079
+
1080
+ // src/register.ts
1081
+ register();
1082
+
1083
+ //# debugId=B15F0C00FB473DF564756E2164756E21
1084
+ //# sourceMappingURL=register.js.map