@threadlabs/looma 0.14.0 → 0.14.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/components/ui-description-list/ui-description-list.html +54 -9
- package/components/ui-table/ui-table.html +131 -0
- package/components/ui-table/ui-table.js +55 -0
- package/dist/index.js +8 -7
- package/package.json +1 -1
- package/styles/ui-description-list.css +52 -7
- package/styles/ui-table.css +134 -0
- package/vanilla/UiDescriptionList.d.ts +3 -1
- package/vanilla/UiDescriptionList.js +6 -2
- package/vanilla/UiTable.d.ts +15 -0
- package/vanilla/UiTable.js +30 -0
- package/vanilla/index.js +1 -0
- package/vue/UiDescriptionList.d.ts +5 -2
- package/vue/UiDescriptionList.vue +51 -7
- package/vue/UiList.d.ts +1 -1
- package/vue/UiTable.d.ts +20 -0
- package/vue/UiTable.js +2 -0
- package/vue/UiTable.vue +172 -0
- package/vue/chunks/UiDescriptionList.js +11 -3
- package/vue/chunks/UiTable.js +40 -0
- package/vue/components.css +157 -9
- package/vue/index.d.ts +1 -0
- package/vue/index.js +2 -1
|
@@ -1,32 +1,77 @@
|
|
|
1
1
|
<template component="ui-description-list">
|
|
2
2
|
<defs>
|
|
3
|
-
<prop name="layout" type="rows | tiles" default="rows"
|
|
4
|
-
>rows sets each term beside its value, one per line, for a short summary;
|
|
5
|
-
|
|
3
|
+
<prop name="layout" type="rows | stacked | grid | tiles" default="rows"
|
|
4
|
+
>rows sets each term beside its value, one per line, for a short summary; stacked sets every term above its value, one pair per line, for a narrow space;
|
|
5
|
+
grid sets the pairs, term above value, in as many columns as fit, for facts to scan across; tiles sets each pair on its own bordered tile in a grid, for
|
|
6
|
+
figures to compare.</prop>
|
|
7
|
+
<prop name="columns" type="'2' | '3' | '4'"
|
|
8
|
+
>The most columns grid and tiles set on a wide list; a narrow list still drops to fewer. Left unset, they fit as many as the width allows.</prop>
|
|
9
|
+
<prop name="density" type="comfortable | compact" default="comfortable"
|
|
10
|
+
>How much room each pair takes. comfortable spaces the pairs apart to read; compact sets them close together, for facts inside other content such as a card
|
|
11
|
+
or a table's detail row.</prop>
|
|
6
12
|
</defs>
|
|
7
13
|
<dl><slot></slot></dl>
|
|
8
14
|
<style>
|
|
9
15
|
/* Named values: a term and what it is, for each item (ui-description-item). */
|
|
10
16
|
/* Rows line every term up in one column and every value in the next, through subgrid. */
|
|
11
17
|
:host {
|
|
18
|
+
--_gap: var(--ui-space-2);
|
|
19
|
+
--_gap-compact: var(--ui-space-1);
|
|
20
|
+
--_column-gap: var(--ui-space-6);
|
|
21
|
+
--_min: 10rem;
|
|
22
|
+
--_track: var(--_min);
|
|
23
|
+
--_tile-padding: var(--ui-space-3);
|
|
12
24
|
display: grid;
|
|
13
25
|
grid-template-columns: minmax(6rem, max-content) minmax(0, 1fr);
|
|
14
|
-
gap: var(--
|
|
26
|
+
gap: var(--_gap) var(--_column-gap);
|
|
15
27
|
min-inline-size: 0;
|
|
16
28
|
margin: 0;
|
|
17
29
|
}
|
|
18
30
|
|
|
19
|
-
/*
|
|
31
|
+
/* Stacked, grid, and tiles set each term above its value, passing the shape to the items through
|
|
32
|
+
private properties, which cross into each item. */
|
|
33
|
+
:host-state([layout="stacked"]),
|
|
34
|
+
:host-state([layout="grid"]),
|
|
20
35
|
:host-state([layout="tiles"]) {
|
|
21
36
|
--_ui-description-display: flex;
|
|
22
37
|
--_ui-description-column: auto;
|
|
38
|
+
--_ui-description-align: stretch;
|
|
39
|
+
--_gap: var(--ui-space-4);
|
|
40
|
+
--_gap-compact: var(--ui-space-2);
|
|
41
|
+
grid-template-columns: minmax(0, 1fr);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* A column is at least --_min wide; with columns set, at least its share of the width, so no more
|
|
45
|
+
than that many fit. */
|
|
46
|
+
:host-state([layout="grid"]),
|
|
47
|
+
:host-state([layout="tiles"]) {
|
|
48
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--_track)), 1fr));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:host-state([layout="tiles"]) {
|
|
23
52
|
--_ui-description-align: flex-start;
|
|
24
53
|
--_ui-description-value-size: var(--ui-font-size-lg);
|
|
25
|
-
--_ui-description-padding: var(--
|
|
54
|
+
--_ui-description-padding: var(--_tile-padding);
|
|
26
55
|
--_ui-description-border-width: 1px;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
gap: var(--
|
|
56
|
+
--_gap: var(--ui-space-3);
|
|
57
|
+
--_gap-compact: var(--ui-space-2);
|
|
58
|
+
--_column-gap: var(--_gap);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:host-state([columns="2"]) {
|
|
62
|
+
--_track: max(var(--_min), (100% - var(--_column-gap)) / 2);
|
|
63
|
+
}
|
|
64
|
+
:host-state([columns="3"]) {
|
|
65
|
+
--_track: max(var(--_min), (100% - 2 * var(--_column-gap)) / 3);
|
|
66
|
+
}
|
|
67
|
+
:host-state([columns="4"]) {
|
|
68
|
+
--_track: max(var(--_min), (100% - 3 * var(--_column-gap)) / 4);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Compact takes each layout's closer spacing. */
|
|
72
|
+
:host-state([density="compact"]) {
|
|
73
|
+
--_gap: var(--_gap-compact);
|
|
74
|
+
--_tile-padding: var(--ui-space-2);
|
|
30
75
|
}
|
|
31
76
|
|
|
32
77
|
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<template component="ui-table" controller="./ui-table.js">
|
|
2
|
+
<defs>
|
|
3
|
+
<prop name="density" type="comfortable | compact" default="comfortable"
|
|
4
|
+
>How tall each row is. Every row is at least one control tall, so a row holding a control lines up with the rows around it. comfortable fits a control of
|
|
5
|
+
the standard height (2.5rem), for tables read or edited at a glance; compact fits a small one (2rem), such as a Checkbox or a Button with size="sm", for
|
|
6
|
+
long tables to scan.</prop>
|
|
7
|
+
<prop name="stickyHeader" type="boolean" default="false"
|
|
8
|
+
>Keeps the header row in view while the rows scroll beneath it. The table scrolls inside its own box, so this needs a bounded height, such as a table that
|
|
9
|
+
fills a fixed-height panel.</prop>
|
|
10
|
+
</defs>
|
|
11
|
+
<div><slot></slot></div>
|
|
12
|
+
<style>
|
|
13
|
+
/* Styles an authored <table> (caption, thead, tbody, th scope) without replacing it: the table is
|
|
14
|
+
meaningful before JavaScript, and a screen reader reads it as a table. The root scrolls a table
|
|
15
|
+
wider than it, or taller than a bounded height; while it does, the controller makes the root a named, focusable region so a
|
|
16
|
+
keyboard can scroll it. A cell's data-ui-align (start, center, or end; start by default) sets its
|
|
17
|
+
content along the row, such as end for a column of figures. */
|
|
18
|
+
:host {
|
|
19
|
+
--_pad-block: var(--ui-space-1);
|
|
20
|
+
--_pad-inline: var(--ui-space-3);
|
|
21
|
+
--_row-size: var(--ui-control-size);
|
|
22
|
+
display: block;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
max-inline-size: 100%;
|
|
25
|
+
min-inline-size: 0;
|
|
26
|
+
min-block-size: 0;
|
|
27
|
+
overflow: auto;
|
|
28
|
+
overscroll-behavior-inline: contain;
|
|
29
|
+
/* The sideways scroll fade every Looma scroller uses (see ui-scroll-area). Only the inline edges
|
|
30
|
+
fade, so a sticky header never fades. */
|
|
31
|
+
--_ui-scroll-fade-direction: to right;
|
|
32
|
+
--_ui-scroll-fade-size: var(--ui-scroll-fade-size, 24px);
|
|
33
|
+
mask-image: linear-gradient(
|
|
34
|
+
var(--_ui-scroll-fade-direction),
|
|
35
|
+
transparent,
|
|
36
|
+
#000 var(--_ui-scroll-fade-start),
|
|
37
|
+
#000 calc(100% - var(--_ui-scroll-fade-end)),
|
|
38
|
+
transparent
|
|
39
|
+
);
|
|
40
|
+
animation: ui-scroll-fade-start linear, ui-scroll-fade-start-hold linear, ui-scroll-fade-end-hold linear, ui-scroll-fade-end linear;
|
|
41
|
+
animation-timeline: scroll(self inline);
|
|
42
|
+
animation-range:
|
|
43
|
+
0 var(--_ui-scroll-fade-size),
|
|
44
|
+
var(--_ui-scroll-fade-size) calc(100% + 1px),
|
|
45
|
+
-1px calc(100% - var(--_ui-scroll-fade-size)),
|
|
46
|
+
calc(100% - var(--_ui-scroll-fade-size)) 100%;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
:host:dir(rtl) {
|
|
50
|
+
--_ui-scroll-fade-direction: to left;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:host:focus-visible {
|
|
54
|
+
outline: 2px solid var(--ui-focus-ring);
|
|
55
|
+
outline-offset: -2px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:host-state([density="compact"]) {
|
|
59
|
+
--_pad-block: var(--ui-space-0-5);
|
|
60
|
+
--_pad-inline: var(--ui-space-2);
|
|
61
|
+
--_row-size: var(--ui-control-size-sm);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Separate borders stay on their cells when a sticky header scrolls; collapsed ones stay behind. */
|
|
65
|
+
:slotted(table) {
|
|
66
|
+
inline-size: 100%;
|
|
67
|
+
border-collapse: separate;
|
|
68
|
+
border-spacing: 0;
|
|
69
|
+
color: var(--ui-text-primary);
|
|
70
|
+
font-size: var(--ui-font-size-sm);
|
|
71
|
+
line-height: var(--ui-line-height);
|
|
72
|
+
font-variant-numeric: tabular-nums;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:slotted(caption) {
|
|
76
|
+
padding-block-end: var(--ui-space-2);
|
|
77
|
+
color: var(--ui-text-secondary);
|
|
78
|
+
font-weight: var(--ui-font-medium);
|
|
79
|
+
text-align: start;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Every row is at least one control tall, so a row holding a checkbox, select, or input keeps the
|
|
83
|
+
rhythm of the rows around it. A cell's height is its minimum. */
|
|
84
|
+
:slotted(th),
|
|
85
|
+
:slotted(td) {
|
|
86
|
+
block-size: var(--_row-size);
|
|
87
|
+
padding: var(--_pad-block) var(--_pad-inline);
|
|
88
|
+
border-block-end: 1px solid var(--ui-border-subtle);
|
|
89
|
+
text-align: start;
|
|
90
|
+
vertical-align: middle;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
:slotted(thead th) {
|
|
94
|
+
block-size: auto;
|
|
95
|
+
padding-block: var(--ui-space-2);
|
|
96
|
+
border-block-end-color: var(--ui-border-default);
|
|
97
|
+
background: var(--ui-surface);
|
|
98
|
+
color: var(--ui-text-muted);
|
|
99
|
+
font-weight: var(--ui-font-medium);
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
vertical-align: bottom;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
:slotted(tbody th) {
|
|
105
|
+
font-weight: var(--ui-font-medium);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
:slotted(tbody tr:last-child > *) {
|
|
109
|
+
border-block-end-width: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
:slotted([data-ui-align="center"]) {
|
|
113
|
+
text-align: center;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
:slotted([data-ui-align="end"]) {
|
|
117
|
+
text-align: end;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
:host-state([stickyHeader]) :slotted(thead th) {
|
|
121
|
+
position: sticky;
|
|
122
|
+
inset-block-start: 0;
|
|
123
|
+
z-index: 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
127
|
+
:host[hidden] {
|
|
128
|
+
display: none;
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
131
|
+
</template>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// While the table is wider than the root, or taller than a root of bounded height, the root scrolls
|
|
2
|
+
// it: it becomes a region a keyboard can focus and scroll with the arrow keys, named by the table's caption unless the author named it. When
|
|
3
|
+
// the table fits, the root is plain again, so it is not an empty stop in the tab order.
|
|
4
|
+
let captions = 0;
|
|
5
|
+
|
|
6
|
+
export default function controller(host) {
|
|
7
|
+
const element = host.element;
|
|
8
|
+
const authored = element.hasAttribute("tabindex") || element.hasAttribute("role");
|
|
9
|
+
if (authored) return undefined;
|
|
10
|
+
|
|
11
|
+
// What this controller set, so a table moved or re-inserted starts plain and is judged again.
|
|
12
|
+
let region = false;
|
|
13
|
+
let labelled = false;
|
|
14
|
+
const plain = () => {
|
|
15
|
+
if (region) {
|
|
16
|
+
element.removeAttribute("role");
|
|
17
|
+
element.removeAttribute("tabindex");
|
|
18
|
+
}
|
|
19
|
+
if (labelled) element.removeAttribute("aria-labelledby");
|
|
20
|
+
region = labelled = false;
|
|
21
|
+
};
|
|
22
|
+
const update = () => {
|
|
23
|
+
const scrolls = element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
|
|
24
|
+
if (!scrolls) return plain();
|
|
25
|
+
element.setAttribute("role", "region");
|
|
26
|
+
element.setAttribute("tabindex", "0");
|
|
27
|
+
region = true;
|
|
28
|
+
const caption = element.querySelector(":scope > table > caption");
|
|
29
|
+
if (caption && !element.hasAttribute("aria-label") && !element.hasAttribute("aria-labelledby")) {
|
|
30
|
+
caption.id ||= `ui-table-caption-${++captions}`;
|
|
31
|
+
element.setAttribute("aria-labelledby", caption.id);
|
|
32
|
+
labelled = true;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const resize = new ResizeObserver(update);
|
|
37
|
+
const observe = () => {
|
|
38
|
+
resize.disconnect();
|
|
39
|
+
resize.observe(element);
|
|
40
|
+
for (const child of element.children) resize.observe(child);
|
|
41
|
+
};
|
|
42
|
+
// A table rendered or replaced later is observed too.
|
|
43
|
+
const children = new MutationObserver(() => {
|
|
44
|
+
observe();
|
|
45
|
+
update();
|
|
46
|
+
});
|
|
47
|
+
children.observe(element, { childList: true });
|
|
48
|
+
observe();
|
|
49
|
+
update();
|
|
50
|
+
return () => {
|
|
51
|
+
resize.disconnect();
|
|
52
|
+
children.disconnect();
|
|
53
|
+
plain();
|
|
54
|
+
};
|
|
55
|
+
}
|