@duskmoon-dev/el-cascader 0.5.0 → 0.6.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.
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * A multi-panel cascading selection component following Ant Design patterns.
5
5
  */
6
- import { BaseElement } from '@duskmoon-dev/el-core';
7
- import type { Size, ValidationState } from '@duskmoon-dev/el-core';
6
+ import { BaseElement } from '@duskmoon-dev/el-base';
7
+ import type { Size, ValidationState } from '@duskmoon-dev/el-base';
8
8
  /**
9
9
  * Cascader option structure
10
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"el-dm-cascader.d.ts","sourceRoot":"","sources":["../../src/el-dm-cascader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"el-dm-cascader.d.ts","sourceRoot":"","sources":["../../src/el-dm-cascader.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duskmoon-dev/el-cascader",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "DuskMoon Cascader custom element for multi-level cascading selection",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -25,16 +25,17 @@
25
25
  "scripts": {
26
26
  "prebuild": "bun run clean",
27
27
  "build": "bun run build:esm && bun run build:cjs && bun run build:types",
28
- "build:esm": "bun build ./src/index.ts ./src/register.ts --outdir ./dist/esm --format esm --sourcemap --external @duskmoon-dev/el-core",
29
- "build:cjs": "bun build ./src/index.ts ./src/register.ts --outdir ./dist/cjs --format cjs --sourcemap --external @duskmoon-dev/el-core",
28
+ "build:esm": "bun build ./src/index.ts ./src/register.ts --outdir ./dist/esm --format esm --sourcemap --external @duskmoon-dev/el-base --external @duskmoon-dev/core",
29
+ "build:cjs": "bun build ./src/index.ts ./src/register.ts --outdir ./dist/cjs --format cjs --sourcemap --external @duskmoon-dev/el-base --external @duskmoon-dev/core",
30
30
  "build:types": "tsc --emitDeclarationOnly --outDir ./dist/types",
31
- "dev": "bun build ./src/index.ts --outdir ./dist/esm --format esm --sourcemap --external @duskmoon-dev/el-core --watch",
31
+ "dev": "bun build ./src/index.ts --outdir ./dist/esm --format esm --sourcemap --external @duskmoon-dev/el-base --external @duskmoon-dev/core --watch",
32
32
  "clean": "rm -rf dist",
33
33
  "test": "bun test",
34
34
  "typecheck": "tsc --noEmit"
35
35
  },
36
36
  "dependencies": {
37
- "@duskmoon-dev/el-core": "0.4.0"
37
+ "@duskmoon-dev/el-base": "0.5.1",
38
+ "@duskmoon-dev/core": "^1.10.1"
38
39
  },
39
40
  "devDependencies": {
40
41
  "typescript": "^5.8.3"
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Type declarations for @duskmoon-dev/core cascader component export
3
+ */
4
+
5
+ declare module '@duskmoon-dev/core/components/cascader' {
6
+ export const css: string;
7
+ }
@@ -0,0 +1,232 @@
1
+ import { expect, test, describe, beforeEach, afterEach } from 'bun:test';
2
+ import { ElDmCascader, register } from './index';
3
+ import type { CascaderOption } from './index';
4
+
5
+ register();
6
+
7
+ const sampleOptions: CascaderOption[] = [
8
+ { value: 'fruits', label: 'Fruits', children: [
9
+ { value: 'apple', label: 'Apple' },
10
+ { value: 'banana', label: 'Banana' },
11
+ ]},
12
+ { value: 'vegs', label: 'Vegetables', children: [
13
+ { value: 'carrot', label: 'Carrot' },
14
+ ]},
15
+ { value: 'grains', label: 'Grains' },
16
+ ];
17
+
18
+ function createCascader(props: Partial<ElDmCascader> = {}): ElDmCascader {
19
+ const el = document.createElement('el-dm-cascader') as ElDmCascader;
20
+ Object.assign(el, props);
21
+ return el;
22
+ }
23
+
24
+ describe('ElDmCascader', () => {
25
+ let container: HTMLDivElement;
26
+
27
+ beforeEach(() => {
28
+ container = document.createElement('div');
29
+ document.body.appendChild(container);
30
+ });
31
+
32
+ afterEach(() => {
33
+ container.remove();
34
+ });
35
+
36
+ // --- Registration ---
37
+ test('is defined', () => {
38
+ expect(customElements.get('el-dm-cascader')).toBe(ElDmCascader);
39
+ });
40
+
41
+ // --- Rendering ---
42
+ test('creates a shadow root', () => {
43
+ const el = createCascader();
44
+ container.appendChild(el);
45
+ expect(el.shadowRoot).toBeDefined();
46
+ });
47
+
48
+ test('has cascader trigger', () => {
49
+ const el = createCascader();
50
+ container.appendChild(el);
51
+ const trigger = el.shadowRoot?.querySelector('.cascader-trigger');
52
+ expect(trigger).toBeDefined();
53
+ });
54
+
55
+ test('shows placeholder text', () => {
56
+ const el = createCascader({ placeholder: 'Pick one' });
57
+ container.appendChild(el);
58
+ const placeholder = el.shadowRoot?.querySelector('.cascader-placeholder');
59
+ expect(placeholder?.textContent).toContain('Pick one');
60
+ });
61
+
62
+ test('shows default placeholder', () => {
63
+ const el = createCascader();
64
+ container.appendChild(el);
65
+ const placeholder = el.shadowRoot?.querySelector('.cascader-placeholder');
66
+ expect(placeholder?.textContent).toContain('Select...');
67
+ });
68
+
69
+ test('has dropdown element', () => {
70
+ const el = createCascader();
71
+ container.appendChild(el);
72
+ const dropdown = el.shadowRoot?.querySelector('.cascader-dropdown');
73
+ expect(dropdown).toBeDefined();
74
+ });
75
+
76
+ test('has panels container', () => {
77
+ const el = createCascader();
78
+ container.appendChild(el);
79
+ const panels = el.shadowRoot?.querySelector('.cascader-panels');
80
+ expect(panels).toBeDefined();
81
+ });
82
+
83
+ test('has chevron icon', () => {
84
+ const el = createCascader();
85
+ container.appendChild(el);
86
+ const chevron = el.shadowRoot?.querySelector('.cascader-chevron');
87
+ expect(chevron).toBeDefined();
88
+ });
89
+
90
+ // --- Properties ---
91
+ test('reflects value attribute', () => {
92
+ const el = createCascader({ value: '["fruits","apple"]' });
93
+ container.appendChild(el);
94
+ expect(el.getAttribute('value')).toBe('["fruits","apple"]');
95
+ });
96
+
97
+ test('reflects disabled attribute', () => {
98
+ const el = createCascader({ disabled: true });
99
+ container.appendChild(el);
100
+ expect(el.hasAttribute('disabled')).toBe(true);
101
+ });
102
+
103
+ test('reflects multiple attribute', () => {
104
+ const el = createCascader({ multiple: true });
105
+ container.appendChild(el);
106
+ expect(el.hasAttribute('multiple')).toBe(true);
107
+ });
108
+
109
+ test('reflects searchable attribute', () => {
110
+ const el = createCascader({ searchable: true });
111
+ container.appendChild(el);
112
+ expect(el.hasAttribute('searchable')).toBe(true);
113
+ });
114
+
115
+ test('reflects clearable attribute', () => {
116
+ const el = createCascader({ clearable: true });
117
+ container.appendChild(el);
118
+ expect(el.hasAttribute('clearable')).toBe(true);
119
+ });
120
+
121
+ test('reflects changeOnSelect attribute', () => {
122
+ const el = createCascader({ changeOnSelect: true });
123
+ container.appendChild(el);
124
+ expect(el.hasAttribute('change-on-select')).toBe(true);
125
+ });
126
+
127
+ test('defaults expandTrigger to click', () => {
128
+ const el = createCascader();
129
+ container.appendChild(el);
130
+ expect(el.expandTrigger).toBe('click');
131
+ });
132
+
133
+ test('defaults separator to " / "', () => {
134
+ const el = createCascader();
135
+ container.appendChild(el);
136
+ expect(el.separator).toBe(' / ');
137
+ });
138
+
139
+ test('defaults size to md', () => {
140
+ const el = createCascader();
141
+ container.appendChild(el);
142
+ expect(el.size).toBe('md');
143
+ });
144
+
145
+ test('reflects size attribute', () => {
146
+ const el = createCascader({ size: 'sm' } as Partial<ElDmCascader>);
147
+ container.appendChild(el);
148
+ expect(el.getAttribute('size')).toBe('sm');
149
+ });
150
+
151
+ // --- Public methods ---
152
+ test('has setOptions method', () => {
153
+ const el = createCascader();
154
+ container.appendChild(el);
155
+ expect(typeof el.setOptions).toBe('function');
156
+ });
157
+
158
+ test('has setLoadData method', () => {
159
+ const el = createCascader();
160
+ container.appendChild(el);
161
+ expect(typeof el.setLoadData).toBe('function');
162
+ });
163
+
164
+ test('setOptions renders options in first panel', () => {
165
+ const el = createCascader();
166
+ container.appendChild(el);
167
+ el.setOptions(sampleOptions);
168
+ const options = el.shadowRoot?.querySelectorAll('.cascader-option');
169
+ expect(options?.length).toBeGreaterThanOrEqual(3);
170
+ });
171
+
172
+ // --- Accessibility ---
173
+ test('has combobox role', () => {
174
+ const el = createCascader();
175
+ container.appendChild(el);
176
+ const combobox = el.shadowRoot?.querySelector('[role="combobox"]');
177
+ expect(combobox).toBeDefined();
178
+ });
179
+
180
+ test('has aria-expanded', () => {
181
+ const el = createCascader();
182
+ container.appendChild(el);
183
+ const trigger = el.shadowRoot?.querySelector('[aria-expanded]');
184
+ expect(trigger).toBeDefined();
185
+ });
186
+
187
+ // --- Disabled ---
188
+ test('trigger is disabled when component disabled', () => {
189
+ const el = createCascader({ disabled: true });
190
+ container.appendChild(el);
191
+ const trigger = el.shadowRoot?.querySelector('.cascader-trigger');
192
+ expect(trigger?.hasAttribute('disabled')).toBe(true);
193
+ });
194
+
195
+ // --- Size via host attribute (styled with :host([size="sm/lg"])) ---
196
+ test('host has size=sm attribute', () => {
197
+ const el = createCascader({ size: 'sm' } as Partial<ElDmCascader>);
198
+ container.appendChild(el);
199
+ expect(el.getAttribute('size')).toBe('sm');
200
+ });
201
+
202
+ test('host has size=lg attribute', () => {
203
+ const el = createCascader({ size: 'lg' } as Partial<ElDmCascader>);
204
+ container.appendChild(el);
205
+ expect(el.getAttribute('size')).toBe('lg');
206
+ });
207
+
208
+ // --- Validation ---
209
+ test('reflects validationState attribute', () => {
210
+ const el = createCascader({ validationState: 'invalid' } as Partial<ElDmCascader>);
211
+ container.appendChild(el);
212
+ expect(el.getAttribute('validation-state')).toBe('invalid');
213
+ });
214
+
215
+ // --- Options with children show expand indicator ---
216
+ test('options with children have expand indicator', () => {
217
+ const el = createCascader();
218
+ container.appendChild(el);
219
+ el.setOptions(sampleOptions);
220
+ const expandable = el.shadowRoot?.querySelectorAll('.cascader-option-arrow');
221
+ // fruits and vegs have children = 2 arrows
222
+ expect(expandable?.length).toBeGreaterThanOrEqual(2);
223
+ });
224
+
225
+ // --- Searchable ---
226
+ test('shows search input when searchable', () => {
227
+ const el = createCascader({ searchable: true });
228
+ container.appendChild(el);
229
+ const searchInput = el.shadowRoot?.querySelector('.cascader-search-input');
230
+ expect(searchInput).toBeDefined();
231
+ });
232
+ });
@@ -4,8 +4,9 @@
4
4
  * A multi-panel cascading selection component following Ant Design patterns.
