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