@nuvia-ui/components 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +27 -0
- package/src/ds-accordion/ds-accordion-item.js +288 -0
- package/src/ds-accordion/ds-accordion-item.stories.js +82 -0
- package/src/ds-accordion/ds-accordion.a11y.test.js +92 -0
- package/src/ds-accordion/ds-accordion.js +68 -0
- package/src/ds-accordion/ds-accordion.stories.js +118 -0
- package/src/ds-accordion/ds-accordion.test.js +146 -0
- package/src/ds-accordion/index.js +2 -0
- package/src/ds-action-bar/ds-action-bar.js +116 -0
- package/src/ds-action-bar/ds-action-bar.stories.js +86 -0
- package/src/ds-action-bar/ds-action-bar.test.js +64 -0
- package/src/ds-action-bar/index.js +1 -0
- package/src/ds-alert/ds-alert.a11y.test.js +151 -0
- package/src/ds-alert/ds-alert.js +223 -0
- package/src/ds-alert/ds-alert.mdx +142 -0
- package/src/ds-alert/ds-alert.stories.js +166 -0
- package/src/ds-alert/ds-alert.test.js +256 -0
- package/src/ds-alert/index.js +1 -0
- package/src/ds-avatar/ds-avatar.a11y.test.js +45 -0
- package/src/ds-avatar/ds-avatar.js +216 -0
- package/src/ds-avatar/ds-avatar.stories.js +120 -0
- package/src/ds-avatar/ds-avatar.test.js +83 -0
- package/src/ds-avatar/index.js +1 -0
- package/src/ds-avatar-extended/ds-avatar-extended.a11y.test.js +29 -0
- package/src/ds-avatar-extended/ds-avatar-extended.js +108 -0
- package/src/ds-avatar-extended/ds-avatar-extended.stories.js +93 -0
- package/src/ds-avatar-extended/ds-avatar-extended.test.js +66 -0
- package/src/ds-avatar-extended/index.js +1 -0
- package/src/ds-banner/ds-banner.a11y.test.js +51 -0
- package/src/ds-banner/ds-banner.js +233 -0
- package/src/ds-banner/ds-banner.stories.js +185 -0
- package/src/ds-banner/ds-banner.test.js +116 -0
- package/src/ds-banner/index.js +1 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.js +135 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.stories.js +49 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.test.js +55 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.js +194 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.stories.js +54 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.test.js +33 -0
- package/src/ds-button/ds-button.a11y.test.js +49 -0
- package/src/ds-button/ds-button.js +205 -0
- package/src/ds-button/ds-button.mdx +141 -0
- package/src/ds-button/ds-button.stories.js +152 -0
- package/src/ds-button/ds-button.test.js +62 -0
- package/src/ds-button/index.js +1 -0
- package/src/ds-button-group/ds-button-group.js +82 -0
- package/src/ds-button-group/ds-button-group.mdx +39 -0
- package/src/ds-button-group/ds-button-group.stories.js +47 -0
- package/src/ds-button-group/ds-button-group.test.js +47 -0
- package/src/ds-button-group/index.js +1 -0
- package/src/ds-checkbox/ds-checkbox.a11y.test.js +79 -0
- package/src/ds-checkbox/ds-checkbox.js +271 -0
- package/src/ds-checkbox/ds-checkbox.stories.js +77 -0
- package/src/ds-checkbox/ds-checkbox.test.js +191 -0
- package/src/ds-checkbox/index.js +1 -0
- package/src/ds-checkbox-group/ds-checkbox-group.a11y.test.js +146 -0
- package/src/ds-checkbox-group/ds-checkbox-group.js +235 -0
- package/src/ds-checkbox-group/ds-checkbox-group.stories.js +210 -0
- package/src/ds-checkbox-group/ds-checkbox-group.test.js +150 -0
- package/src/ds-checkbox-group/index.js +1 -0
- package/src/ds-dialog/ds-dialog.js +466 -0
- package/src/ds-dialog/ds-dialog.stories.js +274 -0
- package/src/ds-dialog/ds-dialog.test.js +441 -0
- package/src/ds-dialog/index.js +1 -0
- package/src/ds-dropdown/ds-dropdown.a11y.test.js +80 -0
- package/src/ds-dropdown/ds-dropdown.js +891 -0
- package/src/ds-dropdown/ds-dropdown.stories.js +259 -0
- package/src/ds-dropdown/ds-dropdown.test.js +268 -0
- package/src/ds-dropdown/index.js +1 -0
- package/src/ds-dropdown-group/ds-dropdown-group.js +55 -0
- package/src/ds-dropdown-panel/ds-dropdown-panel.js +34 -0
- package/src/ds-file-uploaded/ds-file-uploaded.a11y.test.js +40 -0
- package/src/ds-file-uploaded/ds-file-uploaded.js +135 -0
- package/src/ds-file-uploaded/ds-file-uploaded.mdx +33 -0
- package/src/ds-file-uploaded/ds-file-uploaded.stories.js +81 -0
- package/src/ds-file-uploaded/ds-file-uploaded.test.js +85 -0
- package/src/ds-file-uploader/ds-file-uploader.a11y.test.js +61 -0
- package/src/ds-file-uploader/ds-file-uploader.js +442 -0
- package/src/ds-file-uploader/ds-file-uploader.mdx +44 -0
- package/src/ds-file-uploader/ds-file-uploader.stories.js +76 -0
- package/src/ds-file-uploader/ds-file-uploader.test.js +142 -0
- package/src/ds-header/ds-header.a11y.test.js +38 -0
- package/src/ds-header/ds-header.js +149 -0
- package/src/ds-header/ds-header.stories.js +63 -0
- package/src/ds-header/ds-header.test.js +52 -0
- package/src/ds-header/index.js +1 -0
- package/src/ds-header-nav/ds-header-nav.a11y.test.js +69 -0
- package/src/ds-header-nav/ds-header-nav.js +114 -0
- package/src/ds-header-nav/ds-header-nav.stories.js +17 -0
- package/src/ds-header-nav/ds-header-nav.test.js +93 -0
- package/src/ds-header-nav-item/ds-header-nav-item.a11y.test.js +71 -0
- package/src/ds-header-nav-item/ds-header-nav-item.js +124 -0
- package/src/ds-header-nav-item/ds-header-nav-item.stories.js +43 -0
- package/src/ds-header-nav-item/ds-header-nav-item.test.js +61 -0
- package/src/ds-icon/ds-icon.a11y.test.js +49 -0
- package/src/ds-icon/ds-icon.js +75 -0
- package/src/ds-icon/ds-icon.mdx +36 -0
- package/src/ds-icon/ds-icon.stories.js +88 -0
- package/src/ds-icon/ds-icon.test.js +97 -0
- package/src/ds-icon/index.js +1 -0
- package/src/ds-icon-button/ds-icon-button.a11y.test.js +55 -0
- package/src/ds-icon-button/ds-icon-button.js +224 -0
- package/src/ds-icon-button/ds-icon-button.mdx +131 -0
- package/src/ds-icon-button/ds-icon-button.stories.js +128 -0
- package/src/ds-icon-button/ds-icon-button.test.js +90 -0
- package/src/ds-icon-button/index.js +1 -0
- package/src/ds-input/ds-input.a11y.test.js +145 -0
- package/src/ds-input/ds-input.js +645 -0
- package/src/ds-input/ds-input.mdx +251 -0
- package/src/ds-input/ds-input.stories.js +298 -0
- package/src/ds-input/ds-input.test.js +792 -0
- package/src/ds-input/index.js +1 -0
- package/src/ds-link/ds-link.js +111 -0
- package/src/ds-link/ds-link.stories.js +56 -0
- package/src/ds-link/ds-link.test.js +74 -0
- package/src/ds-list-item/ds-list-item.a11y.test.js +39 -0
- package/src/ds-list-item/ds-list-item.js +292 -0
- package/src/ds-list-item/ds-list-item.stories.js +101 -0
- package/src/ds-list-item/ds-list-item.test.js +63 -0
- package/src/ds-menu/ds-menu.js +30 -0
- package/src/ds-menu/ds-menu.stories.js +120 -0
- package/src/ds-menu/ds-menu.test.js +123 -0
- package/src/ds-menu-group/ds-menu-group.js +101 -0
- package/src/ds-menu-group/ds-menu-group.stories.js +99 -0
- package/src/ds-nav-item/ds-nav-item.a11y.test.js +91 -0
- package/src/ds-nav-item/ds-nav-item.js +307 -0
- package/src/ds-nav-item/ds-nav-item.stories.js +99 -0
- package/src/ds-nav-item/ds-nav-item.test.js +169 -0
- package/src/ds-nav-item/index.js +1 -0
- package/src/ds-nav-vertical/ds-nav-vertical.a11y.test.js +69 -0
- package/src/ds-nav-vertical/ds-nav-vertical.js +173 -0
- package/src/ds-nav-vertical/ds-nav-vertical.stories.js +124 -0
- package/src/ds-nav-vertical/ds-nav-vertical.test.js +176 -0
- package/src/ds-nav-vertical/index.js +1 -0
- package/src/ds-pagination/ds-pagination.a11y.test.js +50 -0
- package/src/ds-pagination/ds-pagination.js +232 -0
- package/src/ds-pagination/ds-pagination.stories.js +63 -0
- package/src/ds-pagination/ds-pagination.test.js +141 -0
- package/src/ds-pagination/index.js +1 -0
- package/src/ds-progress-bar/ds-progress-bar.a11y.test.js +25 -0
- package/src/ds-progress-bar/ds-progress-bar.js +81 -0
- package/src/ds-progress-bar/ds-progress-bar.stories.js +69 -0
- package/src/ds-progress-bar/ds-progress-bar.test.js +60 -0
- package/src/ds-radio/ds-radio.a11y.test.js +69 -0
- package/src/ds-radio/ds-radio.js +240 -0
- package/src/ds-radio/ds-radio.stories.js +102 -0
- package/src/ds-radio/ds-radio.test.js +114 -0
- package/src/ds-radio/index.js +1 -0
- package/src/ds-radio-group/ds-radio-group.a11y.test.js +164 -0
- package/src/ds-radio-group/ds-radio-group.js +257 -0
- package/src/ds-radio-group/ds-radio-group.stories.js +247 -0
- package/src/ds-radio-group/ds-radio-group.test.js +194 -0
- package/src/ds-radio-group/index.js +1 -0
- package/src/ds-rich-list/ds-rich-list.js +246 -0
- package/src/ds-rich-list/ds-rich-list.stories.js +368 -0
- package/src/ds-rich-list/ds-rich-list.test.js +293 -0
- package/src/ds-rich-list-item/ds-rich-list-item.js +579 -0
- package/src/ds-rich-list-item/ds-rich-list-item.stories.js +197 -0
- package/src/ds-rich-list-item/ds-rich-list-item.test.js +434 -0
- package/src/ds-slider/ds-slider.js +399 -0
- package/src/ds-slider/ds-slider.stories.js +107 -0
- package/src/ds-slider/ds-slider.test.js +308 -0
- package/src/ds-spinner/ds-spinner.js +173 -0
- package/src/ds-spinner/ds-spinner.stories.js +52 -0
- package/src/ds-spinner/ds-spinner.test.js +50 -0
- package/src/ds-status-border/ds-status-border.js +88 -0
- package/src/ds-status-border/ds-status-border.stories.js +242 -0
- package/src/ds-status-border/ds-status-border.test.js +168 -0
- package/src/ds-stepper/ds-stepper.a11y.test.js +198 -0
- package/src/ds-stepper/ds-stepper.js +207 -0
- package/src/ds-stepper/ds-stepper.stories.js +530 -0
- package/src/ds-stepper/ds-stepper.test.js +311 -0
- package/src/ds-stepper-item/ds-stepper-item.js +485 -0
- package/src/ds-stepper-item/ds-stepper-item.stories.js +288 -0
- package/src/ds-switch/ds-switch.js +348 -0
- package/src/ds-switch/ds-switch.stories.js +145 -0
- package/src/ds-switch/ds-switch.test.js +226 -0
- package/src/ds-switch/index.js +1 -0
- package/src/ds-tab-item/ds-tab-item.js +341 -0
- package/src/ds-tab-item/ds-tab-item.stories.js +69 -0
- package/src/ds-tabs/ds-tab-panel.js +48 -0
- package/src/ds-tabs/ds-tabs.a11y.test.js +56 -0
- package/src/ds-tabs/ds-tabs.js +180 -0
- package/src/ds-tabs/ds-tabs.stories.js +152 -0
- package/src/ds-tabs/ds-tabs.test.js +306 -0
- package/src/ds-tabs/index.js +3 -0
- package/src/ds-tag-action/ds-tag-action.a11y.test.js +32 -0
- package/src/ds-tag-action/ds-tag-action.js +185 -0
- package/src/ds-tag-action/ds-tag-action.stories.js +55 -0
- package/src/ds-tag-action/ds-tag-action.test.js +44 -0
- package/src/ds-tag-removable/ds-tag-removable.a11y.test.js +24 -0
- package/src/ds-tag-removable/ds-tag-removable.js +146 -0
- package/src/ds-tag-removable/ds-tag-removable.stories.js +52 -0
- package/src/ds-tag-removable/ds-tag-removable.test.js +46 -0
- package/src/ds-tag-status/ds-tag-status.a11y.test.js +93 -0
- package/src/ds-tag-status/ds-tag-status.js +164 -0
- package/src/ds-tag-status/ds-tag-status.stories.js +200 -0
- package/src/ds-tag-status/ds-tag-status.test.js +140 -0
- package/src/ds-tag-status/index.js +1 -0
- package/src/ds-textarea/ds-textarea-clearable.test.js +89 -0
- package/src/ds-textarea/ds-textarea.a11y.test.js +66 -0
- package/src/ds-textarea/ds-textarea.js +505 -0
- package/src/ds-textarea/ds-textarea.stories.js +335 -0
- package/src/ds-textarea/ds-textarea.test.js +218 -0
- package/src/ds-textarea/index.js +1 -0
- package/src/ds-thumbnail/ds-thumbnail.js +207 -0
- package/src/ds-thumbnail/ds-thumbnail.stories.js +217 -0
- package/src/ds-thumbnail/ds-thumbnail.test.js +220 -0
- package/src/ds-toast/ds-toast-provider.js +110 -0
- package/src/ds-toast/ds-toast.a11y.test.js +34 -0
- package/src/ds-toast/ds-toast.js +243 -0
- package/src/ds-toast/ds-toast.stories.js +143 -0
- package/src/ds-toast/ds-toast.test.js +93 -0
- package/src/ds-toast/index.js +2 -0
- package/src/ds-tooltip/ds-tooltip.a11y.test.js +110 -0
- package/src/ds-tooltip/ds-tooltip.js +217 -0
- package/src/ds-tooltip/ds-tooltip.mdx +75 -0
- package/src/ds-tooltip/ds-tooltip.stories.js +72 -0
- package/src/ds-tooltip/ds-tooltip.test.js +191 -0
- package/src/ds-tooltip/index.js +1 -0
- package/src/ds-tooltip/positioner.js +117 -0
- package/src/index.js +50 -0
- package/src/mixins/field-label.mixin.js +113 -0
- package/src/mixins/field-message.mixin.js +66 -0
- package/src/token-provider/index.js +1 -0
- package/src/token-provider/token-provider.a11y.test.js +44 -0
- package/src/token-provider/token-provider.js +85 -0
- package/src/token-provider/token-provider.stories.js +105 -0
- package/src/token-provider/token-provider.test.js +134 -0
- package/src/utils/number-input.utils.js +42 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
2
|
+
import '../ds-checkbox/ds-checkbox.js';
|
|
3
|
+
import '../ds-radio/ds-radio.js';
|
|
4
|
+
import '../ds-thumbnail/ds-thumbnail.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rich List Item — a media-rich, interactive list item for complex list patterns.
|
|
8
|
+
*
|
|
9
|
+
* @element ds-rich-list-item
|
|
10
|
+
*
|
|
11
|
+
* @prop {boolean} selected - Whether the item is selected
|
|
12
|
+
* @prop {boolean} disabled - Whether the item is disabled
|
|
13
|
+
* @prop {boolean} clickable - Enables hover/active/focus interaction
|
|
14
|
+
* @prop {string} href - Makes item navigable (renders anchor behavior)
|
|
15
|
+
* @prop {string} value - Value for selection/form association
|
|
16
|
+
* @prop {boolean} multiline - If true, min-height instead of fixed height, no truncation
|
|
17
|
+
*
|
|
18
|
+
* @slot media - 1:1 aspect ratio container (40×40px)
|
|
19
|
+
* @slot title - Primary text
|
|
20
|
+
* @slot description - Secondary text
|
|
21
|
+
* @slot custom - Free-form customizable area
|
|
22
|
+
* @slot action-group - Expects ds-button-group
|
|
23
|
+
*
|
|
24
|
+
* @fires change - Fired when selection state changes
|
|
25
|
+
* @fires rich-list-item-click - Fired when a clickable item is clicked
|
|
26
|
+
*
|
|
27
|
+
* @csspart container - The main item container
|
|
28
|
+
*/
|
|
29
|
+
export class DsRichListItem extends LitElement {
|
|
30
|
+
static properties = {
|
|
31
|
+
selected: { type: Boolean, reflect: true },
|
|
32
|
+
disabled: { type: Boolean, reflect: true },
|
|
33
|
+
clickable: { type: Boolean, reflect: true },
|
|
34
|
+
href: { type: String, reflect: true },
|
|
35
|
+
value: { type: String },
|
|
36
|
+
multiline: { type: Boolean, reflect: true },
|
|
37
|
+
// Internal: set by parent ds-rich-list
|
|
38
|
+
_selectable: { type: String, attribute: false },
|
|
39
|
+
_name: { type: String, attribute: false },
|
|
40
|
+
// Internal: track which slots are populated
|
|
41
|
+
_hasMedia: { type: Boolean, attribute: false },
|
|
42
|
+
_hasCustom: { type: Boolean, attribute: false },
|
|
43
|
+
_hasActionGroup: { type: Boolean, attribute: false },
|
|
44
|
+
// Media helper props
|
|
45
|
+
media: { type: String, reflect: true },
|
|
46
|
+
mediaAlt: { type: String, attribute: 'media-alt' }
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
static styles = css`
|
|
50
|
+
:host {
|
|
51
|
+
display: block;
|
|
52
|
+
outline: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ─── Container ─── */
|
|
56
|
+
.item {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: flex-start; /* Top alignment */
|
|
59
|
+
min-height: 72px; /* Consistent minimum height */
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
padding: var(--ds-space-md);
|
|
62
|
+
gap: var(--ds-space-md);
|
|
63
|
+
background: var(--ds-color-bg-default);
|
|
64
|
+
border: 1px solid var(--ds-color-border-default);
|
|
65
|
+
border-radius: var(--ds-radius-container);
|
|
66
|
+
position: relative;
|
|
67
|
+
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
|
68
|
+
cursor: default;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Multiline: allow growth */
|
|
72
|
+
:host([multiline]) .item {
|
|
73
|
+
height: auto;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ─── Interactive (clickable, href, or selectable) ─── */
|
|
77
|
+
:host([interactive]) .item {
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
:host([interactive]:not([disabled])) .item:hover {
|
|
82
|
+
box-shadow: var(--ds-elevation-floating);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ─── Focus ─── */
|
|
86
|
+
:host(:focus-visible) .item {
|
|
87
|
+
outline: 2px solid var(--ds-color-border-focus);
|
|
88
|
+
outline-offset: 2px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ─── Selected ─── */
|
|
92
|
+
:host([selected]) .item {
|
|
93
|
+
border-color: var(--ds-color-border-selected-secondary);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ─── Disabled ─── */
|
|
97
|
+
:host([disabled]) .item {
|
|
98
|
+
pointer-events: none;
|
|
99
|
+
cursor: not-allowed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
:host([disabled]) .item:hover {
|
|
103
|
+
box-shadow: none;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
:host([disabled]) .media-container {
|
|
107
|
+
opacity: 0.5;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:host([disabled]) .content ::slotted(*) {
|
|
111
|
+
color: var(--ds-color-text-disabled);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ─── Selection Control ─── */
|
|
115
|
+
.selection-control {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
flex-shrink: 0;
|
|
120
|
+
position: relative;
|
|
121
|
+
z-index: 2;
|
|
122
|
+
width: var(--ds-size-40); /* Explicit width for alignment */
|
|
123
|
+
height: var(--ds-size-40); /* Explicit height for alignment */
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.selection-control ds-checkbox,
|
|
127
|
+
.selection-control ds-radio {
|
|
128
|
+
pointer-events: none; /* Let the row handle click */
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ─── Media Container ─── */
|
|
132
|
+
.media-container {
|
|
133
|
+
width: var(--ds-size-40);
|
|
134
|
+
height: var(--ds-size-40);
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
border-radius: var(--ds-radius-media);
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.media-container ::slotted(*) {
|
|
144
|
+
width: 100%;
|
|
145
|
+
height: 100%;
|
|
146
|
+
object-fit: cover;
|
|
147
|
+
display: block;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* Fallback img rendered via media prop uses ds-thumbnail */
|
|
151
|
+
.media-container ds-thumbnail {
|
|
152
|
+
width: 100%;
|
|
153
|
+
height: 100%;
|
|
154
|
+
display: block;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Hide container when slot is empty AND not forced by parent */
|
|
158
|
+
:host(:not([has-media]):not([force-media])) .media-container {
|
|
159
|
+
display: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* ─── Content (Title + Description) ─── */
|
|
163
|
+
.content {
|
|
164
|
+
flex: 1;
|
|
165
|
+
min-width: 0; /* Critical for truncation */
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
justify-content: center; /* Center content within the 40px min-height if single line */
|
|
169
|
+
min-height: var(--ds-size-40); /* Aligns with media/checkbox */
|
|
170
|
+
gap: 0; /* Remove gap as requested */
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.content ::slotted([slot="title"]) {
|
|
174
|
+
font: var(--ds-typo-content-body-bold);
|
|
175
|
+
color: var(--ds-color-text-default);
|
|
176
|
+
white-space: nowrap;
|
|
177
|
+
overflow: hidden;
|
|
178
|
+
text-overflow: ellipsis;
|
|
179
|
+
display: block;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.content ::slotted([slot="description"]) {
|
|
183
|
+
font: var(--ds-typo-content-body-regular);
|
|
184
|
+
color: var(--ds-color-text-secondary);
|
|
185
|
+
white-space: nowrap;
|
|
186
|
+
overflow: hidden;
|
|
187
|
+
text-overflow: ellipsis;
|
|
188
|
+
display: block;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Multiline: disable truncation */
|
|
192
|
+
:host([multiline]) .content ::slotted([slot="title"]),
|
|
193
|
+
:host([multiline]) .content ::slotted([slot="description"]) {
|
|
194
|
+
white-space: normal;
|
|
195
|
+
overflow: visible;
|
|
196
|
+
text-overflow: clip;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* ─── Custom Slot ─── */
|
|
200
|
+
.custom-container {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
flex-shrink: 0;
|
|
204
|
+
position: relative;
|
|
205
|
+
z-index: 2;
|
|
206
|
+
min-width: var(--ds-rich-list-custom-width, auto);
|
|
207
|
+
height: var(--ds-size-40); /* Force 40px height for vertical centering alignment */
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
:host(:not([has-custom]):not([force-custom])) .custom-container {
|
|
211
|
+
display: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* ─── Action Group ─── */
|
|
215
|
+
.action-group-container {
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
flex-shrink: 0;
|
|
219
|
+
position: relative;
|
|
220
|
+
z-index: 2;
|
|
221
|
+
height: var(--ds-size-40); /* Force 40px height for vertical centering alignment */
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
:host(:not([has-action-group]):not([force-action-group])) .action-group-container {
|
|
225
|
+
display: none;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* ─── Link (hidden anchor for href) ─── */
|
|
229
|
+
a.item-link {
|
|
230
|
+
position: absolute;
|
|
231
|
+
inset: 0;
|
|
232
|
+
z-index: 1;
|
|
233
|
+
opacity: 0;
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
constructor() {
|
|
238
|
+
super();
|
|
239
|
+
this.selected = false;
|
|
240
|
+
this.disabled = false;
|
|
241
|
+
this.clickable = false;
|
|
242
|
+
this.href = '';
|
|
243
|
+
this.value = '';
|
|
244
|
+
this.multiline = false;
|
|
245
|
+
this._selectable = 'none';
|
|
246
|
+
this._name = '';
|
|
247
|
+
this._hasMedia = false;
|
|
248
|
+
this._hasCustom = false;
|
|
249
|
+
this._hasActionGroup = false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
connectedCallback() {
|
|
253
|
+
super.connectedCallback();
|
|
254
|
+
// Set ARIA role based on context
|
|
255
|
+
this._updateRole();
|
|
256
|
+
// Reflect interactive state for CSS
|
|
257
|
+
this.toggleAttribute('interactive', this._isInteractive());
|
|
258
|
+
// Host-level keydown for Enter/Space activation when item has focus
|
|
259
|
+
this.addEventListener('keydown', this._handleHostKeydown);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
disconnectedCallback() {
|
|
263
|
+
super.disconnectedCallback();
|
|
264
|
+
this.removeEventListener('keydown', this._handleHostKeydown);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
updated(changedProperties) {
|
|
268
|
+
if (changedProperties.has('_selectable') || changedProperties.has('disabled')) {
|
|
269
|
+
this._updateRole();
|
|
270
|
+
}
|
|
271
|
+
if (changedProperties.has('disabled')) {
|
|
272
|
+
this._updateDisabledState();
|
|
273
|
+
}
|
|
274
|
+
if (changedProperties.has('clickable') || changedProperties.has('href') || changedProperties.has('_selectable') || changedProperties.has('disabled')) {
|
|
275
|
+
this.toggleAttribute('interactive', this._isInteractive());
|
|
276
|
+
}
|
|
277
|
+
if (changedProperties.has('media')) {
|
|
278
|
+
// Allow re-evaluating has-media if prop changes (though usually slotchange handles initial)
|
|
279
|
+
// We need to ensure has-media is true if media prop is set
|
|
280
|
+
this.toggleAttribute('has-media', !!this.media || this._hasMedia);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
_updateRole() {
|
|
285
|
+
if (this._selectable !== 'none') {
|
|
286
|
+
this.setAttribute('role', 'option');
|
|
287
|
+
this.setAttribute('aria-selected', String(this.selected));
|
|
288
|
+
} else {
|
|
289
|
+
this.setAttribute('role', 'listitem');
|
|
290
|
+
this.removeAttribute('aria-selected');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (this.disabled) {
|
|
294
|
+
this.setAttribute('aria-disabled', 'true');
|
|
295
|
+
} else {
|
|
296
|
+
this.removeAttribute('aria-disabled');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
_updateDisabledState() {
|
|
301
|
+
const slots = ['action-group', 'custom'];
|
|
302
|
+
slots.forEach(slotName => {
|
|
303
|
+
const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
|
|
304
|
+
if (slot) {
|
|
305
|
+
const assignedElements = slot.assignedElements({ flatten: true });
|
|
306
|
+
assignedElements.forEach(el => {
|
|
307
|
+
// Try to set disabled property if available
|
|
308
|
+
if ('disabled' in el) {
|
|
309
|
+
el.disabled = this.disabled;
|
|
310
|
+
}
|
|
311
|
+
// Also try looking for children that might be buttons (e.g. inside ds-button-group)
|
|
312
|
+
if (el.tagName === 'DS-BUTTON-GROUP') {
|
|
313
|
+
Array.from(el.children).forEach(child => {
|
|
314
|
+
if ('disabled' in child) {
|
|
315
|
+
child.disabled = this.disabled;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
_isInteractive() {
|
|
325
|
+
if (this.disabled) return false;
|
|
326
|
+
return this.clickable || this.href || this._selectable !== 'none';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
_isEventFromInteractiveChild(e) {
|
|
330
|
+
const path = e.composedPath();
|
|
331
|
+
|
|
332
|
+
for (const el of path) {
|
|
333
|
+
if (el === this) break; // Stop checking when we reach the host
|
|
334
|
+
if (el === window || el === document || el === this.shadowRoot) continue;
|
|
335
|
+
if (!el.tagName) continue;
|
|
336
|
+
|
|
337
|
+
// Allow the main item link (href overlay)
|
|
338
|
+
if (el.classList && el.classList.contains('item-link')) continue;
|
|
339
|
+
|
|
340
|
+
const tag = el.tagName.toLowerCase();
|
|
341
|
+
const interactiveTags = ['button', 'a', 'input', 'select', 'textarea', 'label'];
|
|
342
|
+
|
|
343
|
+
// Check for DS components that are interactive
|
|
344
|
+
// Exclude non-interactive ones
|
|
345
|
+
const isDsComponent = tag.startsWith('ds-') && !['ds-icon', 'ds-avatar', 'ds-badge'].includes(tag);
|
|
346
|
+
|
|
347
|
+
if (interactiveTags.includes(tag) || isDsComponent || el.hasAttribute('onclick') || el.getAttribute('role') === 'button') {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
_handleClick(e) {
|
|
355
|
+
if (this.disabled) return;
|
|
356
|
+
if (this._isEventFromInteractiveChild(e)) return;
|
|
357
|
+
const path = e.composedPath();
|
|
358
|
+
|
|
359
|
+
// Selection logic
|
|
360
|
+
if (this._selectable === 'single') {
|
|
361
|
+
if (!this.selected) {
|
|
362
|
+
this.selected = true;
|
|
363
|
+
this._dispatchChange();
|
|
364
|
+
}
|
|
365
|
+
} else if (this._selectable === 'multiple') {
|
|
366
|
+
this.selected = !this.selected;
|
|
367
|
+
this._dispatchChange();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Clickable event
|
|
371
|
+
if (this.clickable || this.href) {
|
|
372
|
+
this.dispatchEvent(new CustomEvent('rich-list-item-click', {
|
|
373
|
+
detail: { value: this.value, href: this.href },
|
|
374
|
+
bubbles: true,
|
|
375
|
+
composed: true
|
|
376
|
+
}));
|
|
377
|
+
|
|
378
|
+
// Navigate if href
|
|
379
|
+
// Note: If the user clicked a.item-link, the browser handles navigation naturally.
|
|
380
|
+
// We only need manual navigation if the click was on the padding/div.
|
|
381
|
+
// But if specific href logic is needed (like checking meta keys), we keep it.
|
|
382
|
+
// However, verify we don't double-navigate if a.item-link was clicked.
|
|
383
|
+
const clickedLink = path.find(el => el.classList && el.classList.contains('item-link'));
|
|
384
|
+
if (this.href && !clickedLink && !e.defaultPrevented) {
|
|
385
|
+
window.open(this.href, e.metaKey || e.ctrlKey ? '_blank' : '_self');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
_handleHostKeydown = (e) => {
|
|
391
|
+
if (this.disabled) return;
|
|
392
|
+
// Only handle when the host itself is the target (not bubbled from internal elements)
|
|
393
|
+
if (e.target !== this) return;
|
|
394
|
+
|
|
395
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
396
|
+
e.preventDefault();
|
|
397
|
+
this._activate(e);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
_handleKeydown(e) {
|
|
402
|
+
if (this._isEventFromInteractiveChild(e)) return;
|
|
403
|
+
|
|
404
|
+
// This handles keydowns from within the shadow DOM (e.g. inner .item div)
|
|
405
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
406
|
+
e.preventDefault();
|
|
407
|
+
this._activate(e);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
_activate(e) {
|
|
412
|
+
// Selection logic
|
|
413
|
+
if (this._selectable === 'single') {
|
|
414
|
+
if (!this.selected) {
|
|
415
|
+
this.selected = true;
|
|
416
|
+
this._dispatchChange();
|
|
417
|
+
}
|
|
418
|
+
} else if (this._selectable === 'multiple') {
|
|
419
|
+
this.selected = !this.selected;
|
|
420
|
+
this._dispatchChange();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Clickable event
|
|
424
|
+
if (this.clickable || this.href) {
|
|
425
|
+
this.dispatchEvent(new CustomEvent('rich-list-item-click', {
|
|
426
|
+
detail: { value: this.value, href: this.href },
|
|
427
|
+
bubbles: true,
|
|
428
|
+
composed: true
|
|
429
|
+
}));
|
|
430
|
+
|
|
431
|
+
if (this.href) {
|
|
432
|
+
window.open(this.href, e.metaKey || e.ctrlKey ? '_blank' : '_self');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
_dispatchChange() {
|
|
438
|
+
this.setAttribute('aria-selected', String(this.selected));
|
|
439
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
440
|
+
detail: { selected: this.selected, value: this.value },
|
|
441
|
+
bubbles: true,
|
|
442
|
+
composed: true
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
_handleSlotChange(slotName, e) {
|
|
447
|
+
const slot = e.target;
|
|
448
|
+
const nodes = slot.assignedNodes({ flatten: true });
|
|
449
|
+
const hasContent = this._hasVisibleContent(nodes);
|
|
450
|
+
|
|
451
|
+
let changed = false;
|
|
452
|
+
|
|
453
|
+
if (slotName === 'media') {
|
|
454
|
+
if (this._hasMedia !== hasContent) {
|
|
455
|
+
this._hasMedia = hasContent;
|
|
456
|
+
this.toggleAttribute('has-media', hasContent || !!this.media);
|
|
457
|
+
changed = true;
|
|
458
|
+
}
|
|
459
|
+
} else if (slotName === 'custom') {
|
|
460
|
+
if (this._hasCustom !== hasContent) {
|
|
461
|
+
this._hasCustom = hasContent;
|
|
462
|
+
this.toggleAttribute('has-custom', hasContent);
|
|
463
|
+
this._updateDisabledState();
|
|
464
|
+
changed = true;
|
|
465
|
+
}
|
|
466
|
+
} else if (slotName === 'action-group') {
|
|
467
|
+
if (this._hasActionGroup !== hasContent) {
|
|
468
|
+
this._hasActionGroup = hasContent;
|
|
469
|
+
this.toggleAttribute('has-action-group', hasContent);
|
|
470
|
+
this._updateDisabledState();
|
|
471
|
+
changed = true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (changed) {
|
|
476
|
+
this.dispatchEvent(new CustomEvent('rich-list-item-structure-change', {
|
|
477
|
+
bubbles: true,
|
|
478
|
+
composed: true
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
_hasVisibleContent(nodes) {
|
|
484
|
+
if (!nodes) return false;
|
|
485
|
+
return Array.from(nodes).some(node => {
|
|
486
|
+
// Text nodes: check if they contain more than just whitespace
|
|
487
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
488
|
+
return node.textContent.trim() !== '';
|
|
489
|
+
}
|
|
490
|
+
// Element nodes:
|
|
491
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
492
|
+
// Recursively check children of generic containers (div, span)
|
|
493
|
+
// This prevents <span slot="action-group"> </span> from counting as content
|
|
494
|
+
if (['DIV', 'SPAN'].includes(node.tagName)) {
|
|
495
|
+
// If the element has direct attributes that might make it visible (like styles or classes),
|
|
496
|
+
// we should probably consider it "visible" content.
|
|
497
|
+
// But for now, let's focus on the "empty wrapper" case.
|
|
498
|
+
// If it has children, check them.
|
|
499
|
+
if (node.childNodes.length > 0) {
|
|
500
|
+
return this._hasVisibleContent(node.childNodes);
|
|
501
|
+
}
|
|
502
|
+
// Empty div/span -> not visible
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
// Other elements (img, button, ds-icon, etc) are considered visible content
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
return false;
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
_renderSelectionControl() {
|
|
513
|
+
if (this._selectable === 'single') {
|
|
514
|
+
return html`
|
|
515
|
+
<div class="selection-control">
|
|
516
|
+
<ds-radio
|
|
517
|
+
standalone
|
|
518
|
+
?checked=${this.selected}
|
|
519
|
+
?disabled=${this.disabled}
|
|
520
|
+
name=${this._name}
|
|
521
|
+
value=${this.value}
|
|
522
|
+
tabindex="-1"
|
|
523
|
+
></ds-radio>
|
|
524
|
+
</div>
|
|
525
|
+
`;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (this._selectable === 'multiple') {
|
|
529
|
+
return html`
|
|
530
|
+
<div class="selection-control">
|
|
531
|
+
<ds-checkbox
|
|
532
|
+
standalone
|
|
533
|
+
?checked=${this.selected}
|
|
534
|
+
?disabled=${this.disabled}
|
|
535
|
+
tabindex="-1"
|
|
536
|
+
></ds-checkbox>
|
|
537
|
+
</div>
|
|
538
|
+
`;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return nothing;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
render() {
|
|
545
|
+
return html`
|
|
546
|
+
<div
|
|
547
|
+
class="item"
|
|
548
|
+
part="container"
|
|
549
|
+
@click=${this._handleClick}
|
|
550
|
+
@keydown=${this._handleKeydown}
|
|
551
|
+
>
|
|
552
|
+
${this.href ? html`<a class="item-link" href=${this.href} tabindex="-1" aria-hidden="true"></a>` : nothing}
|
|
553
|
+
|
|
554
|
+
${this._renderSelectionControl()}
|
|
555
|
+
|
|
556
|
+
<div class="media-container">
|
|
557
|
+
<slot name="media" @slotchange=${(e) => this._handleSlotChange('media', e)}>
|
|
558
|
+
${this.media ? html`<ds-thumbnail src="${this.media}" alt="${this.mediaAlt || ''}" fit="cover"></ds-thumbnail>` : nothing}
|
|
559
|
+
</slot>
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<div class="content">
|
|
563
|
+
<slot name="title"></slot>
|
|
564
|
+
<slot name="description"></slot>
|
|
565
|
+
</div>
|
|
566
|
+
|
|
567
|
+
<div class="custom-container">
|
|
568
|
+
<slot name="custom" @slotchange=${(e) => this._handleSlotChange('custom', e)}></slot>
|
|
569
|
+
</div>
|
|
570
|
+
|
|
571
|
+
<div class="action-group-container">
|
|
572
|
+
<slot name="action-group" @slotchange=${(e) => this._handleSlotChange('action-group', e)}></slot>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
`;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
customElements.define('ds-rich-list-item', DsRichListItem);
|