5
5
  */
6
6
 
7
- import { BaseElement, css } from '@duskmoon-dev/el-core';
8
- import type { Size, ValidationState } from '@duskmoon-dev/el-core';
7
+ import { BaseElement, css } from '@duskmoon-dev/el-base';
8
+ import type { Size, ValidationState } from '@duskmoon-dev/el-base';
9
+ import { css as cascaderCSS } from '@duskmoon-dev/core/components/cascader';
9
10
 
10
11
  // Icons with explicit dimensions for proper rendering
11
12
  const 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>`;
@@ -59,67 +60,41 @@ interface SearchResult {
59
60
  pathValues: string[];
60
61
  }
61
62
 
62
- // Styles
63
+ // Strip @layer wrapper from core CSS for Shadow DOM usage
64
+ const coreStyles = cascaderCSS.replace(/@layer\s+components\s*\{/, '').replace(/\}\s*$/, '');
65
+
66
+ // Styles: core CSS + element-specific overrides
63
67
  const styles = css`
64
68
  :host {
65
69
  display: inline-block;
66
70
  width: 100%;
67
71
  }
68
72
 
73
+ ${coreStyles}
74
+
75
+ /* Override: block display for full-width behavior */
69
76
  .cascader {
70
- position: relative;
71
- width: 100%;
77
+ display: block;
72
78
  }
