@0001-labs/design-system-1 0.3.0-alpha.17 → 0.3.0-alpha.18
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/DS1/1-root/cjk.css +1 -1
- package/DS1/1-root/context.css +151 -0
- package/DS1/1-root/controls.css +2 -1
- package/DS1/1-root/fonts.css +1 -1
- package/DS1/1-root/one.css +1 -1
- package/DS1/1-root/one.semantic.css +1 -1
- package/DS1/1-root/row.css +65 -10
- package/DS1/1-root/table.css +12 -0
- package/DS1/2-core/ds-image.ts +42 -0
- package/DS1/2-core/styles/ds-image.css +14 -0
- package/DS1/3-unit/ds-calendar-large.ts +5 -1
- package/DS1/3-unit/ds-menu.ts +7 -5
- package/DS1/3-unit/ds-row.ts +6 -2
- package/DS1/3-unit/ds-select.ts +4 -3
- package/DS1/3-unit/ds-token-field.ts +2 -2
- package/DS1/3-unit/styles/ds-calendar-large.css +13 -0
- package/DS1/3-unit/styles/ds-context.css +3 -1
- package/DS1/3-unit/styles/ds-row.css +15 -0
- package/DS1/3-unit/styles/ds-select.css +4 -3
- package/DS1/3-unit/styles/ds-tabs.css +1 -1
- package/DS1/index.ts +2 -0
- package/DS1/react/image.ts +10 -0
- package/dist/2-core/ds-image.d.ts +59 -0
- package/dist/2-core/ds-image.d.ts.map +1 -0
- package/dist/2-core/ds-image.js +42 -0
- package/dist/3-unit/ds-calendar-large.d.ts +5 -0
- package/dist/3-unit/ds-calendar-large.d.ts.map +1 -1
- package/dist/3-unit/ds-calendar-large.js +4 -1
- package/dist/3-unit/ds-context.d.ts +0 -1
- package/dist/3-unit/ds-context.d.ts.map +1 -1
- package/dist/3-unit/ds-menu.d.ts +5 -3
- package/dist/3-unit/ds-menu.d.ts.map +1 -1
- package/dist/3-unit/ds-menu.js +6 -4
- package/dist/3-unit/ds-row.d.ts +6 -0
- package/dist/3-unit/ds-row.d.ts.map +1 -1
- package/dist/3-unit/ds-row.js +5 -2
- package/dist/3-unit/ds-select.d.ts +0 -1
- package/dist/3-unit/ds-select.d.ts.map +1 -1
- package/dist/3-unit/ds-select.js +4 -3
- package/dist/3-unit/ds-token-field.d.ts +1 -1
- package/dist/3-unit/ds-token-field.d.ts.map +1 -1
- package/dist/3-unit/ds-token-field.js +2 -2
- package/dist/design-system-1.bundle.js +86 -23
- package/dist/design-system-1.bundle.js.map +1 -1
- package/dist/design-system-1.bundle.min.js +1213 -1168
- package/dist/design-system-1.bundle.min.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/react/image.d.ts +29 -0
- package/dist/react/image.d.ts.map +1 -0
- package/dist/react/image.js +4 -0
- package/package.json +1 -1
package/DS1/1-root/cjk.css
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
@import "./component-metrics.css";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* `.ds-context` is the `ds-context` element's look on a native menu, for a page
|
|
5
|
+
* without Lit or a menu a library draws: Ezo's gap face. The rows carry the
|
|
6
|
+
* colour and the menu only cuts the outer silhouette, so the page shows
|
|
7
|
+
* through a pixel between rows and two between groups. The ink is the rows'
|
|
8
|
+
* fill and the paper their words, turned over with the theme as the tooltip
|
|
9
|
+
* is. Every row is 19px with the pixel under it, a square's pitch, its words
|
|
10
|
+
* on the sixteen and an icon at its end. A Select's menu is the light face,
|
|
11
|
+
* `.ds-menu`; a Context's is this dark one.
|
|
12
|
+
*
|
|
13
|
+
* ds-context the menu (`role="menu"`); its rows are its buttons,
|
|
14
|
+
* links and menu items, or `ds-context__item`
|
|
15
|
+
* ds-context__item a row; `--danger` (or `data-danger`) in red
|
|
16
|
+
* ds-context__label a heading row, muted
|
|
17
|
+
* ds-context__shortcut a shortcut at the row's end, muted
|
|
18
|
+
* ds-context__separator a group gap (or an `hr`, or `role="separator"`):
|
|
19
|
+
* never a line; first, last or doubled it is quiet
|
|
20
|
+
* [data-ds-refused] a refused change: the row turns red
|
|
21
|
+
*
|
|
22
|
+
* The page places and opens it (a popover at the pointer, say) and owns its
|
|
23
|
+
* keys; the faces of hover, focus, disabled and the wait of a pending menu
|
|
24
|
+
* (`aria-busy="true"`) are here. Tokens as the element's: --ds-context-fill,
|
|
25
|
+
* --ds-context-ink, --ds-context-hover, --ds-context-danger,
|
|
26
|
+
* --ds-context-refused, --ds-context-radius.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
.ds-context {
|
|
30
|
+
--ds-context-row: calc(var(--ds-row-height) - 1px);
|
|
31
|
+
--ds-context-fill: light-dark(var(--ds-black, #1e1e1e), var(--ds-white, #ffffff));
|
|
32
|
+
--ds-context-ink: light-dark(var(--ds-white, #ffffff), var(--ds-black, #1e1e1e));
|
|
33
|
+
--ds-context-hover: color-mix(in srgb, var(--ds-context-fill) 78%, var(--ds-context-ink));
|
|
34
|
+
--ds-context-danger: light-dark(var(--ds-tuned-red), var(--ds-red-for-blood));
|
|
35
|
+
--ds-context-refused: var(--ds-tuned-red);
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
gap: 1px;
|
|
40
|
+
width: var(--ds-menu-width, max-content);
|
|
41
|
+
min-width: calc(4 * var(--ds-unit));
|
|
42
|
+
max-height: var(--ds-menu-max-height, calc(16 * var(--ds-unit)));
|
|
43
|
+
overflow-y: auto;
|
|
44
|
+
margin: 0;
|
|
45
|
+
padding: 0;
|
|
46
|
+
border: 0;
|
|
47
|
+
background: transparent;
|
|
48
|
+
box-shadow: none;
|
|
49
|
+
clip-path: inset(0 round var(--ds-context-radius, var(--ds-radius, 4px)));
|
|
50
|
+
color: var(--ds-context-ink);
|
|
51
|
+
font-family: var(--ds-typeface-regular);
|
|
52
|
+
font-feature-settings: var(--ds-font-features);
|
|
53
|
+
font-size: var(--ds-type-size-default);
|
|
54
|
+
font-weight: var(--ds-type-weight-default);
|
|
55
|
+
line-height: var(--ds-type-lineheight-default);
|
|
56
|
+
letter-spacing: var(--ds-type-letterspacing-default);
|
|
57
|
+
list-style: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ds-context > [role="group"] {
|
|
61
|
+
display: contents;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* A row: whatever the page or library styles its buttons with, one row on the sixteen. */
|
|
65
|
+
.ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"]):not(.ds-context__label, .ds-context__separator, [role="separator"])) {
|
|
66
|
+
box-sizing: border-box !important;
|
|
67
|
+
display: flex !important;
|
|
68
|
+
flex: 0 0 auto !important;
|
|
69
|
+
align-items: flex-end !important;
|
|
70
|
+
justify-content: space-between !important;
|
|
71
|
+
gap: calc(4px * var(--ds-sf));
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: var(--ds-context-row) !important;
|
|
74
|
+
min-height: var(--ds-context-row) !important;
|
|
75
|
+
max-height: var(--ds-context-row) !important;
|
|
76
|
+
margin: 0 !important;
|
|
77
|
+
padding: 0 calc(4px * var(--ds-sf)) !important;
|
|
78
|
+
border: 0 !important;
|
|
79
|
+
border-radius: 0 !important;
|
|
80
|
+
background: var(--ds-context-fill) !important;
|
|
81
|
+
color: var(--ds-context-ink) !important;
|
|
82
|
+
font: inherit;
|
|
83
|
+
line-height: var(--ds-type-lineheight-default) !important;
|
|
84
|
+
letter-spacing: inherit;
|
|
85
|
+
text-align: start;
|
|
86
|
+
text-decoration: none;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
outline: none;
|
|
90
|
+
appearance: none;
|
|
91
|
+
-webkit-tap-highlight-color: transparent;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"])):is(:hover, :focus-visible, [data-highlighted]):not(:disabled, [aria-disabled="true"], [data-disabled]) {
|
|
95
|
+
background: var(--ds-context-hover) !important;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"])):focus-visible {
|
|
99
|
+
box-shadow: inset 0 0 0 2px var(--ds-focus-ring);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"])):is(:disabled, [aria-disabled="true"], [data-disabled]) {
|
|
103
|
+
cursor: default;
|
|
104
|
+
opacity: 0.5;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.ds-context[aria-busy="true"] > * {
|
|
108
|
+
cursor: wait !important;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Danger is red, on either fill. */
|
|
112
|
+
.ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"])):is(.ds-context__item--danger, [data-danger], [data-variant="destructive"]) {
|
|
113
|
+
color: var(--ds-context-danger) !important;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* A refused change: its row turns red a moment. */
|
|
117
|
+
.ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"]))[data-ds-refused] {
|
|
118
|
+
background: var(--ds-context-refused) !important;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Groups are a gap, never a line: the separator takes nothing but the gap after it. */
|
|
122
|
+
.ds-context > :is(hr, .ds-context__separator, [role="separator"]) {
|
|
123
|
+
flex: 0 0 0 !important;
|
|
124
|
+
height: 0 !important;
|
|
125
|
+
margin: 0 !important;
|
|
126
|
+
padding: 0 !important;
|
|
127
|
+
border: 0 !important;
|
|
128
|
+
background: none !important;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* A group left empty leaves no gap. */
|
|
132
|
+
.ds-context > :is(hr, .ds-context__separator, [role="separator"]):is(:first-child, :last-child),
|
|
133
|
+
.ds-context > :is(hr, .ds-context__separator, [role="separator"]) + :is(hr, .ds-context__separator, [role="separator"]) {
|
|
134
|
+
display: none !important;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.ds-context :is(.ds-context__label, [data-ds-menu-label]) {
|
|
138
|
+
box-sizing: border-box;
|
|
139
|
+
height: var(--ds-context-row);
|
|
140
|
+
/* The spare three above the sixteen. */
|
|
141
|
+
padding: calc(var(--ds-context-row) - var(--ds-type-lineheight-default)) calc(4px * var(--ds-sf)) 0;
|
|
142
|
+
background: var(--ds-context-fill);
|
|
143
|
+
color: color-mix(in srgb, var(--ds-context-ink) 60%, var(--ds-context-fill));
|
|
144
|
+
line-height: var(--ds-type-lineheight-default);
|
|
145
|
+
white-space: nowrap;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.ds-context__shortcut {
|
|
149
|
+
margin-left: auto;
|
|
150
|
+
color: color-mix(in srgb, var(--ds-context-ink) 60%, var(--ds-context-fill));
|
|
151
|
+
}
|
package/DS1/1-root/controls.css
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
@import "./figures.css";
|
|
13
13
|
@import "./progress.css";
|
|
14
14
|
@import "./row.css";
|
|
15
|
+
@import "./context.css";
|
|
15
16
|
|
|
16
17
|
/*
|
|
17
18
|
* Native controls in shared dress.
|
|
@@ -454,7 +455,7 @@ textarea.ds-input {
|
|
|
454
455
|
display: flex;
|
|
455
456
|
flex-direction: row;
|
|
456
457
|
align-items: center;
|
|
457
|
-
gap:
|
|
458
|
+
gap: calc(4px * var(--ds-sf));
|
|
458
459
|
min-width: 0;
|
|
459
460
|
height: var(--ds-row-height);
|
|
460
461
|
}
|
package/DS1/1-root/fonts.css
CHANGED
package/DS1/1-root/one.css
CHANGED
package/DS1/1-root/row.css
CHANGED
|
@@ -132,18 +132,50 @@ button.ds-row {
|
|
|
132
132
|
z-index: 2;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
/*
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
/*
|
|
136
|
+
* The pointer, the press, the current destination and the focus ring are one layer under the
|
|
137
|
+
* row's content (Joachim, 19 October 2026). On a one-square row it is a square high and sits two
|
|
138
|
+
* pixels around what the row holds: from two above the lower sixteen to two below the row's foot,
|
|
139
|
+
* and two past the content at each side, its corners the icon tiles' 4px and those two more, so a
|
|
140
|
+
* 16px icon stands in it with two all round. A taller row (a stacked tile, `auto`) is filled whole.
|
|
141
|
+
* Tokens: `--ds-row-fill-radius` (6px), `--ds-row-fill-overhang` (2px).
|
|
142
|
+
*/
|
|
143
|
+
a.ds-row,
|
|
144
|
+
button.ds-row {
|
|
145
|
+
isolation: isolate;
|
|
146
|
+
}
|
|
147
|
+
a.ds-row::after,
|
|
148
|
+
button.ds-row::after {
|
|
149
|
+
content: "";
|
|
150
|
+
position: absolute;
|
|
151
|
+
z-index: -1;
|
|
152
|
+
pointer-events: none;
|
|
153
|
+
}
|
|
154
|
+
a.ds-row::after,
|
|
155
|
+
button.ds-row::after,
|
|
156
|
+
.ds-row__target::after {
|
|
157
|
+
--ds-row-overhang: var(--ds-row-fill-overhang, calc(2px * var(--ds-sf)));
|
|
158
|
+
inset:
|
|
159
|
+
calc(var(--ds-row-height) - var(--ds-control-height) - var(--ds-row-overhang))
|
|
160
|
+
calc(var(--ds-row-padding, calc(4px * var(--ds-sf))) - var(--ds-row-overhang))
|
|
161
|
+
calc(0px - var(--ds-row-overhang));
|
|
162
|
+
border-radius: var(--ds-row-radius, var(--ds-row-fill-radius, calc(6px * var(--ds-sf))));
|
|
163
|
+
}
|
|
164
|
+
:is(a, button).ds-row:is(.ds-row--stacked, .ds-row--auto)::after,
|
|
165
|
+
:is(.ds-row--stacked, .ds-row--auto) .ds-row__target::after {
|
|
166
|
+
inset: 0;
|
|
167
|
+
}
|
|
168
|
+
a.ds-row[href]:hover::after,
|
|
169
|
+
button.ds-row:hover:not(:disabled)::after,
|
|
138
170
|
.ds-row__target:is(a[href], button:not(:disabled)):hover::after {
|
|
139
171
|
background: var(--ds-row-fill-hover, var(--ds-fill));
|
|
140
172
|
}
|
|
141
|
-
a.ds-row[href]:active,
|
|
142
|
-
button.ds-row:active:not(:disabled),
|
|
173
|
+
a.ds-row[href]:active::after,
|
|
174
|
+
button.ds-row:active:not(:disabled)::after,
|
|
143
175
|
.ds-row__target:is(a[href], button:not(:disabled)):active::after {
|
|
144
176
|
background: var(--ds-row-fill-press, var(--ds-fill-hover));
|
|
145
177
|
}
|
|
146
|
-
.ds-row:is(a, button)[aria-current]:not([aria-current="false"]),
|
|
178
|
+
.ds-row:is(a, button)[aria-current]:not([aria-current="false"])::after,
|
|
147
179
|
.ds-row__target[aria-current]:not([aria-current="false"])::after {
|
|
148
180
|
background: var(--ds-row-fill-current, var(--ds-fill-hover));
|
|
149
181
|
}
|
|
@@ -152,8 +184,8 @@ button.ds-row:active:not(:disabled),
|
|
|
152
184
|
--ds-type-weight-default: var(--ds-row-current-weight, 500);
|
|
153
185
|
font-weight: var(--ds-row-current-weight, 500);
|
|
154
186
|
}
|
|
155
|
-
a.ds-row:focus-visible,
|
|
156
|
-
button.ds-row:focus-visible,
|
|
187
|
+
a.ds-row:focus-visible::after,
|
|
188
|
+
button.ds-row:focus-visible::after,
|
|
157
189
|
.ds-row__target:focus-visible::after {
|
|
158
190
|
outline: 2px solid var(--ds-focus-ring);
|
|
159
191
|
outline-offset: -2px;
|
|
@@ -169,8 +201,8 @@ button.ds-row[aria-disabled="true"],
|
|
|
169
201
|
.ds-row__target:is(:disabled, [aria-disabled="true"], a:not([href])) {
|
|
170
202
|
cursor: default;
|
|
171
203
|
}
|
|
172
|
-
a.ds-row[aria-disabled="true"]:hover,
|
|
173
|
-
button.ds-row[aria-disabled="true"]:hover {
|
|
204
|
+
a.ds-row[aria-disabled="true"]:hover::after,
|
|
205
|
+
button.ds-row[aria-disabled="true"]:hover::after {
|
|
174
206
|
background: none;
|
|
175
207
|
}
|
|
176
208
|
|
|
@@ -298,3 +330,26 @@ button.ds-row[aria-disabled="true"]:hover {
|
|
|
298
330
|
background-color: CanvasText;
|
|
299
331
|
}
|
|
300
332
|
}
|
|
333
|
+
|
|
334
|
+
/* A controlled detail view: the caller marks its open row, and clears it on close.
|
|
335
|
+
Navigation's current state and the text weight remain independent. */
|
|
336
|
+
.ds-row[data-open]:not([data-open="false"]) {
|
|
337
|
+
position: relative;
|
|
338
|
+
isolation: isolate;
|
|
339
|
+
}
|
|
340
|
+
.ds-row[data-open]:not([data-open="false"])::after {
|
|
341
|
+
content: "";
|
|
342
|
+
position: absolute;
|
|
343
|
+
z-index: -1;
|
|
344
|
+
pointer-events: none;
|
|
345
|
+
--ds-row-overhang: var(--ds-row-fill-overhang, calc(2px * var(--ds-sf)));
|
|
346
|
+
inset: calc(var(--ds-row-height) - var(--ds-control-height) - var(--ds-row-overhang))
|
|
347
|
+
calc(var(--ds-row-padding, calc(4px * var(--ds-sf))) - var(--ds-row-overhang))
|
|
348
|
+
calc(0px - var(--ds-row-overhang));
|
|
349
|
+
border-radius: var(--ds-row-radius, var(--ds-row-fill-radius, calc(6px * var(--ds-sf))));
|
|
350
|
+
background: var(--ds-row-fill-current, var(--ds-fill-hover));
|
|
351
|
+
}
|
|
352
|
+
.ds-row[data-open]:is(.ds-row--stacked, .ds-row--auto)::after { inset: 0; }
|
|
353
|
+
@media (forced-colors: active) {
|
|
354
|
+
.ds-row[data-open]:not([data-open="false"])::after { outline: 1px solid Highlight; outline-offset: -1px; }
|
|
355
|
+
}
|
package/DS1/1-root/table.css
CHANGED
|
@@ -109,3 +109,15 @@
|
|
|
109
109
|
.ds-table[data-sticky] [aria-selected="true"]:first-child {
|
|
110
110
|
background: var(--ds-accent-color, Highlight);
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
/* Keep the row whose details are open visible, including its sticky first cell.
|
|
114
|
+
Cell selection remains independent and keeps its accent. */
|
|
115
|
+
.ds-table tbody > tr[data-open]:not([data-open="false"]) > :is(th, td):not([aria-selected="true"]) {
|
|
116
|
+
background: var(--ds-row-fill-current, var(--ds-fill-hover));
|
|
117
|
+
}
|
|
118
|
+
@media (forced-colors: active) {
|
|
119
|
+
.ds-table tbody > tr[data-open]:not([data-open="false"]) > :is(th, td):not([aria-selected="true"]) {
|
|
120
|
+
background: Highlight;
|
|
121
|
+
color: HighlightText;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { LitElement, html, nothing, unsafeCSS } from "lit";
|
|
2
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
3
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
4
|
+
import { define } from "../0-face/define";
|
|
5
|
+
import styles from "./styles/ds-image.css?inline";
|
|
6
|
+
|
|
7
|
+
/** Responsive image with native alternative text, optional crop and caption. */
|
|
8
|
+
export class Image extends LitElement {
|
|
9
|
+
static properties = {
|
|
10
|
+
src: { type: String }, alt: { type: String }, caption: { type: String },
|
|
11
|
+
srcset: { type: String }, sizes: { type: String },
|
|
12
|
+
width: { type: Number }, height: { type: Number },
|
|
13
|
+
ratio: { type: String }, fit: { type: String }, position: { type: String },
|
|
14
|
+
loading: { type: String },
|
|
15
|
+
};
|
|
16
|
+
static styles = unsafeCSS(styles);
|
|
17
|
+
declare src: string; declare alt: string; declare caption: string;
|
|
18
|
+
declare srcset: string; declare sizes: string;
|
|
19
|
+
declare width?: number; declare height?: number;
|
|
20
|
+
declare ratio: string; declare fit: string; declare position: string;
|
|
21
|
+
declare loading: string;
|
|
22
|
+
constructor() {
|
|
23
|
+
super(); this.src = ""; this.alt = ""; this.caption = "";
|
|
24
|
+
this.srcset = ""; this.sizes = ""; this.ratio = "";
|
|
25
|
+
this.fit = "cover"; this.position = "center"; this.loading = "lazy";
|
|
26
|
+
}
|
|
27
|
+
render() {
|
|
28
|
+
const dimension = (value?: number) => value && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
29
|
+
return html`<figure>
|
|
30
|
+
<div class="media" part="media" style=${styleMap({ aspectRatio: this.ratio || undefined })}>
|
|
31
|
+
<img part="image" src=${ifDefined(this.src || undefined)} alt=${this.alt}
|
|
32
|
+
srcset=${ifDefined(this.srcset || undefined)} sizes=${ifDefined(this.sizes || undefined)}
|
|
33
|
+
width=${ifDefined(dimension(this.width))} height=${ifDefined(dimension(this.height))}
|
|
34
|
+
loading=${this.loading === "eager" ? "eager" : "lazy"} decoding="async"
|
|
35
|
+
style=${styleMap({ height: this.ratio ? "100%" : "auto", objectFit: this.fit === "contain" ? "contain" : "cover", objectPosition: this.position })} />
|
|
36
|
+
</div>
|
|
37
|
+
${this.caption ? html`<figcaption part="caption">${this.caption}</figcaption>` : nothing}
|
|
38
|
+
</figure>`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
define("ds-image", Image);
|
|
42
|
+
declare global { interface HTMLElementTagNameMap { "ds-image": Image } }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@import "../../1-root/component-metrics.css";
|
|
2
|
+
:host { display: block; width: 100%; max-width: 100%; min-width: 0; }
|
|
3
|
+
:host([hidden]) { display: none !important; }
|
|
4
|
+
figure { margin: 0; }
|
|
5
|
+
.media { overflow: hidden; border-radius: var(--ds-radius); background: var(--ds-image-background, var(--ds-fill)); }
|
|
6
|
+
img { display: block; width: 100%; max-width: 100%; }
|
|
7
|
+
figcaption {
|
|
8
|
+
margin-top: calc(var(--ds-unit) / 2);
|
|
9
|
+
color: var(--ds-text-color-muted);
|
|
10
|
+
font-family: var(--ds-typeface-regular);
|
|
11
|
+
font-feature-settings: var(--ds-font-features);
|
|
12
|
+
font-size: var(--ds-type-size-default);
|
|
13
|
+
line-height: var(--ds-row-height);
|
|
14
|
+
}
|
|
@@ -66,6 +66,7 @@ export class CalendarLarge extends CalendarBase {
|
|
|
66
66
|
start: { type: String, reflect: true },
|
|
67
67
|
months: { type: Number },
|
|
68
68
|
scale: { type: Number },
|
|
69
|
+
activity: { type: Boolean, reflect: true },
|
|
69
70
|
noHeader: { type: Boolean, attribute: "no-header", reflect: true },
|
|
70
71
|
visible: { state: true },
|
|
71
72
|
};
|
|
@@ -75,6 +76,7 @@ export class CalendarLarge extends CalendarBase {
|
|
|
75
76
|
declare start: string | undefined;
|
|
76
77
|
declare months: number | undefined;
|
|
77
78
|
declare scale: number | undefined;
|
|
79
|
+
declare activity: boolean;
|
|
78
80
|
declare noHeader: boolean;
|
|
79
81
|
/** The month at the start of the view, named in the title. */
|
|
80
82
|
declare visible: Day | undefined;
|
|
@@ -88,6 +90,7 @@ export class CalendarLarge extends CalendarBase {
|
|
|
88
90
|
constructor() {
|
|
89
91
|
super();
|
|
90
92
|
this.noHeader = false;
|
|
93
|
+
this.activity = false;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
/** The first of the first month drawn. */
|
|
@@ -120,7 +123,7 @@ export class CalendarLarge extends CalendarBase {
|
|
|
120
123
|
|
|
121
124
|
protected willUpdate(changed: PropertyValues<this>) {
|
|
122
125
|
super.willUpdate(changed);
|
|
123
|
-
const scale = typeof this.scale === "number" && Number.isFinite(this.scale) && this.scale > 0 ? this.scale : 1.5;
|
|
126
|
+
const scale = typeof this.scale === "number" && Number.isFinite(this.scale) && this.scale > 0 ? this.scale : this.activity ? 1 : 1.5;
|
|
124
127
|
this.style.setProperty("--ds-calendar-scale", String(scale));
|
|
125
128
|
}
|
|
126
129
|
|
|
@@ -314,6 +317,7 @@ export class CalendarLarge extends CalendarBase {
|
|
|
314
317
|
data-day=${day}
|
|
315
318
|
tabindex=${day === tab ? 0 : -1}
|
|
316
319
|
aria-label=${this.cellLabel(day, events)}
|
|
320
|
+
title=${this.activity ? this.cellLabel(day, events) : nothing}
|
|
317
321
|
aria-selected=${this.selectable ? String(selected || inside) : nothing}
|
|
318
322
|
aria-current=${isToday ? "date" : nothing}
|
|
319
323
|
aria-disabled=${this.allowed(day) ? nothing : "true"}
|
package/DS1/3-unit/ds-menu.ts
CHANGED
|
@@ -30,7 +30,7 @@ export class Menu extends LitElement {
|
|
|
30
30
|
anchor: { attribute: false },
|
|
31
31
|
placement: { type: String },
|
|
32
32
|
trigger: { attribute: false },
|
|
33
|
-
for: { type: String
|
|
33
|
+
for: { type: String },
|
|
34
34
|
triggerMode: { type: String, attribute: "trigger-mode", reflect: true },
|
|
35
35
|
contextSelector: { type: String, attribute: "context-selector" },
|
|
36
36
|
hover: { type: Boolean, reflect: true },
|
|
@@ -64,7 +64,8 @@ export class Menu extends LitElement {
|
|
|
64
64
|
private search = "";
|
|
65
65
|
private searchAt = 0;
|
|
66
66
|
private gridColumn = 0;
|
|
67
|
-
|
|
67
|
+
/** Move focus into the menu when it opens (a person's opening always does); off to show a menu open without taking the reader's focus, as an example beside the page does. */
|
|
68
|
+
focusOnOpen = true;
|
|
68
69
|
private hoverTimer?: ReturnType<typeof setTimeout>;
|
|
69
70
|
private shown = false;
|
|
70
71
|
private observer?: ResizeObserver;
|
|
@@ -181,7 +182,7 @@ export class Menu extends LitElement {
|
|
|
181
182
|
if (this.dispatchEvent(event)) this.open = open;
|
|
182
183
|
}
|
|
183
184
|
/** Open a context menu from an actual viewport point or the trigger's keyboard position. */
|
|
184
|
-
openAt(point: { x: number; y: number }, invokingControl = this.trigger) {
|
|
185
|
+
openAt(point: { x: number; y: number }, invokingControl = this.trigger, { focus = true }: { focus?: boolean } = {}) {
|
|
185
186
|
if (this.disabled) return;
|
|
186
187
|
if (this.trigger && !this.enabled(this.trigger)) return;
|
|
187
188
|
if (invokingControl && (!invokingControl.isConnected || !this.enabled(invokingControl))) return;
|
|
@@ -191,7 +192,7 @@ export class Menu extends LitElement {
|
|
|
191
192
|
: invokingControl?.contains(focused) && focused instanceof HTMLElement ? focused
|
|
192
193
|
: invokingControl?.querySelector<HTMLElement>(FOCUSABLE) ?? invokingControl;
|
|
193
194
|
if (this.shown) this.opener = this.invokingControl;
|
|
194
|
-
this.focusOnOpen =
|
|
195
|
+
this.focusOnOpen = focus; this.openingEdge = "first";
|
|
195
196
|
this.toggleFromTrigger(true, { trigger: invokingControl, point });
|
|
196
197
|
if (this.open) this.place();
|
|
197
198
|
}
|
|
@@ -401,7 +402,8 @@ export class Menu extends LitElement {
|
|
|
401
402
|
if (this.hasAttribute("data-ds-hug") && !view.getComputedStyle(this).getPropertyValue("--ds-menu-width").trim()) {
|
|
402
403
|
this.style.width = "max-content";
|
|
403
404
|
const natural = this.getBoundingClientRect().width;
|
|
404
|
-
|
|
405
|
+
// Opened at a point it needs only four squares; by an element, at least that element's width.
|
|
406
|
+
const least = (this.triggerMode === "context" || this.triggerMode === "manual") && this.contextPoint ? 4 * unit : anchorWidth;
|
|
405
407
|
this.style.width = `${Math.min(room, Math.max(least, squares(natural, Math.ceil)))}px`;
|
|
406
408
|
}
|
|
407
409
|
if (this.placement === "right" || this.placement === "left" || this.triggerMode === "submenu") {
|
package/DS1/3-unit/ds-row.ts
CHANGED
|
@@ -25,6 +25,7 @@ const CURRENT = new Set(["page", "step", "location", "date", "time", "true"]);
|
|
|
25
25
|
* @attr interactive - Make the row a button.
|
|
26
26
|
* @attr disabled - Keep the row from being pressed.
|
|
27
27
|
* @attr current - The destination being shown: `aria-current="page"`, or the value given (`step`, `location`, …).
|
|
28
|
+
* @attr open - Whether this row’s details are open; controlled by the caller, with aria-expanded on its target.
|
|
28
29
|
* @attr nav - Draw a navigation row: corners of two pixels, as the sidebar's rows.
|
|
29
30
|
* @attr label - The accessible name, when the row's words do not say it.
|
|
30
31
|
* @attr target - Where a link opens (`_blank`, …).
|
|
@@ -45,6 +46,7 @@ export class Row extends LitElement {
|
|
|
45
46
|
interactive: { type: Boolean, reflect: true },
|
|
46
47
|
disabled: { type: Boolean, reflect: true },
|
|
47
48
|
current: { type: String, reflect: true },
|
|
49
|
+
open: { type: Boolean, reflect: true },
|
|
48
50
|
nav: { type: Boolean, reflect: true },
|
|
49
51
|
label: { type: String },
|
|
50
52
|
target: { type: String },
|
|
@@ -60,6 +62,7 @@ export class Row extends LitElement {
|
|
|
60
62
|
declare interactive: boolean;
|
|
61
63
|
declare disabled: boolean;
|
|
62
64
|
declare current: string | null | undefined;
|
|
65
|
+
declare open: boolean | undefined;
|
|
63
66
|
declare nav: boolean;
|
|
64
67
|
declare label: string | undefined;
|
|
65
68
|
declare target: string | undefined;
|
|
@@ -76,6 +79,7 @@ export class Row extends LitElement {
|
|
|
76
79
|
this.interactive = false;
|
|
77
80
|
this.disabled = false;
|
|
78
81
|
this.current = undefined;
|
|
82
|
+
this.open = undefined;
|
|
79
83
|
this.nav = false;
|
|
80
84
|
this.label = undefined;
|
|
81
85
|
this.target = undefined;
|
|
@@ -115,9 +119,9 @@ export class Row extends LitElement {
|
|
|
115
119
|
? this.disabled
|
|
116
120
|
? html`<a class="target" part="target" role="link" aria-disabled="true" aria-label=${ifDefined(label)}>${content}</a>`
|
|
117
121
|
: html`<a class="target" part="target" href=${this.href} target=${ifDefined(this.target || undefined)} rel=${ifDefined(this.rel || undefined)}
|
|
118
|
-
aria-current=${ifDefined(current)} aria-label=${ifDefined(label)} @click=${this.press}>${content}</a>`
|
|
122
|
+
aria-expanded=${ifDefined(this.open === undefined ? undefined : String(this.open))} aria-current=${ifDefined(current)} aria-label=${ifDefined(label)} @click=${this.press}>${content}</a>`
|
|
119
123
|
: html`<button class="target" part="target" type="button" ?disabled=${this.disabled}
|
|
120
|
-
aria-current=${ifDefined(current)} aria-label=${ifDefined(label)} @click=${this.press}>${content}</button>`;
|
|
124
|
+
aria-expanded=${ifDefined(this.open === undefined ? undefined : String(this.open))} aria-current=${ifDefined(current)} aria-label=${ifDefined(label)} @click=${this.press}>${content}</button>`;
|
|
121
125
|
return html`${target}<slot name="end"></slot>`;
|
|
122
126
|
}
|
|
123
127
|
}
|
package/DS1/3-unit/ds-select.ts
CHANGED
|
@@ -83,9 +83,10 @@ export class Select extends Menu {
|
|
|
83
83
|
protected willUpdate(changed: PropertyValues) {
|
|
84
84
|
// An action menu with an opener floats; one without stays in place.
|
|
85
85
|
if (!this.field && !this.hasAttribute("floating") && (this.trigger || this.for)) this.floating = true;
|
|
86
|
-
|
|
86
|
+
// A field's suggestions (a controller steering it) close when the field says so.
|
|
87
|
+
this.closesOnChoice = !this.field && !this.controller;
|
|
87
88
|
// A menu from a button fits its words, on whole squares.
|
|
88
|
-
this.toggleAttribute("data-ds-hug", !this.field && this.triggerMode !== "submenu");
|
|
89
|
+
this.toggleAttribute("data-ds-hug", !this.field && this.triggerMode !== "submenu" && !this.controller);
|
|
89
90
|
super.willUpdate(changed);
|
|
90
91
|
}
|
|
91
92
|
focus(options?: FocusOptions) {
|
|
@@ -203,7 +204,7 @@ export class Select extends Menu {
|
|
|
203
204
|
const select = this.control();
|
|
204
205
|
const label = this.fieldName(select);
|
|
205
206
|
const disabled = this.fieldDisabled ? "disabled" : "";
|
|
206
|
-
return html`<slot @slotchange=${this.observeControl}></slot><span aria-hidden="true" ?hidden=${this.listbox || Boolean(this.swatch)} class="arrow ${disabled}"></span>${this.swatch && !this.listbox ? html`<ds-swatch aria-hidden="true" .value=${this.swatch} class=${disabled}></ds-swatch>` : null}${this.listed ? html`<ds-menu class="list" id=${this.listId} floating role="listbox" trigger-mode="manual" aria-label=${label || nothing} .anchor=${this} ?open=${this.listOpen} @ds-menu-close=${this.onListClose}>${this.listOpen ? this.renderChoices() : nothing}</ds-menu>` : nothing}`;
|
|
207
|
+
return html`<slot @slotchange=${this.observeControl}></slot><span aria-hidden="true" ?hidden=${this.listbox || Boolean(this.swatch)} class="arrow ${disabled}"></span>${this.swatch && !this.listbox ? html`<ds-swatch aria-hidden="true" .value=${this.swatch} class=${disabled}></ds-swatch>` : null}${this.listed ? html`<ds-menu class="list" id=${this.listId} floating data-ds-hug role="listbox" trigger-mode="manual" aria-label=${label || nothing} .anchor=${this} ?open=${this.listOpen} @ds-menu-close=${this.onListClose}>${this.listOpen ? this.renderChoices() : nothing}</ds-menu>` : nothing}`;
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
210
|
|
|
@@ -7,7 +7,7 @@ import styles from "./styles/ds-token-field.css?inline";
|
|
|
7
7
|
let nextId = 0;
|
|
8
8
|
export type TokenCommit = { text: string; reason: "enter" | "separator" | "input" | "blur" };
|
|
9
9
|
/** Native field and ARIA targets stay together in light DOM. This component
|
|
10
|
-
* owns chip layout, editing gestures and suggestion navigation through
|
|
10
|
+
* owns chip layout, editing gestures and suggestion navigation through its Select (`ds-select slot="suggestions"`). */
|
|
11
11
|
export class TokenField extends LitElement {
|
|
12
12
|
static formAssociated = true;
|
|
13
13
|
static properties = {
|
|
@@ -70,7 +70,7 @@ export class TokenField extends LitElement {
|
|
|
70
70
|
menu.floating = true; menu.anchor = this; menu.controller = input;
|
|
71
71
|
menu.trigger = this.shadowRoot?.querySelector<HTMLButtonElement>("button") ?? null;
|
|
72
72
|
menu.open = this.expanded;
|
|
73
|
-
const choices = Array.from(menu.querySelectorAll<HTMLElement>('[role="option"]')).filter(option => option.closest("ds-menu") === menu);
|
|
73
|
+
const choices = Array.from(menu.querySelectorAll<HTMLElement>('[role="option"]')).filter(option => option.closest("ds-menu, ds-select") === menu);
|
|
74
74
|
const options = choices.filter(option => !option.matches(':disabled, [disabled], [aria-disabled="true"]') && !option.closest("[hidden], [inert]"));
|
|
75
75
|
for (const option of options) { if (!option.id) option.id = `${this.idPrefix}-option-${++this.optionId}`; option.tabIndex = -1; }
|
|
76
76
|
if (!options.some(option => option.id === this.activeId)) this.activeId = options[0]?.id ?? "";
|
|
@@ -163,3 +163,16 @@
|
|
|
163
163
|
.rail .weekday:not(:empty) { font-size: 0; }
|
|
164
164
|
.rail .weekday:not(:empty)::after { content: attr(data-narrow); font-size: var(--ds-type-size-default); }
|
|
165
165
|
}
|
|
166
|
+
|
|
167
|
+
/* Activity keeps the day grid, with quiet separated squares and event colours. */
|
|
168
|
+
:host([activity]) .frame { border: 0; border-radius: 0; }
|
|
169
|
+
:host([activity]) .cell { position: relative; border: 0; background: none; }
|
|
170
|
+
:host([activity]) .cell::before {
|
|
171
|
+
content: "";
|
|
172
|
+
position: absolute;
|
|
173
|
+
inset: calc(2px * var(--ds-sf, 1));
|
|
174
|
+
border-radius: calc(2px * var(--ds-sf, 1));
|
|
175
|
+
background: var(--ds-calendar-cell-fill, var(--ds-fill));
|
|
176
|
+
}
|
|
177
|
+
:host([activity]) .date { display: none; }
|
|
178
|
+
:host([activity]) .cell[data-selected]::before { outline: 1px solid var(--ds-calendar-ink); }
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
::slotted(ds-button) {
|
|
38
38
|
--ds-button-height: var(--ds-menu-row);
|
|
39
39
|
--ds-button-padding: 0 calc(4px * var(--ds-sf));
|
|
40
|
-
|
|
40
|
+
/* The words at the row's start, an icon at its end. */
|
|
41
|
+
--ds-button-align: space-between;
|
|
41
42
|
--ds-button-background: var(--ds-context-fill);
|
|
42
43
|
--ds-button-color: var(--ds-context-ink);
|
|
43
44
|
--ds-radius: 0px;
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
box-sizing: border-box !important;
|
|
51
52
|
display: flex !important;
|
|
52
53
|
align-items: flex-end !important;
|
|
54
|
+
justify-content: space-between !important;
|
|
53
55
|
gap: calc(4px * var(--ds-sf));
|
|
54
56
|
width: 100%;
|
|
55
57
|
padding: 0 calc(4px * var(--ds-sf)) !important;
|
|
@@ -94,6 +94,19 @@
|
|
|
94
94
|
inset: 0;
|
|
95
95
|
border-radius: var(--ds-row-radius, 0);
|
|
96
96
|
}
|
|
97
|
+
/* The state layer two pixels around what a one-square row holds (1-root/row.css says why). */
|
|
98
|
+
.target::after {
|
|
99
|
+
--ds-row-overhang: var(--ds-row-fill-overhang, calc(2px * var(--ds-sf)));
|
|
100
|
+
inset:
|
|
101
|
+
calc(var(--ds-row-height) - var(--ds-control-height) - var(--ds-row-overhang))
|
|
102
|
+
calc(var(--ds-row-padding, calc(4px * var(--ds-sf))) - var(--ds-row-overhang))
|
|
103
|
+
calc(0px - var(--ds-row-overhang));
|
|
104
|
+
border-radius: var(--ds-row-radius, var(--ds-row-fill-radius, calc(6px * var(--ds-sf))));
|
|
105
|
+
}
|
|
106
|
+
:host([stacked]) .target::after,
|
|
107
|
+
:host([auto]) .target::after {
|
|
108
|
+
inset: 0;
|
|
109
|
+
}
|
|
97
110
|
.target::before { z-index: 1; }
|
|
98
111
|
.target::after { z-index: -1; }
|
|
99
112
|
::slotted([slot="end"]) { position: relative; z-index: 2; }
|
|
@@ -105,6 +118,7 @@
|
|
|
105
118
|
font-weight: var(--ds-row-current-weight, 500);
|
|
106
119
|
}
|
|
107
120
|
.target[aria-current]::after { background: var(--ds-row-fill-current, var(--ds-fill-hover)); }
|
|
121
|
+
:host([open]) .target::after { background: var(--ds-row-fill-current, var(--ds-fill-hover)); }
|
|
108
122
|
.target:focus-visible::after { outline: 2px solid var(--ds-focus-ring); outline-offset: -2px; }
|
|
109
123
|
|
|
110
124
|
:host([disabled]) .target { cursor: default; opacity: 0.5; }
|
|
@@ -114,6 +128,7 @@
|
|
|
114
128
|
:host([nav]) { --ds-row-radius: calc(2px * var(--ds-sf)); }
|
|
115
129
|
|
|
116
130
|
@media (forced-colors: active) {
|
|
131
|
+
:host([open]) .target::after,
|
|
117
132
|
.target[aria-current]::after { outline: 1px solid Highlight; outline-offset: -1px; }
|
|
118
133
|
.target:focus-visible::after { outline-color: Highlight; }
|
|
119
134
|
}
|
|
@@ -104,14 +104,15 @@ ds-swatch.disabled { opacity: .5; }
|
|
|
104
104
|
* The field's list, in place of the platform's picker (`data-listed`). The
|
|
105
105
|
* select takes no pointer, so a press on the field opens the list and no
|
|
106
106
|
* platform picker; the keyboard still reaches the select, and its opening keys
|
|
107
|
-
* open the list. The list is the menu engine's, as wide as
|
|
108
|
-
*
|
|
107
|
+
* open the list. The list is the menu engine's, as wide as its choices in whole
|
|
108
|
+
* squares and at least the field (`--ds-select-list-width` sets one), each choice one
|
|
109
109
|
* square on the sixteen: an icon on the first square, the name, muted details,
|
|
110
110
|
* a shortcut at the end and the check on the last square for the chosen one.
|
|
111
111
|
*/
|
|
112
112
|
:host([data-listed]) { cursor: pointer; }
|
|
113
113
|
:host([data-listed]) ::slotted(select) { pointer-events: none !important; }
|
|
114
|
-
|
|
114
|
+
/* The list fits its choices on whole squares, at least as wide as the field. */
|
|
115
|
+
.list { --ds-menu-width: var(--ds-select-list-width); }
|
|
115
116
|
.choice,
|
|
116
117
|
.group-label {
|
|
117
118
|
box-sizing: border-box;
|