@mosaicajs/components 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/aggregate.d.ts +4 -10
- package/dist/aggregate.js +7 -13
- package/dist/artifact-card.d.ts +21 -7
- package/dist/artifact-card.js +136 -79
- package/dist/base.d.ts +44 -5
- package/dist/base.js +54 -5
- package/dist/cluster-filter.d.ts +29 -25
- package/dist/cluster-filter.js +123 -106
- package/dist/colour-editor.d.ts +15 -4
- package/dist/colour-editor.js +107 -34
- package/dist/colour-picker.d.ts +12 -5
- package/dist/colour-picker.js +119 -32
- package/dist/display.js +77 -9
- package/dist/events.d.ts +21 -7
- package/dist/explorer.d.ts +116 -37
- package/dist/explorer.js +476 -210
- package/dist/field-card.d.ts +304 -48
- package/dist/field-card.js +1364 -244
- package/dist/filter-panel.d.ts +54 -10
- package/dist/filter-panel.js +242 -42
- package/dist/filter.d.ts +22 -4
- package/dist/filter.js +113 -34
- package/dist/hierarchy.d.ts +68 -45
- package/dist/hierarchy.js +320 -243
- package/dist/item-card.d.ts +47 -16
- package/dist/item-card.js +241 -108
- package/dist/key-picker.d.ts +2 -1
- package/dist/key-picker.js +4 -3
- package/dist/layer-picker.d.ts +4 -2
- package/dist/layer-picker.js +16 -7
- package/dist/lineage.d.ts +132 -0
- package/dist/lineage.js +307 -0
- package/dist/map.d.ts +135 -24
- package/dist/map.js +387 -117
- package/dist/mosaica-components.js +1837 -833
- package/dist/mosaica-components.js.sri +1 -1
- package/dist/mosaica-components.json +2 -2
- package/dist/parts.d.ts +4 -4
- package/dist/parts.js +18 -4
- package/dist/paths.d.ts +34 -12
- package/dist/paths.js +84 -46
- package/dist/selection.d.ts +22 -3
- package/dist/selection.js +125 -59
- package/dist/states.d.ts +72 -0
- package/dist/states.js +135 -26
- package/dist/status.d.ts +63 -5
- package/dist/status.js +215 -36
- package/dist/tokens.d.ts +8 -4
- package/dist/tokens.js +144 -14
- package/dist/view-picker.js +5 -0
- package/package.json +4 -3
package/dist/hierarchy.js
CHANGED
|
@@ -34,11 +34,12 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
34
34
|
};
|
|
35
35
|
import { css, html, nothing } from 'lit';
|
|
36
36
|
import { property, state } from 'lit/decorators.js';
|
|
37
|
-
import {
|
|
37
|
+
import { browsableLayers, isFilterLayer, withMember, withoutMember } from '@mosaicajs/client';
|
|
38
38
|
import { refusalOf } from '@mosaicajs/client/internal';
|
|
39
|
-
import { MosaicaElement,
|
|
39
|
+
import { MosaicaElement, called, calledList, emit, focusLost, idString, labelOf } from './base.js';
|
|
40
40
|
import { attachContextRoot, defineOnce } from './define.js';
|
|
41
41
|
import { icon } from './icons.js';
|
|
42
|
+
import { Lineage, focusAdded, spokenCounts } from './lineage.js';
|
|
42
43
|
import { renderState, stateOf } from './states.js';
|
|
43
44
|
import { chrome, tokens } from './tokens.js';
|
|
44
45
|
import './count.js';
|
|
@@ -52,10 +53,19 @@ const masked = (n) => ({ value: Number(n), exact: true });
|
|
|
52
53
|
* hierarchy, filter layers among them. When there are several, a select switches between them.
|
|
53
54
|
*
|
|
54
55
|
* Each row shows how many of the artifact's items the viewer may see. While filters are set, it
|
|
55
|
-
* also shows how many of those items match
|
|
56
|
-
*
|
|
56
|
+
* also shows how many of those items match, and a screen reader is given each count with what it
|
|
57
|
+
* counts. An artifact the viewer may not see is left out, and its children appear under its
|
|
58
|
+
* nearest ancestors the viewer may see, or at the top level where it has none. A row under several
|
|
59
|
+
* served parents names the others, and a parent no page has served yet is asked for by name. When
|
|
60
|
+
* the filters change, the rows are fetched again, a few requests at a time: as many pages of the
|
|
61
|
+
* roots and of each open row as More had loaded, since a page's cursor is good only for the
|
|
62
|
+
* filters it was served under. Focus stays on the control it was on.
|
|
57
63
|
*
|
|
58
|
-
* A row's
|
|
64
|
+
* A row's name is one line, cut with an ellipsis where it is too long, unless
|
|
65
|
+
* `--mosaica-long-text` is `normal`. A row's buttons highlight or filter the map by its artifact.
|
|
66
|
+
* They are shown in place of its counts while the pointer or focus is in the row, and Escape puts
|
|
67
|
+
* them away again; where there is no pointer to hover with, they stand beside the counts. Its Fit
|
|
68
|
+
* button only fires
|
|
59
69
|
* `mosaica-artifactfit`, so you handle the event by passing its id to the map's `fitTo`. The
|
|
60
70
|
* explorer does this for a hierarchy you put in one of its slots. `fitTo` finds only the artifacts
|
|
61
71
|
* the map currently draws, so Fit does nothing for a row whose artifact is not drawn at this zoom.
|
|
@@ -70,31 +80,39 @@ const masked = (n) => ({ value: Number(n), exact: true });
|
|
|
70
80
|
* @fires {CustomEvent<MosaicaEventDetails['mosaica-artifactfit']>} mosaica-artifactfit - A row's Fit
|
|
71
81
|
* button was pressed.
|
|
72
82
|
* @csspart title - The heading.
|
|
73
|
-
* @csspart state - The state line, with `data-state
|
|
83
|
+
* @csspart state - The state line, with `data-state`, inside a `role="status"` region, which also
|
|
84
|
+
* says which rows are loading children and which were refused them.
|
|
74
85
|
* @csspart refusal - The words "View refused" or "Layer unavailable", with `data-code`.
|
|
75
86
|
* @csspart reason - The server's reason for a refusal, under "View refused".
|
|
76
87
|
* @csspart layer - The layer select, shown if there are several layers.
|
|
77
88
|
* @csspart search - The search box.
|
|
78
|
-
* @csspart tree - The tree of rows.
|
|
79
|
-
* @csspart row - One row, with `data-id`,
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
89
|
+
* @csspart tree - The tree of rows, named by the layer.
|
|
90
|
+
* @csspart row - One row, with `data-id`, `data-clause` (`filter`, `highlight` or both, separated
|
|
91
|
+
* by a space) while its artifact is filtered or highlighted, and `data-buttons-away` once Escape has put
|
|
92
|
+
* its buttons away.
|
|
93
|
+
* @csspart expander - A row's expand button, with `aria-expanded` while its children are shown. On
|
|
94
|
+
* a row with no children it has `data-leaf` and is hidden, keeping the names aligned.
|
|
95
|
+
* @csspart name - A row's name. Where the artifact has none it is the artifact's key, with
|
|
96
|
+
* `data-key-name`, and where it has no key either it is the word Unnamed, with `data-unnamed`.
|
|
97
|
+
* Pressing it highlights the artifact, and it has `aria-pressed` while the artifact is
|
|
98
|
+
* highlighted.
|
|
99
|
+
* @csspart counts - A row's counts as they are drawn, hidden from a screen reader, which is given
|
|
100
|
+
* them beside it with what each counts. They are not drawn while the row's buttons are shown
|
|
101
|
+
* over them.
|
|
86
102
|
* @csspart count-matched - The `<mosaica-count>` of a row's items that match the filters, while a
|
|
87
103
|
* filter is set.
|
|
88
104
|
* @csspart count-masked - The `<mosaica-count>` of a row's items the viewer may see.
|
|
89
|
-
* @csspart actions - A row's buttons
|
|
105
|
+
* @csspart actions - A row's buttons, shown over its counts while the pointer or focus is in the
|
|
106
|
+
* row.
|
|
90
107
|
* @csspart highlight - A row's highlight button, with `aria-pressed`.
|
|
91
108
|
* @csspart filter - A row's filter button, with `aria-pressed`.
|
|
92
109
|
* @csspart fit - A row's Fit button, on a layer that draws.
|
|
93
110
|
* @csspart dismiss - A × that removes a row's filter or highlight, one for each, with `data-verb`.
|
|
94
|
-
*
|
|
95
|
-
*
|
|
111
|
+
* The highlight's has a bar under it.
|
|
112
|
+
* @csspart also - The row's other parents, each with `data-key-name` where it is called by its key,
|
|
113
|
+
* or "Children unavailable" with `data-code` if its children were refused.
|
|
96
114
|
* @csspart children - An expanded row's children.
|
|
97
|
-
* @csspart more - The More button, which loads the next page.
|
|
115
|
+
* @csspart more - The More button, which loads the next page and moves focus to its first row.
|
|
98
116
|
*/
|
|
99
117
|
let MosaicaHierarchy = (() => {
|
|
100
118
|
let _classSuper = MosaicaElement;
|
|
@@ -104,30 +122,21 @@ let MosaicaHierarchy = (() => {
|
|
|
104
122
|
let _limit_decorators;
|
|
105
123
|
let _limit_initializers = [];
|
|
106
124
|
let _limit_extraInitializers = [];
|
|
107
|
-
let _roots_decorators;
|
|
108
|
-
let _roots_initializers = [];
|
|
109
|
-
let _roots_extraInitializers = [];
|
|
110
|
-
let _rootsNext_decorators;
|
|
111
|
-
let _rootsNext_initializers = [];
|
|
112
|
-
let _rootsNext_extraInitializers = [];
|
|
113
125
|
let _query_decorators;
|
|
114
126
|
let _query_initializers = [];
|
|
115
127
|
let _query_extraInitializers = [];
|
|
116
128
|
let _searching_decorators;
|
|
117
129
|
let _searching_initializers = [];
|
|
118
130
|
let _searching_extraInitializers = [];
|
|
119
|
-
let
|
|
120
|
-
let
|
|
121
|
-
let
|
|
122
|
-
let _refusal_decorators;
|
|
123
|
-
let _refusal_initializers = [];
|
|
124
|
-
let _refusal_extraInitializers = [];
|
|
131
|
+
let _searchRefusal_decorators;
|
|
132
|
+
let _searchRefusal_initializers = [];
|
|
133
|
+
let _searchRefusal_extraInitializers = [];
|
|
125
134
|
let _open_decorators;
|
|
126
135
|
let _open_initializers = [];
|
|
127
136
|
let _open_extraInitializers = [];
|
|
128
|
-
let
|
|
129
|
-
let
|
|
130
|
-
let
|
|
137
|
+
let _buttonsAway_decorators;
|
|
138
|
+
let _buttonsAway_initializers = [];
|
|
139
|
+
let _buttonsAway_extraInitializers = [];
|
|
131
140
|
let _shown_decorators;
|
|
132
141
|
let _shown_initializers = [];
|
|
133
142
|
let _shown_extraInitializers = [];
|
|
@@ -136,25 +145,19 @@ let MosaicaHierarchy = (() => {
|
|
|
136
145
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
137
146
|
_layer_decorators = [property()];
|
|
138
147
|
_limit_decorators = [property({ type: Number })];
|
|
139
|
-
_roots_decorators = [state()];
|
|
140
|
-
_rootsNext_decorators = [state()];
|
|
141
148
|
_query_decorators = [state()];
|
|
142
149
|
_searching_decorators = [state()];
|
|
143
|
-
|
|
144
|
-
_refusal_decorators = [state()];
|
|
150
|
+
_searchRefusal_decorators = [state()];
|
|
145
151
|
_open_decorators = [state()];
|
|
146
|
-
|
|
152
|
+
_buttonsAway_decorators = [state()];
|
|
147
153
|
_shown_decorators = [state()];
|
|
148
154
|
__esDecorate(this, null, _layer_decorators, { kind: "accessor", name: "layer", static: false, private: false, access: { has: obj => "layer" in obj, get: obj => obj.layer, set: (obj, value) => { obj.layer = value; } }, metadata: _metadata }, _layer_initializers, _layer_extraInitializers);
|
|
149
155
|
__esDecorate(this, null, _limit_decorators, { kind: "accessor", name: "limit", static: false, private: false, access: { has: obj => "limit" in obj, get: obj => obj.limit, set: (obj, value) => { obj.limit = value; } }, metadata: _metadata }, _limit_initializers, _limit_extraInitializers);
|
|
150
|
-
__esDecorate(this, null, _roots_decorators, { kind: "accessor", name: "roots", static: false, private: false, access: { has: obj => "roots" in obj, get: obj => obj.roots, set: (obj, value) => { obj.roots = value; } }, metadata: _metadata }, _roots_initializers, _roots_extraInitializers);
|
|
151
|
-
__esDecorate(this, null, _rootsNext_decorators, { kind: "accessor", name: "rootsNext", static: false, private: false, access: { has: obj => "rootsNext" in obj, get: obj => obj.rootsNext, set: (obj, value) => { obj.rootsNext = value; } }, metadata: _metadata }, _rootsNext_initializers, _rootsNext_extraInitializers);
|
|
152
156
|
__esDecorate(this, null, _query_decorators, { kind: "accessor", name: "query", static: false, private: false, access: { has: obj => "query" in obj, get: obj => obj.query, set: (obj, value) => { obj.query = value; } }, metadata: _metadata }, _query_initializers, _query_extraInitializers);
|
|
153
157
|
__esDecorate(this, null, _searching_decorators, { kind: "accessor", name: "searching", static: false, private: false, access: { has: obj => "searching" in obj, get: obj => obj.searching, set: (obj, value) => { obj.searching = value; } }, metadata: _metadata }, _searching_initializers, _searching_extraInitializers);
|
|
154
|
-
__esDecorate(this, null,
|
|
155
|
-
__esDecorate(this, null, _refusal_decorators, { kind: "accessor", name: "refusal", static: false, private: false, access: { has: obj => "refusal" in obj, get: obj => obj.refusal, set: (obj, value) => { obj.refusal = value; } }, metadata: _metadata }, _refusal_initializers, _refusal_extraInitializers);
|
|
158
|
+
__esDecorate(this, null, _searchRefusal_decorators, { kind: "accessor", name: "searchRefusal", static: false, private: false, access: { has: obj => "searchRefusal" in obj, get: obj => obj.searchRefusal, set: (obj, value) => { obj.searchRefusal = value; } }, metadata: _metadata }, _searchRefusal_initializers, _searchRefusal_extraInitializers);
|
|
156
159
|
__esDecorate(this, null, _open_decorators, { kind: "accessor", name: "open", static: false, private: false, access: { has: obj => "open" in obj, get: obj => obj.open, set: (obj, value) => { obj.open = value; } }, metadata: _metadata }, _open_initializers, _open_extraInitializers);
|
|
157
|
-
__esDecorate(this, null,
|
|
160
|
+
__esDecorate(this, null, _buttonsAway_decorators, { kind: "accessor", name: "buttonsAway", static: false, private: false, access: { has: obj => "buttonsAway" in obj, get: obj => obj.buttonsAway, set: (obj, value) => { obj.buttonsAway = value; } }, metadata: _metadata }, _buttonsAway_initializers, _buttonsAway_extraInitializers);
|
|
158
161
|
__esDecorate(this, null, _shown_decorators, { kind: "accessor", name: "shown", static: false, private: false, access: { has: obj => "shown" in obj, get: obj => obj.shown, set: (obj, value) => { obj.shown = value; } }, metadata: _metadata }, _shown_initializers, _shown_extraInitializers);
|
|
159
162
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
160
163
|
}
|
|
@@ -172,15 +175,15 @@ let MosaicaHierarchy = (() => {
|
|
|
172
175
|
[part='search'] {
|
|
173
176
|
margin-bottom: 8px;
|
|
174
177
|
}
|
|
178
|
+
/* The box reaches 3 px left of the rows, so the expander's target at a row's edge is whole. */
|
|
175
179
|
[part='tree'] {
|
|
176
180
|
list-style: none;
|
|
177
|
-
margin: 0;
|
|
178
|
-
padding: 0;
|
|
181
|
+
margin: 0 0 0 -3px;
|
|
182
|
+
padding: 0 0 0 3px;
|
|
179
183
|
max-height: 320px;
|
|
180
184
|
overflow-y: auto;
|
|
181
185
|
}
|
|
182
186
|
[part='row'] {
|
|
183
|
-
position: relative;
|
|
184
187
|
display: flex;
|
|
185
188
|
align-items: center;
|
|
186
189
|
gap: 6px;
|
|
@@ -199,19 +202,36 @@ let MosaicaHierarchy = (() => {
|
|
|
199
202
|
background: var(--_mosaica-highlight-soft);
|
|
200
203
|
color: var(--_mosaica-highlight);
|
|
201
204
|
}
|
|
205
|
+
/* Drawn 14 px wide. The pointer's target is 24 px square around it, in the row's own room. */
|
|
202
206
|
[part='expander'] {
|
|
207
|
+
position: relative;
|
|
203
208
|
display: inline-flex;
|
|
209
|
+
align-items: center;
|
|
204
210
|
width: 14px;
|
|
211
|
+
height: 24px;
|
|
212
|
+
flex: none;
|
|
205
213
|
color: var(--_mosaica-ink-3);
|
|
206
214
|
}
|
|
215
|
+
[part='expander']::before {
|
|
216
|
+
content: '';
|
|
217
|
+
position: absolute;
|
|
218
|
+
inset: 0 -5px;
|
|
219
|
+
}
|
|
220
|
+
[part='expander']:focus-visible {
|
|
221
|
+
outline-offset: 0;
|
|
222
|
+
}
|
|
207
223
|
[part='expander'][data-leaf] {
|
|
208
224
|
visibility: hidden;
|
|
209
225
|
}
|
|
226
|
+
/* One line, cut with an ellipsis, unless the host asks for long names to wrap. */
|
|
210
227
|
[part='name'] {
|
|
211
228
|
flex: 1;
|
|
229
|
+
min-width: 0;
|
|
230
|
+
min-height: 24px;
|
|
212
231
|
overflow: hidden;
|
|
213
232
|
text-overflow: ellipsis;
|
|
214
|
-
white-space:
|
|
233
|
+
white-space: var(--_mosaica-long-text);
|
|
234
|
+
overflow-wrap: anywhere;
|
|
215
235
|
text-align: left;
|
|
216
236
|
}
|
|
217
237
|
[part='counts'] {
|
|
@@ -221,28 +241,46 @@ let MosaicaHierarchy = (() => {
|
|
|
221
241
|
font-size: 12px;
|
|
222
242
|
color: var(--_mosaica-ink-2);
|
|
223
243
|
}
|
|
224
|
-
/*
|
|
244
|
+
/* The counts and the row's buttons. Where there is no pointer to hover with, side by side. */
|
|
245
|
+
.figures {
|
|
246
|
+
position: relative;
|
|
247
|
+
display: inline-flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
gap: 4px;
|
|
250
|
+
flex: none;
|
|
251
|
+
}
|
|
225
252
|
[part='actions'] {
|
|
226
|
-
position: absolute;
|
|
227
|
-
right: 2px;
|
|
228
|
-
top: 50%;
|
|
229
|
-
transform: translateY(-50%);
|
|
230
253
|
display: inline-flex;
|
|
231
|
-
gap: 2px;
|
|
232
|
-
padding-left: 6px;
|
|
233
|
-
background: var(--_mosaica-surface-2);
|
|
234
|
-
border-radius: var(--_mosaica-radius-control);
|
|
235
|
-
opacity: 0;
|
|
236
|
-
pointer-events: none;
|
|
237
254
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
255
|
+
/* With a pointer, the buttons take no room: they are shown over the counts, which are not
|
|
256
|
+
drawn meanwhile, and over the end of a name beside short counts, while the pointer or
|
|
257
|
+
focus is in the row and Escape has not put them away. They are above the counts, which
|
|
258
|
+
come after them and, not drawn, would still lie over them. */
|
|
259
|
+
@media (hover: hover) {
|
|
260
|
+
[part='actions'] {
|
|
261
|
+
position: absolute;
|
|
262
|
+
right: 0;
|
|
263
|
+
z-index: 1;
|
|
264
|
+
padding-left: 6px;
|
|
265
|
+
background: var(--_mosaica-surface-2);
|
|
266
|
+
border-radius: var(--_mosaica-radius-control);
|
|
267
|
+
opacity: 0;
|
|
268
|
+
pointer-events: none;
|
|
269
|
+
}
|
|
270
|
+
[part='row']:not([data-buttons-away]):is(:hover, :focus-within) [part='actions'] {
|
|
271
|
+
opacity: 1;
|
|
272
|
+
pointer-events: auto;
|
|
273
|
+
}
|
|
274
|
+
[part='row']:not([data-buttons-away]):is(:hover, :focus-within) [part='counts'] {
|
|
275
|
+
opacity: 0;
|
|
276
|
+
pointer-events: none;
|
|
277
|
+
}
|
|
242
278
|
}
|
|
243
279
|
[part='actions'] button {
|
|
244
|
-
display: inline-
|
|
245
|
-
|
|
280
|
+
display: inline-grid;
|
|
281
|
+
place-items: center;
|
|
282
|
+
width: 24px;
|
|
283
|
+
height: 24px;
|
|
246
284
|
color: var(--_mosaica-ink-2);
|
|
247
285
|
border-radius: 4px;
|
|
248
286
|
}
|
|
@@ -250,7 +288,14 @@ let MosaicaHierarchy = (() => {
|
|
|
250
288
|
color: var(--_mosaica-ink);
|
|
251
289
|
background: var(--_mosaica-surface-3);
|
|
252
290
|
}
|
|
291
|
+
[part='actions'] button[aria-pressed='true'] {
|
|
292
|
+
color: var(--_mosaica-ink);
|
|
293
|
+
background: var(--_mosaica-surface-3);
|
|
294
|
+
box-shadow: inset 0 -2px 0 currentColor;
|
|
295
|
+
}
|
|
296
|
+
/* Drawn 20 px square, with a 24 px target around it. */
|
|
253
297
|
[part='dismiss'] {
|
|
298
|
+
position: relative;
|
|
254
299
|
display: inline-grid;
|
|
255
300
|
place-items: center;
|
|
256
301
|
width: 20px;
|
|
@@ -259,8 +304,15 @@ let MosaicaHierarchy = (() => {
|
|
|
259
304
|
border-radius: 4px;
|
|
260
305
|
color: var(--_mosaica-ink-2);
|
|
261
306
|
}
|
|
307
|
+
[part='dismiss']::before {
|
|
308
|
+
content: '';
|
|
309
|
+
position: absolute;
|
|
310
|
+
inset: -2px;
|
|
311
|
+
}
|
|
312
|
+
/* The × that removes a highlight has a bar under it, so it differs from the filter's by more than its colour. */
|
|
262
313
|
[part='dismiss'][data-verb='highlight'] {
|
|
263
314
|
color: var(--_mosaica-highlight);
|
|
315
|
+
box-shadow: inset 0 -2px 0 currentColor;
|
|
264
316
|
}
|
|
265
317
|
[part='dismiss']:hover {
|
|
266
318
|
background: var(--_mosaica-surface-3);
|
|
@@ -270,9 +322,25 @@ let MosaicaHierarchy = (() => {
|
|
|
270
322
|
font-size: 11px;
|
|
271
323
|
color: var(--_mosaica-ink-3);
|
|
272
324
|
}
|
|
325
|
+
[part='also'] [data-key-name] {
|
|
326
|
+
color: inherit;
|
|
327
|
+
}
|
|
328
|
+
/* Drawn as a line of text, with a target 24 px high and at least as wide around it. */
|
|
273
329
|
[part='more'] {
|
|
330
|
+
position: relative;
|
|
274
331
|
margin: 4px 0 4px calc(18px + var(--depth, 0) * 14px);
|
|
275
332
|
}
|
|
333
|
+
[part='more']::before {
|
|
334
|
+
content: '';
|
|
335
|
+
position: absolute;
|
|
336
|
+
inset: -4px 0;
|
|
337
|
+
min-width: 24px;
|
|
338
|
+
}
|
|
339
|
+
[part='children'] {
|
|
340
|
+
list-style: none;
|
|
341
|
+
margin: 0;
|
|
342
|
+
padding: 0;
|
|
343
|
+
}
|
|
276
344
|
[part='refusal'] {
|
|
277
345
|
display: flex;
|
|
278
346
|
align-items: center;
|
|
@@ -289,52 +357,42 @@ let MosaicaHierarchy = (() => {
|
|
|
289
357
|
/** Rows per page, from 1 up to the server's `maxBrowseRows`. */
|
|
290
358
|
get limit() { return this.#limit_accessor_storage; }
|
|
291
359
|
set limit(value) { this.#limit_accessor_storage = value; }
|
|
292
|
-
#
|
|
293
|
-
get roots() { return this.#roots_accessor_storage; }
|
|
294
|
-
set roots(value) { this.#roots_accessor_storage = value; }
|
|
295
|
-
#rootsNext_accessor_storage = (__runInitializers(this, _roots_extraInitializers), __runInitializers(this, _rootsNext_initializers, null));
|
|
296
|
-
get rootsNext() { return this.#rootsNext_accessor_storage; }
|
|
297
|
-
set rootsNext(value) { this.#rootsNext_accessor_storage = value; }
|
|
298
|
-
#query_accessor_storage = (__runInitializers(this, _rootsNext_extraInitializers), __runInitializers(this, _query_initializers, ''));
|
|
360
|
+
#query_accessor_storage = (__runInitializers(this, _limit_extraInitializers), __runInitializers(this, _query_initializers, ''));
|
|
299
361
|
get query() { return this.#query_accessor_storage; }
|
|
300
362
|
set query(value) { this.#query_accessor_storage = value; }
|
|
301
363
|
#searching_accessor_storage = (__runInitializers(this, _query_extraInitializers), __runInitializers(this, _searching_initializers, null));
|
|
302
364
|
get searching() { return this.#searching_accessor_storage; }
|
|
303
365
|
set searching(value) { this.#searching_accessor_storage = value; }
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
set refusal(value) { this.#refusal_accessor_storage = value; }
|
|
310
|
-
#open_accessor_storage = (__runInitializers(this, _refusal_extraInitializers), __runInitializers(this, _open_initializers, new Set()));
|
|
366
|
+
#searchRefusal_accessor_storage = (__runInitializers(this, _searching_extraInitializers), __runInitializers(this, _searchRefusal_initializers, null));
|
|
367
|
+
/** Why the last search was refused. */
|
|
368
|
+
get searchRefusal() { return this.#searchRefusal_accessor_storage; }
|
|
369
|
+
set searchRefusal(value) { this.#searchRefusal_accessor_storage = value; }
|
|
370
|
+
#open_accessor_storage = (__runInitializers(this, _searchRefusal_extraInitializers), __runInitializers(this, _open_initializers, new Set()));
|
|
311
371
|
/** Which paths are open, so a re-render of the tree keeps the walk. */
|
|
312
372
|
get open() { return this.#open_accessor_storage; }
|
|
313
373
|
set open(value) { this.#open_accessor_storage = value; }
|
|
314
|
-
#
|
|
315
|
-
/**
|
|
316
|
-
get
|
|
317
|
-
set
|
|
318
|
-
/** The
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
374
|
+
#buttonsAway_accessor_storage = (__runInitializers(this, _open_extraInitializers), __runInitializers(this, _buttonsAway_initializers, new Set()));
|
|
375
|
+
/** The paths of the rows whose buttons Escape has put away, each until the pointer and focus leave it. */
|
|
376
|
+
get buttonsAway() { return this.#buttonsAway_accessor_storage; }
|
|
377
|
+
set buttonsAway(value) { this.#buttonsAway_accessor_storage = value; }
|
|
378
|
+
/** The browse pages of the current layer: its roots and the children of the rows opened. */
|
|
379
|
+
lineage = (__runInitializers(this, _buttonsAway_extraInitializers), new Lineage({
|
|
380
|
+
store: () => this.resolvedStore,
|
|
381
|
+
layer: () => this.current(),
|
|
382
|
+
limit: () => this.limit,
|
|
383
|
+
isOpen: (path) => this.open.has(path),
|
|
384
|
+
changed: () => this.requestUpdate()
|
|
385
|
+
}));
|
|
325
386
|
searchTimer = null;
|
|
326
|
-
/** Moved by {@link resetServerData}, so a
|
|
387
|
+
/** Moved by {@link resetServerData}, so a search asked for before it is dropped. */
|
|
327
388
|
epoch = 0;
|
|
328
389
|
resetServerData() {
|
|
329
390
|
this.epoch += 1;
|
|
330
|
-
this.
|
|
331
|
-
this.rootsNext = null;
|
|
391
|
+
this.lineage.reset();
|
|
332
392
|
this.searching = null;
|
|
333
|
-
this.
|
|
334
|
-
this.refusal = null;
|
|
393
|
+
this.searchRefusal = null;
|
|
335
394
|
this.open = new Set();
|
|
336
|
-
this.
|
|
337
|
-
this.fetchedUnder = '';
|
|
395
|
+
this.buttonsAway = new Set();
|
|
338
396
|
if (this.searchTimer)
|
|
339
397
|
clearTimeout(this.searchTimer);
|
|
340
398
|
this.searchTimer = null;
|
|
@@ -347,15 +405,6 @@ let MosaicaHierarchy = (() => {
|
|
|
347
405
|
const offered = this.layers();
|
|
348
406
|
return offered.find((l) => l.name === this.layer) ?? offered[0] ?? null;
|
|
349
407
|
}
|
|
350
|
-
/**
|
|
351
|
-
* The question the panel is asking, as a string: the layer and the map's filter. A change
|
|
352
|
-
* refetches the roots and drops the walk, whose counts answer the old question.
|
|
353
|
-
*/
|
|
354
|
-
question() {
|
|
355
|
-
// Hashed from `requestFilters()`, what `store.browse` sends, which includes the drawn region.
|
|
356
|
-
// A `member_of` leaf holds its artifact as a decimal string, so it serialises.
|
|
357
|
-
return `${this.current()?.name ?? ''}|${JSON.stringify(this.resolvedStore?.requestFilters() ?? null)}`;
|
|
358
|
-
}
|
|
359
408
|
#shown_accessor_storage = __runInitializers(this, _shown_initializers, false);
|
|
360
409
|
/**
|
|
361
410
|
* Whether the panel is shown. A hidden panel sends no request, so a panel in a closed drawer
|
|
@@ -369,6 +418,7 @@ let MosaicaHierarchy = (() => {
|
|
|
369
418
|
observer = (__runInitializers(this, _shown_extraInitializers), null);
|
|
370
419
|
connectedCallback() {
|
|
371
420
|
super.connectedCallback();
|
|
421
|
+
document.addEventListener('keydown', this.onEscape);
|
|
372
422
|
if (typeof IntersectionObserver === 'undefined')
|
|
373
423
|
return;
|
|
374
424
|
this.observer ??= new IntersectionObserver((entries) => {
|
|
@@ -380,123 +430,128 @@ let MosaicaHierarchy = (() => {
|
|
|
380
430
|
this.observer.observe(this);
|
|
381
431
|
}
|
|
382
432
|
disconnectedCallback() {
|
|
433
|
+
document.removeEventListener('keydown', this.onEscape);
|
|
383
434
|
this.observer?.disconnect();
|
|
384
435
|
this.observer = null;
|
|
385
436
|
super.disconnectedCallback();
|
|
386
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Escape puts away the buttons shown over a row's counts, wherever focus is, since the pointer
|
|
440
|
+
* alone shows them. Focus on one of them goes to the row's name.
|
|
441
|
+
*/
|
|
442
|
+
onEscape = (e) => {
|
|
443
|
+
if (e.key !== 'Escape')
|
|
444
|
+
return;
|
|
445
|
+
const active = this.shadowRoot?.activeElement ?? null;
|
|
446
|
+
const focused = active?.closest('[part="row"]') ?? null;
|
|
447
|
+
const shown = [focused, this.renderRoot.querySelector('[part="row"]:hover')].filter((row) => row !== null && !this.buttonsAway.has(row.path));
|
|
448
|
+
if (shown.length === 0)
|
|
449
|
+
return;
|
|
450
|
+
this.buttonsAway = new Set([...this.buttonsAway, ...shown.map((row) => row.path)]);
|
|
451
|
+
if (active?.closest('[part="actions"]'))
|
|
452
|
+
focused?.querySelector('[part="name"]')?.focus();
|
|
453
|
+
};
|
|
454
|
+
/** The buttons Escape put away come back once the pointer and focus have both left the row. */
|
|
455
|
+
leftRow(e, path) {
|
|
456
|
+
if (!this.buttonsAway.has(path))
|
|
457
|
+
return;
|
|
458
|
+
const row = e.currentTarget;
|
|
459
|
+
const to = e.type === 'focusout' ? e.relatedTarget : (this.shadowRoot?.activeElement ?? null);
|
|
460
|
+
if (row.contains(to) || (e.type === 'focusout' && row.matches(':hover')))
|
|
461
|
+
return;
|
|
462
|
+
this.shownAgain(path);
|
|
463
|
+
}
|
|
464
|
+
/** Let a row show its buttons again. */
|
|
465
|
+
shownAgain(path) {
|
|
466
|
+
if (this.buttonsAway.has(path))
|
|
467
|
+
this.buttonsAway = new Set([...this.buttonsAway].filter((p) => p !== path));
|
|
468
|
+
}
|
|
387
469
|
/** Whether this element is displayed; see {@link shown}. */
|
|
388
470
|
displayed() {
|
|
389
471
|
return typeof this.checkVisibility === 'function' ? this.checkVisibility() : true;
|
|
390
472
|
}
|
|
391
473
|
/** The roots, where the question they were fetched under is no longer the one being asked. */
|
|
392
474
|
loadRootsIfStale() {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
this.fetchedUnder = q;
|
|
396
|
-
void this.loadRoots();
|
|
397
|
-
}
|
|
475
|
+
if (this.shown)
|
|
476
|
+
this.lineage.loadIfStale();
|
|
398
477
|
}
|
|
399
478
|
onStoreChange() {
|
|
400
479
|
this.loadRootsIfStale();
|
|
401
480
|
super.onStoreChange();
|
|
402
481
|
}
|
|
482
|
+
/** The control focus was on as the last render began. */
|
|
483
|
+
held = null;
|
|
403
484
|
/** Re-asked before every render, so putting the panel away and taking it out again both count. */
|
|
404
485
|
willUpdate(changed) {
|
|
405
486
|
super.willUpdate(changed);
|
|
406
487
|
if (!this.shown && this.displayed())
|
|
407
488
|
this.shown = true;
|
|
489
|
+
this.held = this.heldControl();
|
|
408
490
|
}
|
|
409
491
|
updated() {
|
|
410
|
-
if (this.
|
|
411
|
-
this.
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
/** One page of the current layer, under the map's filter, which the store supplies. */
|
|
416
|
-
page(req) {
|
|
417
|
-
const s = this.resolvedStore;
|
|
418
|
-
const layer = this.current();
|
|
419
|
-
if (!s || !layer)
|
|
420
|
-
return null;
|
|
421
|
-
const ceiling = s.get('meta')?.selection.maxBrowseRows ?? this.limit;
|
|
422
|
-
return s.browse({ ...req, layer: layer.name, limit: Math.max(1, Math.min(this.limit, ceiling)) });
|
|
423
|
-
}
|
|
424
|
-
async loadRoots(cursor) {
|
|
425
|
-
const request = this.page(cursor === undefined ? {} : { cursor });
|
|
426
|
-
if (!request)
|
|
427
|
-
return;
|
|
428
|
-
const epoch = this.epoch;
|
|
429
|
-
this.loading = true;
|
|
430
|
-
this.refusal = null;
|
|
431
|
-
try {
|
|
432
|
-
const page = await request;
|
|
433
|
-
if (epoch !== this.epoch)
|
|
434
|
-
return;
|
|
435
|
-
const nodes = page.artifacts.map((row) => this.node(row, ''));
|
|
436
|
-
this.roots = cursor === undefined ? nodes : [...(this.roots ?? []), ...nodes];
|
|
437
|
-
this.rootsNext = page.next;
|
|
438
|
-
}
|
|
439
|
-
catch (error) {
|
|
440
|
-
if (epoch !== this.epoch)
|
|
441
|
-
return;
|
|
442
|
-
this.refusal = refusalOf(error);
|
|
443
|
-
this.roots = this.roots ?? [];
|
|
444
|
-
}
|
|
445
|
-
finally {
|
|
446
|
-
if (epoch === this.epoch)
|
|
447
|
-
this.loading = false;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
node(row, parentPath) {
|
|
451
|
-
const name = artifactName(row);
|
|
452
|
-
if (name !== null)
|
|
453
|
-
this.names.set(row.mosaicaId, name);
|
|
454
|
-
return { row, path: `${parentPath}/${row.mosaicaId}`, children: null, next: null, loading: false, refusal: null };
|
|
455
|
-
}
|
|
456
|
-
/** What to call an artifact: its name where the walk has met one. */
|
|
457
|
-
nameOf(id) {
|
|
458
|
-
return this.names.get(id) ?? UNNAMED;
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Expand a node: its children, paged. A node keeps what it fetched while {@link question} is
|
|
462
|
-
* unchanged, so reopening it fetches nothing.
|
|
463
|
-
*/
|
|
464
|
-
async expand(node, more = false) {
|
|
465
|
-
const request = this.page({ parent: node.row.mosaicaId, ...(more && node.next ? { cursor: node.next } : {}) });
|
|
466
|
-
if (!request)
|
|
467
|
-
return;
|
|
468
|
-
const epoch = this.epoch;
|
|
469
|
-
node.loading = true;
|
|
470
|
-
node.refusal = null;
|
|
471
|
-
this.revision++;
|
|
472
|
-
try {
|
|
473
|
-
const page = await request;
|
|
474
|
-
if (epoch !== this.epoch)
|
|
475
|
-
return;
|
|
476
|
-
const nodes = page.artifacts.map((row) => this.node(row, node.path));
|
|
477
|
-
node.children = more ? [...(node.children ?? []), ...nodes] : nodes;
|
|
478
|
-
node.next = page.next;
|
|
479
|
-
}
|
|
480
|
-
catch (error) {
|
|
481
|
-
node.refusal = refusalOf(error);
|
|
482
|
-
node.children = node.children ?? [];
|
|
483
|
-
}
|
|
484
|
-
finally {
|
|
485
|
-
node.loading = false;
|
|
486
|
-
this.revision++;
|
|
487
|
-
}
|
|
492
|
+
if (this.held)
|
|
493
|
+
this.keepFocus(this.held);
|
|
494
|
+
this.loadRootsIfStale();
|
|
488
495
|
}
|
|
496
|
+
/** Open a row, fetching its children where it has none yet, or close one that is open or opening. */
|
|
489
497
|
toggle(node) {
|
|
490
498
|
const open = new Set(this.open);
|
|
491
|
-
if (open.has(node.path))
|
|
499
|
+
if (open.has(node.path) && (node.children !== null || node.loading))
|
|
492
500
|
open.delete(node.path);
|
|
493
501
|
else {
|
|
494
502
|
open.add(node.path);
|
|
495
503
|
if (node.children === null && !node.loading)
|
|
496
|
-
void this.expand(node);
|
|
504
|
+
void this.lineage.expand(node);
|
|
497
505
|
}
|
|
498
506
|
this.open = open;
|
|
499
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Load the next page of the roots, or of `parent`'s children, and move focus from the More
|
|
510
|
+
* button, which the page replaces or removes, to the first row it added. Focus that has gone
|
|
511
|
+
* anywhere else meanwhile stays there.
|
|
512
|
+
*/
|
|
513
|
+
async more(button, parent) {
|
|
514
|
+
const added = await (parent ? this.lineage.expand(parent, true) : this.lineage.loadMore());
|
|
515
|
+
const first = added[0];
|
|
516
|
+
if (first)
|
|
517
|
+
await focusAdded(this, button, () => this.rowElements().find((r) => r.path === first.path)?.querySelector('[part="name"]'));
|
|
518
|
+
}
|
|
519
|
+
rowElements() {
|
|
520
|
+
return Array.from(this.renderRoot.querySelectorAll('[part="row"]'));
|
|
521
|
+
}
|
|
522
|
+
/** The control in a row that focus is on, read before a render so {@link keepFocus} can put it back. */
|
|
523
|
+
heldControl() {
|
|
524
|
+
const active = this.shadowRoot?.activeElement;
|
|
525
|
+
const row = active?.closest('[part="row"]');
|
|
526
|
+
const part = active?.getAttribute('part');
|
|
527
|
+
if (!active || !row || !part)
|
|
528
|
+
return null;
|
|
529
|
+
return { path: row.path, at: this.rowElements().indexOf(row), part, verb: active.getAttribute('data-verb') };
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* After a render, put focus back on the control it was on, in the row it was on. Where a ×
|
|
533
|
+
* removed itself, focus goes to the row's other ×, else to the button that makes the clause.
|
|
534
|
+
* Where the row is gone, it goes to the row now in its place, else the last, else the search box.
|
|
535
|
+
*/
|
|
536
|
+
keepFocus(held) {
|
|
537
|
+
const rows = this.rowElements();
|
|
538
|
+
const active = this.shadowRoot?.activeElement ?? null;
|
|
539
|
+
// Focus is still in the tree, or the render removed the control that had it.
|
|
540
|
+
if (!active && !focusLost())
|
|
541
|
+
return;
|
|
542
|
+
const same = rows.find((r) => r.path === held.path);
|
|
543
|
+
const row = same ?? (active ? null : rows[Math.min(held.at, rows.length - 1)]);
|
|
544
|
+
if (!row) {
|
|
545
|
+
if (!active)
|
|
546
|
+
this.renderRoot.querySelector('[part="search"] input')?.focus();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const exact = row.querySelector(`[part="${held.part}"]${held.verb ? `[data-verb="${held.verb}"]` : ''}`);
|
|
550
|
+
const other = held.part === 'dismiss' ? (row.querySelector('[part="dismiss"]') ?? row.querySelector(`[part="actions"] [data-verb="${held.verb}"]`)) : null;
|
|
551
|
+
const to = exact ?? other ?? row.querySelector('[part="name"]');
|
|
552
|
+
if (to !== active)
|
|
553
|
+
to?.focus();
|
|
554
|
+
}
|
|
500
555
|
search(text) {
|
|
501
556
|
this.query = text;
|
|
502
557
|
if (this.searchTimer)
|
|
@@ -507,21 +562,24 @@ let MosaicaHierarchy = (() => {
|
|
|
507
562
|
const q = text.trim();
|
|
508
563
|
if (q.length === 0) {
|
|
509
564
|
this.searching = null;
|
|
565
|
+
this.searchRefusal = null;
|
|
510
566
|
return;
|
|
511
567
|
}
|
|
512
|
-
const request = this.
|
|
568
|
+
const request = this.lineage.search(q);
|
|
513
569
|
if (!request)
|
|
514
570
|
return;
|
|
515
571
|
const epoch = this.epoch;
|
|
516
572
|
void request
|
|
517
|
-
.then((
|
|
518
|
-
if (epoch
|
|
519
|
-
|
|
573
|
+
.then((nodes) => {
|
|
574
|
+
if (epoch !== this.epoch)
|
|
575
|
+
return;
|
|
576
|
+
this.searching = nodes;
|
|
577
|
+
this.searchRefusal = null;
|
|
520
578
|
})
|
|
521
579
|
.catch((error) => {
|
|
522
580
|
if (epoch !== this.epoch)
|
|
523
581
|
return;
|
|
524
|
-
this.
|
|
582
|
+
this.searchRefusal = refusalOf(error);
|
|
525
583
|
this.searching = [];
|
|
526
584
|
});
|
|
527
585
|
}, 250);
|
|
@@ -539,12 +597,16 @@ let MosaicaHierarchy = (() => {
|
|
|
539
597
|
return;
|
|
540
598
|
const held = s.get('filters').members;
|
|
541
599
|
const on = this.clausesOn(id).includes(verb);
|
|
542
|
-
|
|
543
|
-
s.setMembers(on
|
|
544
|
-
? withoutMember(held, layer.name, id, verb)
|
|
545
|
-
: withMember(held, { layer: layer.name, artifact: id, outside: false, verb, ...(label === undefined ? {} : { label }) }));
|
|
600
|
+
s.setMembers(on ? withoutMember(held, layer.name, id, verb) : withMember(held, { layer: layer.name, artifact: id, outside: false, verb, ...labelOf(this.lineage.nameOf(id)) }));
|
|
546
601
|
emit(this, 'mosaica-clausechange', { id: idString(id), layer: layer.name, outside: false, verb, on: !on });
|
|
547
602
|
}
|
|
603
|
+
/** What the rows' own fetches say, for the status region: the rows loading children, and those refused them. */
|
|
604
|
+
rowNotes(nodes) {
|
|
605
|
+
return nodes.flatMap((n) => {
|
|
606
|
+
const name = called(n.row.name, n.row.key).text;
|
|
607
|
+
return [...(n.loading ? [`Loading children of ${name}`] : []), ...(n.refusal ? [`Children of ${name} unavailable`] : []), ...this.rowNotes(n.children ?? [])];
|
|
608
|
+
});
|
|
609
|
+
}
|
|
548
610
|
render() {
|
|
549
611
|
const s = this.resolvedStore;
|
|
550
612
|
const heading = html `<h2 part="title">Hierarchy</h2>`;
|
|
@@ -555,14 +617,26 @@ let MosaicaHierarchy = (() => {
|
|
|
555
617
|
if (offered.length === 0)
|
|
556
618
|
return html `<div class="panel">${heading}<span part="state" data-state="empty">No hierarchical layers</span></div>`;
|
|
557
619
|
const layer = this.current();
|
|
558
|
-
const
|
|
559
|
-
const
|
|
620
|
+
const title = layer.title || layer.name;
|
|
621
|
+
const { roots, loading } = this.lineage;
|
|
622
|
+
const refusal = this.searchRefusal ?? this.lineage.refusal;
|
|
623
|
+
const rows = this.searching ?? roots;
|
|
624
|
+
const state = refusal ? 'refused' : rows === null ? (loading ? 'loading' : 'shown') : rows.length === 0 ? 'empty' : 'shown';
|
|
625
|
+
const said = refusal
|
|
626
|
+
? html `<span part="refusal" data-code=${refusal.code}><span class="dot refuse"></span>Layer unavailable</span>`
|
|
627
|
+
: state === 'loading'
|
|
628
|
+
? html `<span class="sr-only">Loading</span>`
|
|
629
|
+
: state === 'empty'
|
|
630
|
+
? this.searching
|
|
631
|
+
? 'No match'
|
|
632
|
+
: 'Nothing here'
|
|
633
|
+
: nothing;
|
|
634
|
+
const notes = this.rowNotes(rows ?? []);
|
|
560
635
|
return html `<div class="panel">${heading}
|
|
561
|
-
<span part="state" data-state=${this.refusal ? 'refused' : this.loading && rows === null ? 'loading' : 'shown'}></span>
|
|
562
636
|
${offered.length > 1
|
|
563
637
|
? html `<select part="layer" aria-label="Hierarchy layer" .value=${layer.name} @change=${(e) => {
|
|
564
638
|
this.layer = e.target.value;
|
|
565
|
-
this.
|
|
639
|
+
this.lineage.clear();
|
|
566
640
|
this.searching = null;
|
|
567
641
|
this.open = new Set();
|
|
568
642
|
}}>
|
|
@@ -571,66 +645,69 @@ let MosaicaHierarchy = (() => {
|
|
|
571
645
|
: nothing}
|
|
572
646
|
<div part="search" class="input">${icon('search', 14)}<input
|
|
573
647
|
type="search"
|
|
574
|
-
aria-label=${`Search ${
|
|
648
|
+
aria-label=${`Search ${title}`}
|
|
575
649
|
.value=${this.query}
|
|
576
650
|
placeholder="Search names"
|
|
577
651
|
autocomplete="off"
|
|
578
652
|
@input=${(e) => this.search(e.target.value)}
|
|
579
653
|
/></div>
|
|
580
|
-
|
|
581
|
-
${rows === null ||
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
? html `<span part="state" data-state="empty">${this.searching ? 'No match' : 'Nothing here'}</span>`
|
|
585
|
-
: html `<ul part="tree">${rows.map((n) => this.renderNode(n, 0, layer, filtered))}</ul>`}
|
|
586
|
-
${this.searching === null && this.rootsNext
|
|
587
|
-
? html `<button part="more" class="more-link" type="button" @click=${() => void this.loadRoots(this.rootsNext ?? undefined)}>More</button>`
|
|
654
|
+
<div role="status"><span part="state" data-state=${state}>${said}</span>${notes.length > 0 ? html `<span class="sr-only">${notes.join('. ')}</span>` : nothing}</div>
|
|
655
|
+
${rows === null || rows.length === 0 ? nothing : html `<ul part="tree" aria-label=${title}>${rows.map((n) => this.renderNode(n, 0, layer))}</ul>`}
|
|
656
|
+
${this.searching === null && this.lineage.hasMore
|
|
657
|
+
? html `<button part="more" class="more-link" type="button" @click=${(e) => void this.more(e.currentTarget, null)}>More</button>`
|
|
588
658
|
: nothing}
|
|
589
659
|
</div>`;
|
|
590
660
|
}
|
|
591
|
-
renderNode(node, depth, layer
|
|
592
|
-
|
|
593
|
-
const
|
|
594
|
-
const
|
|
661
|
+
renderNode(node, depth, layer) {
|
|
662
|
+
// A row is expanded while its children are drawn, not while they are on their way.
|
|
663
|
+
const open = this.open.has(node.path) && node.children !== null;
|
|
664
|
+
const id = node.row.mosaicaId;
|
|
665
|
+
const clauses = this.clausesOn(id);
|
|
666
|
+
const name = called(node.row.name, node.row.key);
|
|
667
|
+
const text = name.text;
|
|
595
668
|
// A `dag` node is drawn under each served parent; the row names the others.
|
|
596
|
-
const also =
|
|
669
|
+
const also = this.lineage.alsoUnder(node);
|
|
597
670
|
const drawn = !isFilterLayer(layer);
|
|
671
|
+
const pressed = (verb) => (clauses.includes(verb) ? 'true' : 'false');
|
|
598
672
|
return html `<li>
|
|
599
|
-
<div part="row" style=${`--depth:${depth}`} data-id=${idString(
|
|
673
|
+
<div part="row" style=${`--depth:${depth}`} .path=${node.path} data-id=${idString(id)} data-clause=${clauses.length > 0 ? clauses.sort().join(' ') : nothing}
|
|
674
|
+
data-buttons-away=${this.buttonsAway.has(node.path) ? '' : nothing} @pointerleave=${(e) => this.leftRow(e, node.path)} @focusout=${(e) => this.leftRow(e, node.path)}>
|
|
600
675
|
<button part="expander" type="button" data-leaf=${node.row.childCount === 0 ? '' : nothing}
|
|
601
676
|
aria-expanded=${open ? 'true' : 'false'}
|
|
602
|
-
aria-label=${open ? `Collapse ${name
|
|
603
|
-
@click=${() => this.toggle(node)}>${icon(open ? 'chev' : 'chevr', 12)}</button>
|
|
604
|
-
<button part="name" type="button" data-
|
|
605
|
-
title="Highlight"
|
|
606
|
-
@click=${() => this.apply(
|
|
607
|
-
<span
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
<span part="actions">
|
|
615
|
-
<button part="highlight" type="button" data-verb="highlight" aria-pressed=${clauses.includes('highlight') ? 'true' : 'false'}
|
|
616
|
-
title="Highlight this" @click=${() => this.apply(node.row.mosaicaId, 'highlight')}>${icon('highlight', 13)}</button>
|
|
617
|
-
<button part="filter" type="button" data-verb="filter" aria-pressed=${clauses.includes('filter') ? 'true' : 'false'}
|
|
618
|
-
title="Filter to this" @click=${() => this.apply(node.row.mosaicaId, 'filter')}>${icon('filter', 13)}</button>
|
|
619
|
-
${
|
|
677
|
+
aria-label=${open ? `Collapse ${name.from === 'none' ? 'row' : text}` : `Expand ${name.from === 'none' ? 'row' : text}`}
|
|
678
|
+
@click=${() => this.toggle(node)}>${node.loading ? html `<span class="spinner"></span>` : icon(open ? 'chev' : 'chevr', 12)}</button>
|
|
679
|
+
<button part="name" type="button" ?data-key-name=${name.from === 'key'} ?data-unnamed=${name.from === 'none'}
|
|
680
|
+
aria-label=${`Highlight ${text}`} aria-pressed=${pressed('highlight')} title="Highlight"
|
|
681
|
+
@click=${() => this.apply(id, 'highlight')}>${text}</button>
|
|
682
|
+
<span class="figures">
|
|
683
|
+
<span part="actions" @focusin=${() => this.shownAgain(node.path)}>
|
|
684
|
+
<button part="highlight" type="button" data-verb="highlight" aria-label=${`Highlight ${text}`} aria-pressed=${pressed('highlight')}
|
|
685
|
+
title="Highlight this" @click=${() => this.apply(id, 'highlight')}>${icon('highlight', 13)}</button>
|
|
686
|
+
<button part="filter" type="button" data-verb="filter" aria-label=${`Filter to ${text}`} aria-pressed=${pressed('filter')}
|
|
687
|
+
title="Filter to this" @click=${() => this.apply(id, 'filter')}>${icon('filter', 13)}</button>
|
|
688
|
+
${
|
|
620
689
|
// A filter layer draws nothing, so there is nothing to fit to.
|
|
621
690
|
drawn
|
|
622
|
-
? html `<button part="fit" type="button" title="Fit the map to it"
|
|
623
|
-
|
|
691
|
+
? html `<button part="fit" type="button" aria-label=${`Fit the map to ${text}`} title="Fit the map to it"
|
|
692
|
+
@click=${() => emit(this, 'mosaica-artifactfit', { id: idString(id) })}>${icon('fit', 13)}</button>`
|
|
624
693
|
: nothing}
|
|
694
|
+
</span>
|
|
695
|
+
<span part="counts" aria-hidden="true">
|
|
696
|
+
${node.row.matchedCount !== null
|
|
697
|
+
? html `<mosaica-count part="count-matched" .masked=${masked(node.row.matchedCount)}></mosaica-count>/`
|
|
698
|
+
: nothing}<mosaica-count part="count-masked" .masked=${masked(node.row.maskedCount)}></mosaica-count>
|
|
699
|
+
</span><span class="sr-only">${spokenCounts(node.row)}</span>
|
|
625
700
|
</span>
|
|
701
|
+
${clauses.map((verb) => html `<button part="dismiss" type="button" data-verb=${verb} aria-label=${verb === 'highlight' ? `Stop highlighting ${text}` : `Stop filtering to ${text}`}
|
|
702
|
+
title=${verb === 'highlight' ? 'Stop highlighting' : 'Stop filtering'} @click=${() => this.apply(id, verb)}>${icon('close', 12)}</button>`)}
|
|
626
703
|
</div>
|
|
627
|
-
${also.length > 0 ? html `<div part="also" style=${`--depth:${depth}`}>also under ${also
|
|
704
|
+
${also.length > 0 ? html `<div part="also" style=${`--depth:${depth}`}>also under ${calledList(also)}</div>` : nothing}
|
|
628
705
|
${node.refusal ? html `<div part="also" style=${`--depth:${depth}`} data-code=${node.refusal.code}>Children unavailable</div>` : nothing}
|
|
629
|
-
${open
|
|
630
|
-
? html `<ul part="children"
|
|
631
|
-
${node.children.map((c) => this.renderNode(c, depth + 1, layer
|
|
706
|
+
${open
|
|
707
|
+
? html `<ul part="children" aria-label=${`Children of ${text}`}>
|
|
708
|
+
${node.children.map((c) => this.renderNode(c, depth + 1, layer))}
|
|
632
709
|
${node.next
|
|
633
|
-
? html `<li><button part="more" class="more-link" style=${`--depth:${depth + 1}`} type="button" @click=${() => void this.
|
|
710
|
+
? html `<li><button part="more" class="more-link" style=${`--depth:${depth + 1}`} type="button" @click=${(e) => void this.more(e.currentTarget, node)}>More</button></li>`
|
|
634
711
|
: nothing}
|
|
635
712
|
</ul>`
|
|
636
713
|
: nothing}
|