73
79
 
74
- /* Trigger Button */
80
+ /* Override: trigger sizing & gap */
75
81
  .cascader-trigger {
76
- display: flex;
77
- align-items: center;
78
82
  gap: 0.5rem;
79
- width: 100%;
80
83
  min-height: 2.75rem;
81
84
  padding: 0.5rem 0.75rem;
82
- font-size: var(--font-size-md, 1rem);
83
- line-height: 1.5;
84
- color: var(--color-on-surface);
85
- background-color: var(--color-surface);
86
- border: 1px solid var(--color-outline);
87
- border-radius: var(--radius-md, 0.5rem);
88
- cursor: pointer;
89
- transition: border-color 150ms ease, box-shadow 150ms ease;
90
- }
91
-
92
- .cascader-trigger:hover:not(:disabled) {
93
- border-color: var(--color-on-surface-variant);
94
- }
95
-
96
- .cascader-trigger:focus {
97
- outline: none;
98
- border-color: var(--color-primary);
99
- box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-primary) 15%, transparent);
100
- }
101
-
102
- .cascader-trigger:disabled {
103
- cursor: not-allowed;
104
- opacity: 0.5;
105
- background-color: var(--color-surface-container);
106
85
  }
107
86
 
108
- /* Value Display */
87
+ /* Override: value display as block text */
109
88
  .cascader-value {
110
- flex: 1;
111
- overflow: hidden;
112
- text-overflow: ellipsis;
113
- white-space: nowrap;
89
+ display: block;
114
90
  text-align: left;
115
91
  }
