@c2n/list 0.0.6
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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/list.js +317 -0
- package/package.json +61 -0
- package/types/src/list.d.ts +109 -0
- package/types/src/list.d.ts.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Nguyen Thai Vinh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @c2n/list
|
|
2
|
+
|
|
3
|
+
Vertical list of `c2-list-item` rows with single or multiple selection, built with Lit.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @c2n/list @c2n/list-item
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script type="module">
|
|
11
|
+
import '@c2n/list'
|
|
12
|
+
import '@c2n/list-item'
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<c2-list value="design" multiple>
|
|
16
|
+
<h6>Folders</h6>
|
|
17
|
+
<c2-list-item value="inbox">Inbox</c2-list-item>
|
|
18
|
+
<c2-list-item value="design">Design</c2-list-item>
|
|
19
|
+
<hr />
|
|
20
|
+
<c2-list-item value="archive">Archive</c2-list-item>
|
|
21
|
+
</c2-list>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **Selection**: the list owns it. `value` is `;`-separated in the attribute and an array in the property; the matching rows are marked selected through context. `multiple` allows several rows, `required` keeps at least one, `disabled` freezes the list. `selection-change` carries `detail.value` and `detail.data` (each row's `data` property).
|
|
25
|
+
- **Keyboard**: the list is a `listbox` with a roving `tabindex`. Arrow Up/Down, Home/End move between enabled rows, Enter and Space select, typing jumps to the next matching row.
|
|
26
|
+
- **Structure**: `<hr>` dividers and `<h1>`–`<h6>` group headings can sit between rows; they are styled by the `__divider` and `__heading` tokens and ignored by selection.
|
|
27
|
+
- **Layout**: `--c2-list--max-height` makes a long list scroll; a list with zero vertical padding passes its corner radius to the first and last row.
|
|
28
|
+
|
|
29
|
+
Theming: `--c2-list--*` for the container plus `--c2-list__divider--*` and `--c2-list__heading--*`; the rows are themed with `--c2-list-item--*`, which inherit through the list. The full list is in `custom-elements.json` and on the docs site.
|
package/dist/list.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { LitElement, html, unsafeCSS } from "lit";
|
|
2
|
+
import { customElement, property, query } from "lit/decorators.js";
|
|
3
|
+
import { selectedItemValueContext } from "@c2n/list-item/list-item-context.js";
|
|
4
|
+
import { ListItem } from "@c2n/list-item";
|
|
5
|
+
import { arrayPropertyConverter } from "@c2n/core/lit-helper.js";
|
|
6
|
+
//#region src/list.scss?inline
|
|
7
|
+
var list_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n gap: var(--c2-list--gap);\n overflow: auto;\n overscroll-behavior: contain;\n pointer-events: initial;\n max-height: var(--c2-list--max-height);\n border-top: var(--c2-list--border-top);\n border-bottom: var(--c2-list--border-bottom);\n border-right: var(--c2-list--border-right);\n border-left: var(--c2-list--border-left);\n padding-top: var(--c2-list--padding-top,4px);\n padding-bottom: var(--c2-list--padding-bottom,4px);\n padding-right: var(--c2-list--padding-right,4px);\n padding-left: var(--c2-list--padding-left,4px);\n background: var(--c2-list--background,#ffffff);\n box-shadow: var(--c2-list--box-shadow);\n border-top-left-radius: var(--c2-list--border-top-left-radius);\n border-top-right-radius: var(--c2-list--border-top-right-radius);\n border-bottom-left-radius: var(--c2-list--border-bottom-left-radius);\n border-bottom-right-radius: var(--c2-list--border-bottom-right-radius);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n:host(:focus) {\n outline: none;\n}\n\n::slotted(hr) {\n flex: none;\n border: none;\n border-top: 1px solid var(--c2-list__divider--color,#e4e4e7);\n margin: var(--c2-list__divider--margin,4px 0);\n}\n\n::slotted(:is(h1, h2, h3, h4, h5, h6)) {\n flex: none;\n margin: 0;\n color: var(--c2-list__heading--color,#71717a);\n font-size: var(--c2-list__heading--font-size,11px);\n font-weight: var(--c2-list__heading--font-weight,600);\n letter-spacing: var(--c2-list__heading--letter-spacing,0.06em);\n text-transform: var(--c2-list__heading--text-transform,uppercase);\n line-height: 1.4;\n padding-top: var(--c2-list__heading--padding-top,10px);\n padding-right: var(--c2-list__heading--padding-right,10px);\n padding-bottom: var(--c2-list__heading--padding-bottom,4px);\n padding-left: var(--c2-list__heading--padding-left,10px);\n user-select: none;\n}\n\n:host(.padding-top-0) ::slotted(c2-list-item:first-child) {\n --c2-list-item--border-top-left-radius: var(--c2-list--border-top-left-radius);\n --c2-list-item--border-top-right-radius: var(--c2-list--border-top-right-radius);\n}\n\n:host(.padding-bottom-0) ::slotted(c2-list-item:last-child) {\n --c2-list-item--border-bottom-left-radius: var(--c2-list--border-bottom-left-radius);\n --c2-list-item--border-bottom-right-radius: var(--c2-list--border-bottom-right-radius);\n}\n\n:host([disabled]) {\n pointer-events: none;\n opacity: 0.38;\n}";
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region ../../../node_modules/@lit/context/lib/context-request-event.js
|
|
10
|
+
/**
|
|
11
|
+
* @license
|
|
12
|
+
* Copyright 2021 Google LLC
|
|
13
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
14
|
+
*/
|
|
15
|
+
var s$1 = class extends Event {
|
|
16
|
+
constructor(s, t, e, o) {
|
|
17
|
+
super("context-request", {
|
|
18
|
+
bubbles: !0,
|
|
19
|
+
composed: !0
|
|
20
|
+
}), this.context = s, this.contextTarget = t, this.callback = e, this.subscribe = o ?? !1;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region ../../../node_modules/@lit/context/lib/value-notifier.js
|
|
25
|
+
/**
|
|
26
|
+
* @license
|
|
27
|
+
* Copyright 2021 Google LLC
|
|
28
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
29
|
+
*/
|
|
30
|
+
var s = class {
|
|
31
|
+
get value() {
|
|
32
|
+
return this.o;
|
|
33
|
+
}
|
|
34
|
+
set value(s) {
|
|
35
|
+
this.setValue(s);
|
|
36
|
+
}
|
|
37
|
+
setValue(s, t = !1) {
|
|
38
|
+
const i = t || !Object.is(s, this.o);
|
|
39
|
+
this.o = s, i && this.updateObservers();
|
|
40
|
+
}
|
|
41
|
+
constructor(s) {
|
|
42
|
+
this.subscriptions = /* @__PURE__ */ new Map(), this.updateObservers = () => {
|
|
43
|
+
for (const [s, { disposer: t }] of this.subscriptions) s(this.o, t);
|
|
44
|
+
}, void 0 !== s && (this.value = s);
|
|
45
|
+
}
|
|
46
|
+
addCallback(s, t, i) {
|
|
47
|
+
if (!i) return void s(this.value);
|
|
48
|
+
this.subscriptions.has(s) || this.subscriptions.set(s, {
|
|
49
|
+
disposer: () => {
|
|
50
|
+
this.subscriptions.delete(s);
|
|
51
|
+
},
|
|
52
|
+
consumerHost: t
|
|
53
|
+
});
|
|
54
|
+
const { disposer: h } = this.subscriptions.get(s);
|
|
55
|
+
s(this.value, h);
|
|
56
|
+
}
|
|
57
|
+
clearCallbacks() {
|
|
58
|
+
this.subscriptions.clear();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region ../../../node_modules/@lit/context/lib/controllers/context-provider.js
|
|
63
|
+
/**
|
|
64
|
+
* @license
|
|
65
|
+
* Copyright 2021 Google LLC
|
|
66
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
67
|
+
*/ var e$1 = class extends Event {
|
|
68
|
+
constructor(t, s) {
|
|
69
|
+
super("context-provider", {
|
|
70
|
+
bubbles: !0,
|
|
71
|
+
composed: !0
|
|
72
|
+
}), this.context = t, this.contextTarget = s;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var i = class extends s {
|
|
76
|
+
constructor(s, e, i) {
|
|
77
|
+
super(void 0 !== e.context ? e.initialValue : i), this.onContextRequest = (t) => {
|
|
78
|
+
if (t.context !== this.context) return;
|
|
79
|
+
const s = t.contextTarget ?? t.composedPath()[0];
|
|
80
|
+
s !== this.host && (t.stopPropagation(), this.addCallback(t.callback, s, t.subscribe));
|
|
81
|
+
}, this.onProviderRequest = (s) => {
|
|
82
|
+
if (s.context !== this.context) return;
|
|
83
|
+
if ((s.contextTarget ?? s.composedPath()[0]) === this.host) return;
|
|
84
|
+
const e = /* @__PURE__ */ new Set();
|
|
85
|
+
for (const [s, { consumerHost: i }] of this.subscriptions) e.has(s) || (e.add(s), i.dispatchEvent(new s$1(this.context, i, s, !0)));
|
|
86
|
+
s.stopPropagation();
|
|
87
|
+
}, this.host = s, void 0 !== e.context ? this.context = e.context : this.context = e, this.attachListeners(), this.host.addController?.(this);
|
|
88
|
+
}
|
|
89
|
+
attachListeners() {
|
|
90
|
+
this.host.addEventListener("context-request", this.onContextRequest), this.host.addEventListener("context-provider", this.onProviderRequest);
|
|
91
|
+
}
|
|
92
|
+
hostConnected() {
|
|
93
|
+
this.host.dispatchEvent(new e$1(this.context, this.host));
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region ../../../node_modules/@lit/context/lib/decorators/provide.js
|
|
98
|
+
/**
|
|
99
|
+
* @license
|
|
100
|
+
* Copyright 2017 Google LLC
|
|
101
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
102
|
+
*/ function e({ context: e }) {
|
|
103
|
+
return (n, i$1) => {
|
|
104
|
+
const r = /* @__PURE__ */ new WeakMap();
|
|
105
|
+
if ("object" == typeof i$1) return {
|
|
106
|
+
get() {
|
|
107
|
+
return n.get.call(this);
|
|
108
|
+
},
|
|
109
|
+
set(t) {
|
|
110
|
+
return r.get(this).setValue(t), n.set.call(this, t);
|
|
111
|
+
},
|
|
112
|
+
init(n) {
|
|
113
|
+
return r.set(this, new i(this, {
|
|
114
|
+
context: e,
|
|
115
|
+
initialValue: n
|
|
116
|
+
})), n;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
{
|
|
120
|
+
n.constructor.addInitializer(((n) => {
|
|
121
|
+
r.set(n, new i(n, { context: e }));
|
|
122
|
+
}));
|
|
123
|
+
const o = Object.getOwnPropertyDescriptor(n, i$1);
|
|
124
|
+
let s;
|
|
125
|
+
if (void 0 === o) {
|
|
126
|
+
const t = /* @__PURE__ */ new WeakMap();
|
|
127
|
+
s = {
|
|
128
|
+
get() {
|
|
129
|
+
return t.get(this);
|
|
130
|
+
},
|
|
131
|
+
set(e) {
|
|
132
|
+
r.get(this).setValue(e), t.set(this, e);
|
|
133
|
+
},
|
|
134
|
+
configurable: !0,
|
|
135
|
+
enumerable: !0
|
|
136
|
+
};
|
|
137
|
+
} else {
|
|
138
|
+
const t = o.set;
|
|
139
|
+
s = {
|
|
140
|
+
...o,
|
|
141
|
+
set(e) {
|
|
142
|
+
r.get(this).setValue(e), t?.call(this, e);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
Object.defineProperty(n, i$1, s);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
|
|
153
|
+
function __decorate(decorators, target, key, desc) {
|
|
154
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
155
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
156
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
157
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/list.ts
|
|
161
|
+
var List = class List extends LitElement {
|
|
162
|
+
constructor(..._args) {
|
|
163
|
+
super(..._args);
|
|
164
|
+
this.value = [];
|
|
165
|
+
this.data = [];
|
|
166
|
+
this.disabled = false;
|
|
167
|
+
this.multiple = false;
|
|
168
|
+
this.required = false;
|
|
169
|
+
this.focusedItem = void 0;
|
|
170
|
+
this.typeahead = "";
|
|
171
|
+
}
|
|
172
|
+
static {
|
|
173
|
+
this.styles = unsafeCSS(list_default);
|
|
174
|
+
}
|
|
175
|
+
/** Slotted rows, in DOM order (dividers and headings excluded). */
|
|
176
|
+
get items() {
|
|
177
|
+
return (this.listItemSlot?.assignedElements({ flatten: true }) ?? []).map((slotItem) => slotItem instanceof ListItem ? slotItem : slotItem.firstChild).filter((item) => item instanceof ListItem);
|
|
178
|
+
}
|
|
179
|
+
get enabledItems() {
|
|
180
|
+
return this.items.filter((item) => !item.disabled);
|
|
181
|
+
}
|
|
182
|
+
handleSlotChange() {
|
|
183
|
+
for (const listItem of this.items) if (!listItem.applyContext) listItem.applyContext = true;
|
|
184
|
+
this.syncRows();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Mirrors the selection onto the rows: `data`, the roving tabindex, and `joined-before` / `joined-after` on adjacent
|
|
188
|
+
* selected rows so `c2-list-item` can square the corners between them and a run of selected rows reads as one block.
|
|
189
|
+
*/
|
|
190
|
+
syncRows() {
|
|
191
|
+
const items = this.items;
|
|
192
|
+
if (items.length === 0) return;
|
|
193
|
+
this.data = this.value.map((value) => items.find((item) => item.value === value)?.data);
|
|
194
|
+
items.forEach((item, index) => {
|
|
195
|
+
const selected = this.value.includes(item.value);
|
|
196
|
+
item.toggleAttribute("joined-before", selected && index > 0 && this.value.includes(items[index - 1].value));
|
|
197
|
+
item.toggleAttribute("joined-after", selected && index < items.length - 1 && this.value.includes(items[index + 1].value));
|
|
198
|
+
});
|
|
199
|
+
const enabled = this.enabledItems;
|
|
200
|
+
if (!this.focusedItem || !enabled.includes(this.focusedItem)) this.focusedItem = enabled.find((item) => this.value.includes(item.value)) ?? enabled[0];
|
|
201
|
+
for (const item of items) item.tabIndex = item === this.focusedItem && !this.disabled ? 0 : -1;
|
|
202
|
+
}
|
|
203
|
+
setFocusedItem(item, focus = true) {
|
|
204
|
+
this.focusedItem = item;
|
|
205
|
+
for (const other of this.items) other.tabIndex = other === item ? 0 : -1;
|
|
206
|
+
if (focus) item.focus();
|
|
207
|
+
}
|
|
208
|
+
handleListItemClick(event) {
|
|
209
|
+
const target = event.target;
|
|
210
|
+
if (target instanceof ListItem && !target.disabled) {
|
|
211
|
+
this.setFocusedItem(target, false);
|
|
212
|
+
this.select(target.value);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Applies a user selection of `value` (toggle in `multiple` mode) and fires `selection-change`. */
|
|
216
|
+
select(value) {
|
|
217
|
+
const updatedValues = this.value.includes(value) ? this.value.filter((v) => v !== value) : this.multiple ? [...this.value, value] : [value];
|
|
218
|
+
if (this.required && updatedValues.length === 0) {
|
|
219
|
+
this.dispatchSelectionChangeEvent();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.value = updatedValues;
|
|
223
|
+
this.syncRows();
|
|
224
|
+
this.dispatchSelectionChangeEvent();
|
|
225
|
+
}
|
|
226
|
+
handleKeydown(event) {
|
|
227
|
+
if (this.disabled) return;
|
|
228
|
+
const enabled = this.enabledItems;
|
|
229
|
+
if (enabled.length === 0) return;
|
|
230
|
+
const current = event.target instanceof ListItem ? event.target : this.focusedItem;
|
|
231
|
+
const index = current ? enabled.indexOf(current) : -1;
|
|
232
|
+
switch (event.key) {
|
|
233
|
+
case "ArrowDown":
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
this.setFocusedItem(enabled[Math.min(index + 1, enabled.length - 1)]);
|
|
236
|
+
return;
|
|
237
|
+
case "ArrowUp":
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
this.setFocusedItem(enabled[Math.max(index - 1, 0)]);
|
|
240
|
+
return;
|
|
241
|
+
case "Home":
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
this.setFocusedItem(enabled[0]);
|
|
244
|
+
return;
|
|
245
|
+
case "End":
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
this.setFocusedItem(enabled[enabled.length - 1]);
|
|
248
|
+
return;
|
|
249
|
+
case "Enter":
|
|
250
|
+
case " ":
|
|
251
|
+
if (current) {
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
if (current.href) current.renderRoot.querySelector("a")?.click();
|
|
254
|
+
else this.select(current.value);
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
259
|
+
clearTimeout(this.typeaheadTimer);
|
|
260
|
+
this.typeahead += event.key.toLowerCase();
|
|
261
|
+
this.typeaheadTimer = setTimeout(() => this.typeahead = "", 600);
|
|
262
|
+
const start = this.typeahead.length === 1 ? index + 1 : index;
|
|
263
|
+
const match = [...enabled.slice(Math.max(start, 0)), ...enabled.slice(0, Math.max(start, 0))].find((item) => item.displayText.trim().toLowerCase().startsWith(this.typeahead));
|
|
264
|
+
if (match) this.setFocusedItem(match);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
dispatchSelectionChangeEvent() {
|
|
268
|
+
this.dispatchEvent(new CustomEvent("selection-change", {
|
|
269
|
+
bubbles: true,
|
|
270
|
+
cancelable: true,
|
|
271
|
+
detail: {
|
|
272
|
+
value: this.value,
|
|
273
|
+
data: this.data
|
|
274
|
+
}
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
/** Flush lists (no vertical padding) let the first and last rows take the list's corner radius. */
|
|
278
|
+
syncPaddingClasses() {
|
|
279
|
+
const styleMap = this.computedStyleMap();
|
|
280
|
+
this.classList.toggle("padding-top-0", styleMap.get("padding-top")?.toString() === "0px");
|
|
281
|
+
this.classList.toggle("padding-bottom-0", styleMap.get("padding-bottom")?.toString() === "0px");
|
|
282
|
+
}
|
|
283
|
+
willUpdate(changedProperties) {
|
|
284
|
+
if (changedProperties.has("value") && typeof this.value === "string") this.value = arrayPropertyConverter.fromAttribute(this.value);
|
|
285
|
+
}
|
|
286
|
+
updated(changedProperties) {
|
|
287
|
+
if (changedProperties.has("value")) this.syncRows();
|
|
288
|
+
this.setAttribute("role", "listbox");
|
|
289
|
+
this.setAttribute("aria-multiselectable", String(this.multiple));
|
|
290
|
+
if (this.disabled) this.setAttribute("aria-disabled", "true");
|
|
291
|
+
else this.removeAttribute("aria-disabled");
|
|
292
|
+
this.syncPaddingClasses();
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* private function used for demo project in some edge case need to refresh component
|
|
296
|
+
*/
|
|
297
|
+
_initComponent() {
|
|
298
|
+
this.syncPaddingClasses();
|
|
299
|
+
}
|
|
300
|
+
render() {
|
|
301
|
+
return html`<slot @slotchange=${this.handleSlotChange} @click=${this.handleListItemClick} @keydown=${this.handleKeydown}></slot>`;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
__decorate([e({ context: selectedItemValueContext }), property({
|
|
305
|
+
converter: arrayPropertyConverter,
|
|
306
|
+
reflect: true
|
|
307
|
+
})], List.prototype, "value", void 0);
|
|
308
|
+
__decorate([property({
|
|
309
|
+
type: Boolean,
|
|
310
|
+
reflect: true
|
|
311
|
+
})], List.prototype, "disabled", void 0);
|
|
312
|
+
__decorate([property({ type: Boolean })], List.prototype, "multiple", void 0);
|
|
313
|
+
__decorate([property({ type: Boolean })], List.prototype, "required", void 0);
|
|
314
|
+
__decorate([query("slot")], List.prototype, "listItemSlot", void 0);
|
|
315
|
+
List = __decorate([customElement("c2-list")], List);
|
|
316
|
+
//#endregion
|
|
317
|
+
export { List };
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@c2n/list",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/list.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"default": "./dist/list.js",
|
|
9
|
+
"types": "./types/src/list.d.ts"
|
|
10
|
+
},
|
|
11
|
+
"./custom-elements.json": "./custom-elements.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"types"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"list",
|
|
19
|
+
"web component",
|
|
20
|
+
"lit"
|
|
21
|
+
],
|
|
22
|
+
"author": "code2nguyen@gmail.com",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org",
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/code2nguyen/web-components.git"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"dev": "vite",
|
|
33
|
+
"build": "wireit",
|
|
34
|
+
"type-check": "wireit"
|
|
35
|
+
},
|
|
36
|
+
"wireit": {
|
|
37
|
+
"type-check": {
|
|
38
|
+
"dependencies": [
|
|
39
|
+
"../../core:build",
|
|
40
|
+
"../list-item:build"
|
|
41
|
+
],
|
|
42
|
+
"command": "tsc -p tsconfig.lib.json --composite false"
|
|
43
|
+
},
|
|
44
|
+
"build": {
|
|
45
|
+
"dependencies": [
|
|
46
|
+
"type-check"
|
|
47
|
+
],
|
|
48
|
+
"command": "vite build"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@c2n/core": "0.0.6",
|
|
53
|
+
"@c2n/list-item": "0.0.6",
|
|
54
|
+
"lit": "3.3.3"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@c2n/config": "*"
|
|
58
|
+
},
|
|
59
|
+
"customElements": "custom-elements.json",
|
|
60
|
+
"gitHead": "2921054bc75299da66dad11c1e374246ff88a51e"
|
|
61
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { LitElement, type PropertyValueMap } from 'lit';
|
|
2
|
+
import { ListItem } from '@c2n/list-item';
|
|
3
|
+
export interface SelectionChangeEventDetail {
|
|
4
|
+
value: string[];
|
|
5
|
+
data: unknown[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A vertical list of `c2-list-item` rows with single or multiple selection. The list owns the selection: it reads the
|
|
9
|
+
* `value` of the clicked row, updates its own `value` (a `;`-separated list in the attribute, an array in the property)
|
|
10
|
+
* and pushes the selected state down to every row through context, so rows never need a `selected` attribute of
|
|
11
|
+
* their own when they live inside a list.
|
|
12
|
+
*
|
|
13
|
+
* Keyboard: the list is a `listbox` with a roving `tabindex`. Arrow Up/Down move between enabled rows, Home/End jump to
|
|
14
|
+
* the first/last, Enter and Space select the focused row, and typing letters jumps to the next row starting with them.
|
|
15
|
+
*
|
|
16
|
+
* Besides rows, the default slot accepts `<hr>` dividers and `<h1>`–`<h6>` group headings; both are styled by the
|
|
17
|
+
* `__divider` and `__heading` tokens and skipped by selection and keyboard.
|
|
18
|
+
*
|
|
19
|
+
* @tag c2-list
|
|
20
|
+
*
|
|
21
|
+
* @slot default - The rows: `c2-list-item` elements (or wrappers whose first child is a `c2-list-item`), plus `<hr>` dividers and heading elements.
|
|
22
|
+
*
|
|
23
|
+
* @event {CustomEvent<SelectionChangeEventDetail>} selection-change - Fired after the user changes the selection. `detail.value` is the array of selected values, `detail.data` the matching `data` of each row.
|
|
24
|
+
*
|
|
25
|
+
* @cssproperty {color} [--c2-list--background=#ffffff]
|
|
26
|
+
* @cssproperty {pixel} --c2-list--gap
|
|
27
|
+
*
|
|
28
|
+
* @cssproperty {border-radius} --c2-list--border-top-left-radius
|
|
29
|
+
* @cssproperty {border-radius} --c2-list--border-top-right-radius
|
|
30
|
+
* @cssproperty {border-radius} --c2-list--border-bottom-left-radius
|
|
31
|
+
* @cssproperty {border-radius} --c2-list--border-bottom-right-radius
|
|
32
|
+
*
|
|
33
|
+
* @cssproperty {border} --c2-list--border-top
|
|
34
|
+
* @cssproperty {border} --c2-list--border-bottom
|
|
35
|
+
* @cssproperty {border} --c2-list--border-right
|
|
36
|
+
* @cssproperty {border} --c2-list--border-left
|
|
37
|
+
*
|
|
38
|
+
* @cssproperty {box-shadow} --c2-list--box-shadow
|
|
39
|
+
*
|
|
40
|
+
* @cssproperty {padding} [--c2-list--padding-top=4px]
|
|
41
|
+
* @cssproperty {padding} [--c2-list--padding-bottom=4px]
|
|
42
|
+
* @cssproperty {padding} [--c2-list--padding-right=4px]
|
|
43
|
+
* @cssproperty {padding} [--c2-list--padding-left=4px]
|
|
44
|
+
*
|
|
45
|
+
* @cssproperty {pixel} --c2-list--max-height - Scrolls when the rows are taller than this.
|
|
46
|
+
*
|
|
47
|
+
* @cssproperty {color} [--c2-list__divider--color=#e4e4e7]
|
|
48
|
+
* @cssproperty {margin} [--c2-list__divider--margin=4px 0]
|
|
49
|
+
*
|
|
50
|
+
* @cssproperty {color} [--c2-list__heading--color=#71717a]
|
|
51
|
+
* @cssproperty {font-size} [--c2-list__heading--font-size=11px]
|
|
52
|
+
* @cssproperty {font-weight} [--c2-list__heading--font-weight=600]
|
|
53
|
+
* @cssproperty {letter-spacing} [--c2-list__heading--letter-spacing=0.06em]
|
|
54
|
+
* @cssproperty {text-transform} [--c2-list__heading--text-transform=uppercase]
|
|
55
|
+
* @cssproperty {padding} [--c2-list__heading--padding-top=10px]
|
|
56
|
+
* @cssproperty {padding} [--c2-list__heading--padding-right=10px]
|
|
57
|
+
* @cssproperty {padding} [--c2-list__heading--padding-bottom=4px]
|
|
58
|
+
* @cssproperty {padding} [--c2-list__heading--padding-left=10px]
|
|
59
|
+
*
|
|
60
|
+
* @slotcomponent c2-list-item
|
|
61
|
+
*/
|
|
62
|
+
export declare class List extends LitElement {
|
|
63
|
+
static styles: import("lit").CSSResult;
|
|
64
|
+
/** Selected values. Written as `value="a;b"` in markup, read as `['a', 'b']` from the property. */
|
|
65
|
+
value: string[];
|
|
66
|
+
/** The `data` of every selected row, in `value` order. Not an attribute. */
|
|
67
|
+
data: unknown[];
|
|
68
|
+
/** Dims the whole list and ignores clicks and keyboard. */
|
|
69
|
+
disabled: boolean;
|
|
70
|
+
/** Allow several rows to be selected; clicking a selected row deselects it. */
|
|
71
|
+
multiple: boolean;
|
|
72
|
+
/** Keep at least one row selected: clicking the only selected row does nothing. */
|
|
73
|
+
required: boolean;
|
|
74
|
+
private listItemSlot;
|
|
75
|
+
/** The row that currently holds the roving tabindex. */
|
|
76
|
+
private focusedItem;
|
|
77
|
+
private typeahead;
|
|
78
|
+
private typeaheadTimer;
|
|
79
|
+
/** Slotted rows, in DOM order (dividers and headings excluded). */
|
|
80
|
+
get items(): ListItem[];
|
|
81
|
+
private get enabledItems();
|
|
82
|
+
private handleSlotChange;
|
|
83
|
+
/**
|
|
84
|
+
* Mirrors the selection onto the rows: `data`, the roving tabindex, and `joined-before` / `joined-after` on adjacent
|
|
85
|
+
* selected rows so `c2-list-item` can square the corners between them and a run of selected rows reads as one block.
|
|
86
|
+
*/
|
|
87
|
+
private syncRows;
|
|
88
|
+
private setFocusedItem;
|
|
89
|
+
private handleListItemClick;
|
|
90
|
+
/** Applies a user selection of `value` (toggle in `multiple` mode) and fires `selection-change`. */
|
|
91
|
+
private select;
|
|
92
|
+
private handleKeydown;
|
|
93
|
+
private dispatchSelectionChangeEvent;
|
|
94
|
+
/** Flush lists (no vertical padding) let the first and last rows take the list's corner radius. */
|
|
95
|
+
private syncPaddingClasses;
|
|
96
|
+
protected willUpdate(changedProperties: PropertyValueMap<this>): void;
|
|
97
|
+
protected updated(changedProperties: PropertyValueMap<this>): void;
|
|
98
|
+
/**
|
|
99
|
+
* private function used for demo project in some edge case need to refresh component
|
|
100
|
+
*/
|
|
101
|
+
_initComponent(): void;
|
|
102
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
103
|
+
}
|
|
104
|
+
declare global {
|
|
105
|
+
interface HTMLElementTagNameMap {
|
|
106
|
+
'c2-list': List;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAmB,KAAK,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAIxE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAIzC,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,IAAI,EAAE,OAAO,EAAE,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBACa,IAAK,SAAQ,UAAU;IAClC,OAAgB,MAAM,0BAAoB;IAE1C,mGAAmG;IAMnG,KAAK,EAAE,MAAM,EAAE,CAAK;IAEpB,4EAA4E;IAC5E,IAAI,EAAE,OAAO,EAAE,CAAK;IAEpB,2DAA2D;IACf,QAAQ,EAAE,OAAO,CAAQ;IAErE,+EAA+E;IAClD,QAAQ,EAAE,OAAO,CAAQ;IAEtD,mFAAmF;IACtD,QAAQ,EAAE,OAAO,CAAQ;IAGtD,OAAO,CAAC,YAAY,CAAkB;IAEtC,wDAAwD;IACxD,OAAO,CAAC,WAAW,CAAkC;IAErD,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,cAAc,CAA2C;IAEjE,mEAAmE;IACnE,IAAI,KAAK,IAAI,QAAQ,EAAE,CAItB;IAED,OAAO,KAAK,YAAY,GAEvB;IAED,OAAO,CAAC,gBAAgB;IASxB;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,mBAAmB;IAQ3B,oGAAoG;IACpG,OAAO,CAAC,MAAM;IAad,OAAO,CAAC,aAAa;IA8CrB,OAAO,CAAC,4BAA4B;IAapC,mGAAmG;IACnG,OAAO,CAAC,kBAAkB;cAMP,UAAU,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG,IAAI;cAO3D,OAAO,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG,IAAI;IAW3E;;OAEG;IACH,cAAc;IAIL,MAAM;CAGhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,SAAS,EAAE,IAAI,CAAA;KAChB;CACF"}
|