116
92
 
117
93
  .cascader-placeholder {
118
- color: var(--color-on-surface-variant);
119
94
  opacity: 0.7;
120
95
  }
121
96
 
122
- /* Tags Container (for multiple mode) */
97
+ /* Tags (not in core) */
123
98
  .cascader-tags {
124
99
  display: flex;
125
100
  flex-wrap: wrap;
@@ -179,81 +154,20 @@ const styles = css`
179
154
  color: var(--color-on-surface-variant);
180
155
  }
181
156
 
182
- /* Icons */
183
- .cascader-arrow {
184
- display: inline-flex;
185
- align-items: center;
186
- justify-content: center;
187
- width: 20px;
188
- height: 20px;
189
- flex-shrink: 0;
190
- color: var(--color-on-surface-variant);
191
- transition: transform 150ms ease;
192
- }
193
-
194
- .cascader-arrow svg {
195
- width: 16px;
196
- height: 16px;
197
- display: block;
198
- }
199
-
200
- .cascader.open .cascader-arrow {
201
- transform: rotate(180deg);
202
- }
203
-
204
- .cascader-clear {
205
- display: inline-flex;
206
- align-items: center;
207
- justify-content: center;
208
- width: 20px;
209
- height: 20px;
210
- padding: 0;
211
- color: var(--color-on-surface-variant);
212
- background-color: transparent;
213
- border-radius: 50%;
214
- cursor: pointer;
215
- flex-shrink: 0;
216
- transition: background-color 150ms ease;
217
- }
218
-
157
+ /* Override: arrow/clear SVG sizing */
158
+ .cascader-arrow svg,
219
159
  .cascader-clear svg {
220
- width: 14px;
221
- height: 14px;
222
160
  display: block;
223
161
  }
224
162
 
225
- .cascader-clear:hover {
226
- background-color: var(--color-surface-container-high);
227
- }
228
-
229
- /* Dropdown - uses Popover API (top-layer requires position: fixed) */
163
+ /* Override: dropdown uses Popover API (top-layer requires position: fixed) */
230
164
  .cascader-dropdown {
231
165
  position: fixed;
232
- margin: 0;
233
- padding: 0;
234
- border: 1px solid var(--color-outline-variant);
235
- border-radius: var(--radius-md, 0.5rem);
236
- background-color: var(--color-surface);
237
- box-shadow: var(--shadow-md, 0 4px 6px -1px rgba(0, 0, 0, 0.1));
238
- overflow: hidden;
239
- display: none;
240
166
  flex-direction: column;
241
167
  z-index: 1000;
242
168
  }
243
169
 
244
- .cascader-dropdown:popover-open {
245
- display: flex;
246
- }
247
-
248
- /* Search */
249
- .cascader-search {
250
- display: flex;
251
- align-items: center;
252
- gap: 0.5rem;
253
- padding: 0.5rem;
254
- border-bottom: 1px solid var(--color-outline-variant);
255
- }
256
-
170
+ /* Search icon (not in core) */
257
171
  .cascader-search-icon {
258
172
  display: inline-flex;
259
173
  align-items: center;
@@ -270,86 +184,16 @@ const styles = css`
270
184
  display: block;
271
185
  }
272
186
 
273
- .cascader-search-input {
274
- flex: 1;
275
- padding: 0.375rem 0.5rem;
276
- font-size: var(--font-size-sm, 0.875rem);
277
- color: var(--color-on-surface);
278
- background-color: var(--color-surface-container);
279
- border: none;
280
- border-radius: var(--radius-sm, 0.25rem);
281
- outline: none;
282
- }
283
-
284
- .cascader-search-input:focus {
285
- background-color: var(--color-surface-container-high);
286
- }
287
-
288
- .cascader-search-input::placeholder {
289
- color: var(--color-on-surface-variant);
290
- opacity: 0.7;
291
- }
292
-
293
- /* Panels Container */
187
+ /* Override: panel max-height */
294
188
  .cascader-panels {
295
- display: flex;
296
189
  max-height: 18rem;
297
190
  }
298
191
 
299
- /* Panel */
300
192
  .cascader-panel {
301
- display: flex;
302
- flex-direction: column;
303
- min-width: 10rem;
304
- max-width: 14rem;
305
193
  max-height: 18rem;
306
- overflow-y: auto;
307
- border-right: 1px solid var(--color-outline-variant);
308
- }
309
-
310
- .cascader-panel:last-child {
311
- border-right: none;
312
- }
313
-
314
- .cascader-panel-options {
315
- padding: 0.25rem;
316
- }
317
-
318
- /* Option */
319
- .cascader-option {
320
- display: flex;
321
- align-items: center;
322
- gap: 0.5rem;
323
- width: 100%;
324
- padding: 0.5rem 0.75rem;
325
- font-size: var(--font-size-sm, 0.875rem);
326
- color: var(--color-on-surface);
327
- background-color: transparent;
328
- border: none;
329
- border-radius: var(--radius-sm, 0.25rem);
330
- cursor: pointer;
331
- text-align: left;
332
- transition: background-color 150ms ease;
333
- }
334
-
335
- .cascader-option:hover:not(.disabled) {
336
- background-color: var(--color-surface-container);
337
- }
338
-
339
- .cascader-option.active {
340
- background-color: var(--color-surface-container-high);
341
- }
342
-
343
- .cascader-option.selected {
344
- background-color: var(--color-primary-container, #e8def8);
345
- color: var(--color-on-primary-container, #1d1b20);
346
- }
347
-
348
- .cascader-option.disabled {
349
- opacity: 0.5;
350
- cursor: not-allowed;
351
194
  }
352
195
 
196
+ /* Checkbox (not in core) */
353
197
  .cascader-option-checkbox {
354
198
  display: flex;
355
199
  align-items: center;
@@ -362,27 +206,13 @@ const styles = css`
362
206
  flex-shrink: 0;
363
207
  }
364
208
 
365
- .cascader-option.selected .cascader-option-checkbox {
209
+ .cascader-option-selected .cascader-option-checkbox {
366
210
  background-color: var(--color-primary);
367
211
  border-color: var(--color-primary);
368
212
  color: var(--color-on-primary, white);
369
213
  }
370
214
 
371
- .cascader-option-label {
372
- flex: 1;
373
- overflow: hidden;
374
- text-overflow: ellipsis;
375
- white-space: nowrap;
376
- }
377
-
378
- .cascader-option-arrow {
379
- display: flex;
380
- align-items: center;
381
- justify-content: center;
382
- color: var(--color-on-surface-variant);
383
- flex-shrink: 0;
384
- }
385
-
215
+ /* Loading spinner (not in core) */
386
216
  .cascader-option-loading {
387
217
  display: flex;
388
218
  align-items: center;
@@ -399,7 +229,7 @@ const styles = css`
399
229
  to { transform: rotate(360deg); }
400
230
  }
401
231
 
402
- /* Search Results */
232
+ /* Search results (not in core) */
403
233
  .cascader-search-results {
404
234
  padding: 0.25rem;
405
235
  max-height: 18rem;
@@ -443,15 +273,7 @@ const styles = css`
443
273
  margin: 0 0.25rem;
444
274
  }
445
275
 
446
- /* Empty State */
447
- .cascader-empty {
448
- padding: 1.5rem;
449
- text-align: center;
450
- color: var(--color-on-surface-variant);
451
- font-size: var(--font-size-sm, 0.875rem);
452
- }
453
-
454
- /* Size Variants */
276
+ /* Size variants via :host (override core's container-class approach) */
455
277
  :host([size="sm"]) .cascader-trigger {
456
278
  min-height: 2.25rem;
457
279
  padding: 0.375rem 0.5rem;
@@ -466,7 +288,7 @@ const styles = css`
466
288
  border-radius: var(--radius-lg, 0.625rem);
467
289
  }
468
290
 
469
- /* Validation States */
291
+ /* Validation states via :host (override core's container-class approach) */
470
292
  :host([validation-state="invalid"]) .cascader-trigger {
471
293
  border-color: var(--color-error);
472
294
  }
@@ -480,7 +302,7 @@ const styles = css`
480
302
  border-color: var(--color-success);
481
303
  }
482
304
 
483
- /* Disabled State */
305
+ /* Disabled state via :host */
484
306
  :host([disabled]) {
485
307
  pointer-events: none;
486
308
  }
@@ -1047,7 +869,7 @@ export class ElDmCascader extends BaseElement {
1047
869
  // Rendering
1048
870
  protected render(): string {
1049
871
  return `
1050
- <div class="cascader ${this._isOpen ? 'open' : ''}">
872
+ <div class="cascader ${this._isOpen ? 'cascader-open' : ''}">
1051
873
  ${this._renderTrigger()}
1052
874
  ${this._renderDropdown()}
1053
875
  </div>
@@ -1157,9 +979,9 @@ export class ElDmCascader extends BaseElement {
1157
979
 
1158
980
  const classes = [
1159
981
  'cascader-option',
1160
- isActive ? 'active' : '',
1161
- isSelected ? 'selected' : '',
1162
- option.disabled ? 'disabled' : '',
982
+ isActive ? 'cascader-option-active' : '',
983
+ isSelected ? 'cascader-option-selected' : '',
984
+ option.disabled ? 'cascader-option-disabled' : '',
1163
985
  ].filter(Boolean).join(' ');
1164
986
 
1165
987
  return `
@@ -1182,7 +1004,7 @@ export class ElDmCascader extends BaseElement {
1182
1004
 
1183
1005
  return `
1184
1006
  <div class="cascader-panel">
1185
- <div class="cascader-panel-options">${optionsHtml}</div>
1007
+ <div class="cascader-options">${optionsHtml}</div>
1186
1008
  </div>
1187
1009
  `;
1188
1010
  }