@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/cluster-filter.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
2
2
|
import { type BrowseRow, type Refusal } from '@mosaicajs/client';
|
|
3
3
|
import { MosaicaElement } from './base.js';
|
|
4
|
-
/** One cluster the list offers
|
|
4
|
+
/** One cluster the list offers. */
|
|
5
5
|
type Offer = {
|
|
6
6
|
row: BrowseRow;
|
|
7
|
-
path: string[] | null;
|
|
8
|
-
beyond: boolean;
|
|
9
7
|
};
|
|
10
8
|
/**
|
|
11
9
|
* Use `<mosaica-cluster-filter>` when you want to search a layer's clusters without the field card
|
|
12
10
|
* around it. Set `layer` to a layer `meta.layers` lists.
|
|
13
11
|
*
|
|
14
12
|
* With the box empty, the list offers the layer's top-level clusters, largest first. Typing
|
|
15
|
-
* searches the cluster names at every level.
|
|
16
|
-
* chooses
|
|
13
|
+
* searches the cluster names at every level. The arrow keys move between the rows, as do Home and
|
|
14
|
+
* End once one is reached. Enter chooses the row they reached, and Escape closes the list. A line
|
|
15
|
+
* under the box says how many clusters are listed, or why none is, and that they are loading once
|
|
16
|
+
* that has taken a quarter of a second.
|
|
17
17
|
*
|
|
18
18
|
* Each row counts the items the filters match in that cluster without applying this layer's own
|
|
19
19
|
* filter, so a cluster the viewer has filtered out still shows its count. Choosing a row filters
|
|
@@ -26,12 +26,15 @@ type Offer = {
|
|
|
26
26
|
* row added or removed a `member_of` clause.
|
|
27
27
|
* @csspart entry - The search box.
|
|
28
28
|
* @csspart values - The list, while it is open.
|
|
29
|
-
* @csspart option - One cluster in the list, with `data-id
|
|
30
|
-
* on.
|
|
31
|
-
* @csspart name - A row's name
|
|
32
|
-
*
|
|
29
|
+
* @csspart option - One cluster in the list, with `data-id`, `aria-selected` while its clause is
|
|
30
|
+
* on, and `data-active` on the one the arrow keys reached.
|
|
31
|
+
* @csspart name - A row's name. Where the cluster has none it is the cluster's key, with
|
|
32
|
+
* `data-key-name`, and where it has no key either it is the word Unnamed, with `data-unnamed`.
|
|
33
|
+
* @csspart path - A row's path of its parents' names, each step naming every served parent of the
|
|
34
|
+
* step below it, by its key where it has no name.
|
|
33
35
|
* @csspart value-count - A row's exact count.
|
|
34
|
-
* @csspart more - The note under the box when nothing
|
|
36
|
+
* @csspart more - The note under the box when there is nothing to list, or when more match than
|
|
37
|
+
* are listed.
|
|
35
38
|
* @csspart refusal - The words "Clusters unavailable", with `data-code`, if the list was refused.
|
|
36
39
|
*/
|
|
37
40
|
export declare class MosaicaClusterFilter extends MosaicaElement {
|
|
@@ -55,7 +58,7 @@ export declare class MosaicaClusterFilter extends MosaicaElement {
|
|
|
55
58
|
accessor search: string;
|
|
56
59
|
/** Whether the list is open. @internal */
|
|
57
60
|
accessor listOpen: boolean;
|
|
58
|
-
/** The row the arrow keys reached, by `mosaicaId`; `null`
|
|
61
|
+
/** The row the arrow keys reached, by `mosaicaId`; `null` before they reach one. @internal */
|
|
59
62
|
accessor activeId: bigint | null;
|
|
60
63
|
/** The top-level clusters, once fetched. @internal */
|
|
61
64
|
accessor tops: Offer[] | null;
|
|
@@ -68,11 +71,11 @@ export declare class MosaicaClusterFilter extends MosaicaElement {
|
|
|
68
71
|
/** @internal */
|
|
69
72
|
accessor refusal: Refusal | null;
|
|
70
73
|
private readonly counts;
|
|
74
|
+
/** Whether the open list's clusters have been awaited long enough to say so. */
|
|
75
|
+
private readonly awaited;
|
|
71
76
|
private readonly floating;
|
|
72
|
-
/**
|
|
73
|
-
private readonly
|
|
74
|
-
/** Each artifact's served parents as a children-form page gave them. */
|
|
75
|
-
private readonly parentsOf;
|
|
77
|
+
/** The names of the offered clusters' parents, walked {@link PATH_DEPTH} steps up. */
|
|
78
|
+
private readonly paths;
|
|
76
79
|
/** The layer and view the top-level clusters were fetched for. */
|
|
77
80
|
private fetchedFor;
|
|
78
81
|
private searchTimer;
|
|
@@ -81,21 +84,21 @@ export declare class MosaicaClusterFilter extends MosaicaElement {
|
|
|
81
84
|
protected resetServerData(): void;
|
|
82
85
|
disconnectedCallback(): void;
|
|
83
86
|
private declared;
|
|
84
|
-
/** The
|
|
85
|
-
private
|
|
87
|
+
/** The first declared level of a levelled layer, whose clusters are the top level; `undefined` on any other. */
|
|
88
|
+
private firstLevel;
|
|
86
89
|
/** One browse page of the layer. Counts come from the aggregate, so the page is asked unfiltered. */
|
|
87
90
|
private browse;
|
|
91
|
+
/** Offer the rows of a page, and walk the parents of each. */
|
|
88
92
|
private offer;
|
|
93
|
+
/** An offer's path of parents, once walked; empty for a cluster with none. */
|
|
94
|
+
private pathOf;
|
|
89
95
|
/** Fetch the top-level clusters, once per layer and view. */
|
|
90
96
|
private fetchTops;
|
|
91
|
-
/** Search the layer's names for `q`, at every level of a levelled layer, and walk each match's path. */
|
|
92
|
-
private searchFor;
|
|
93
97
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* only parents already met are named.
|
|
98
|
+
* Search the layer's names for `q`, and walk each match's path. One request with no level
|
|
99
|
+
* searches every level of a levelled layer.
|
|
97
100
|
*/
|
|
98
|
-
private
|
|
101
|
+
private searchFor;
|
|
99
102
|
/** The clauses this field holds: the layer's, in its position. */
|
|
100
103
|
private held;
|
|
101
104
|
private toggle;
|
|
@@ -109,8 +112,9 @@ export declare class MosaicaClusterFilter extends MosaicaElement {
|
|
|
109
112
|
/** The rows and width {@link fitPaths} last measured. */
|
|
110
113
|
private pathsMeasured;
|
|
111
114
|
/**
|
|
112
|
-
*
|
|
113
|
-
* the left. Measured with the line's own font, after the list is
|
|
115
|
+
* Where a path is kept to one line, name as many of each listed path's nearest parents as fit
|
|
116
|
+
* it, dropping whole names from the left. Measured with the line's own font, after the list is
|
|
117
|
+
* placed. A path that wraps names them all.
|
|
114
118
|
*/
|
|
115
119
|
private fitPaths;
|
|
116
120
|
protected updated(changed: PropertyValues<this>): void;
|
package/dist/cluster-filter.js
CHANGED
|
@@ -38,10 +38,12 @@ import { repeat } from 'lit/directives/repeat.js';
|
|
|
38
38
|
import { artifactName, withMember, withoutMember } from '@mosaicajs/client';
|
|
39
39
|
import { refusalOf } from '@mosaicajs/client/internal';
|
|
40
40
|
import { HeldAggregate, artifactGroupings, countsByKey } from './aggregate.js';
|
|
41
|
-
import { MosaicaElement,
|
|
41
|
+
import { MosaicaElement, called, emit, idString, labelOf } from './base.js';
|
|
42
42
|
import { FloatingList } from './float.js';
|
|
43
43
|
import { attachContextRoot, defineOnce } from './define.js';
|
|
44
44
|
import { icon } from './icons.js';
|
|
45
|
+
import { ClusterPaths } from './paths.js';
|
|
46
|
+
import { NamedQuery } from './states.js';
|
|
45
47
|
import { chrome, tokens } from './tokens.js';
|
|
46
48
|
/** How long typing must be quiet before a name search is sent. */
|
|
47
49
|
const SEARCH_DEBOUNCE_MS = 250;
|
|
@@ -51,8 +53,8 @@ const TOP_LEVEL_ROWS = 50;
|
|
|
51
53
|
* A path of `n` of its nearest parents, nearest last, led by an ellipsis where parents further up
|
|
52
54
|
* are left out.
|
|
53
55
|
*/
|
|
54
|
-
function pathText(path,
|
|
55
|
-
return `${beyond || n < path.length ? '… › ' : ''}${path.slice(path.length - n).join(' › ')}`;
|
|
56
|
+
function pathText(path, n) {
|
|
57
|
+
return `${path.beyond || n < path.steps.length ? '… › ' : ''}${path.steps.slice(path.steps.length - n).join(' › ')}`;
|
|
56
58
|
}
|
|
57
59
|
/** How many parents a match's path is walked up, nearest first. */
|
|
58
60
|
const PATH_DEPTH = 3;
|
|
@@ -61,8 +63,10 @@ const PATH_DEPTH = 3;
|
|
|
61
63
|
* around it. Set `layer` to a layer `meta.layers` lists.
|
|
62
64
|
*
|
|
63
65
|
* With the box empty, the list offers the layer's top-level clusters, largest first. Typing
|
|
64
|
-
* searches the cluster names at every level.
|
|
65
|
-
* chooses
|
|
66
|
+
* searches the cluster names at every level. The arrow keys move between the rows, as do Home and
|
|
67
|
+
* End once one is reached. Enter chooses the row they reached, and Escape closes the list. A line
|
|
68
|
+
* under the box says how many clusters are listed, or why none is, and that they are loading once
|
|
69
|
+
* that has taken a quarter of a second.
|
|
66
70
|
*
|
|
67
71
|
* Each row counts the items the filters match in that cluster without applying this layer's own
|
|
68
72
|
* filter, so a cluster the viewer has filtered out still shows its count. Choosing a row filters
|
|
@@ -75,12 +79,15 @@ const PATH_DEPTH = 3;
|
|
|
75
79
|
* row added or removed a `member_of` clause.
|
|
76
80
|
* @csspart entry - The search box.
|
|
77
81
|
* @csspart values - The list, while it is open.
|
|
78
|
-
* @csspart option - One cluster in the list, with `data-id
|
|
79
|
-
* on.
|
|
80
|
-
* @csspart name - A row's name
|
|
81
|
-
*
|
|
82
|
+
* @csspart option - One cluster in the list, with `data-id`, `aria-selected` while its clause is
|
|
83
|
+
* on, and `data-active` on the one the arrow keys reached.
|
|
84
|
+
* @csspart name - A row's name. Where the cluster has none it is the cluster's key, with
|
|
85
|
+
* `data-key-name`, and where it has no key either it is the word Unnamed, with `data-unnamed`.
|
|
86
|
+
* @csspart path - A row's path of its parents' names, each step naming every served parent of the
|
|
87
|
+
* step below it, by its key where it has no name.
|
|
82
88
|
* @csspart value-count - A row's exact count.
|
|
83
|
-
* @csspart more - The note under the box when nothing
|
|
89
|
+
* @csspart more - The note under the box when there is nothing to list, or when more match than
|
|
90
|
+
* are listed.
|
|
84
91
|
* @csspart refusal - The words "Clusters unavailable", with `data-code`, if the list was refused.
|
|
85
92
|
*/
|
|
86
93
|
let MosaicaClusterFilter = (() => {
|
|
@@ -186,6 +193,11 @@ let MosaicaClusterFilter = (() => {
|
|
|
186
193
|
background: var(--_mosaica-surface-2);
|
|
187
194
|
font-weight: 600;
|
|
188
195
|
}
|
|
196
|
+
/* The box keeps focus, so the row the arrow keys reached is ringed as if it had it. */
|
|
197
|
+
[part~='option'][data-active] {
|
|
198
|
+
outline: 2px solid var(--_mosaica-focus);
|
|
199
|
+
outline-offset: -2px;
|
|
200
|
+
}
|
|
189
201
|
.opt {
|
|
190
202
|
display: flex;
|
|
191
203
|
flex-direction: column;
|
|
@@ -193,11 +205,12 @@ let MosaicaClusterFilter = (() => {
|
|
|
193
205
|
}
|
|
194
206
|
[part='name'],
|
|
195
207
|
[part='path'] {
|
|
208
|
+
white-space: var(--_mosaica-long-text, nowrap);
|
|
196
209
|
overflow: hidden;
|
|
197
210
|
text-overflow: ellipsis;
|
|
198
|
-
|
|
211
|
+
overflow-wrap: anywhere;
|
|
199
212
|
}
|
|
200
|
-
/*
|
|
213
|
+
/* On one line, shortened from the left a whole name at a time, so the nearest parents stay; see pathText. */
|
|
201
214
|
[part='path'] {
|
|
202
215
|
font-size: 12px;
|
|
203
216
|
font-weight: 400;
|
|
@@ -216,6 +229,14 @@ let MosaicaClusterFilter = (() => {
|
|
|
216
229
|
font-size: 12px;
|
|
217
230
|
color: var(--_mosaica-ink-3);
|
|
218
231
|
}
|
|
232
|
+
.skel {
|
|
233
|
+
margin-top: 8px;
|
|
234
|
+
}
|
|
235
|
+
@media (forced-colors: active) {
|
|
236
|
+
[part~='option'][data-active] {
|
|
237
|
+
outline-color: Highlight;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
219
240
|
`
|
|
220
241
|
];
|
|
221
242
|
#layer_accessor_storage = __runInitializers(this, _layer_initializers, '');
|
|
@@ -243,7 +264,7 @@ let MosaicaClusterFilter = (() => {
|
|
|
243
264
|
get listOpen() { return this.#listOpen_accessor_storage; }
|
|
244
265
|
set listOpen(value) { this.#listOpen_accessor_storage = value; }
|
|
245
266
|
#activeId_accessor_storage = (__runInitializers(this, _listOpen_extraInitializers), __runInitializers(this, _activeId_initializers, null));
|
|
246
|
-
/** The row the arrow keys reached, by `mosaicaId`; `null`
|
|
267
|
+
/** The row the arrow keys reached, by `mosaicaId`; `null` before they reach one. @internal */
|
|
247
268
|
get activeId() { return this.#activeId_accessor_storage; }
|
|
248
269
|
set activeId(value) { this.#activeId_accessor_storage = value; }
|
|
249
270
|
#tops_accessor_storage = (__runInitializers(this, _activeId_extraInitializers), __runInitializers(this, _tops_initializers, null));
|
|
@@ -259,15 +280,15 @@ let MosaicaClusterFilter = (() => {
|
|
|
259
280
|
get refusal() { return this.#refusal_accessor_storage; }
|
|
260
281
|
set refusal(value) { this.#refusal_accessor_storage = value; }
|
|
261
282
|
counts = (__runInitializers(this, _refusal_extraInitializers), new HeldAggregate('clusters'));
|
|
283
|
+
/** Whether the open list's clusters have been awaited long enough to say so. */
|
|
284
|
+
awaited = new NamedQuery(this);
|
|
262
285
|
floating = new FloatingList(() => {
|
|
263
286
|
const list = this.renderRoot.querySelector('[part="values"]');
|
|
264
287
|
const anchor = this.renderRoot.querySelector('.combo');
|
|
265
288
|
return list && anchor ? { list, anchor } : null;
|
|
266
289
|
});
|
|
267
|
-
/**
|
|
268
|
-
|
|
269
|
-
/** Each artifact's served parents as a children-form page gave them. */
|
|
270
|
-
parentsOf = new Map();
|
|
290
|
+
/** The names of the offered clusters' parents, walked {@link PATH_DEPTH} steps up. */
|
|
291
|
+
paths = new ClusterPaths(() => this.requestUpdate());
|
|
271
292
|
/** The layer and view the top-level clusters were fetched for. */
|
|
272
293
|
fetchedFor = '';
|
|
273
294
|
searchTimer = null;
|
|
@@ -278,8 +299,7 @@ let MosaicaClusterFilter = (() => {
|
|
|
278
299
|
this.tops = null;
|
|
279
300
|
this.found = null;
|
|
280
301
|
this.refusal = null;
|
|
281
|
-
this.
|
|
282
|
-
this.parentsOf.clear();
|
|
302
|
+
this.paths.reset();
|
|
283
303
|
this.fetchedFor = '';
|
|
284
304
|
}
|
|
285
305
|
disconnectedCallback() {
|
|
@@ -288,15 +308,16 @@ let MosaicaClusterFilter = (() => {
|
|
|
288
308
|
this.searchTimer = null;
|
|
289
309
|
this.counts.set(null, null);
|
|
290
310
|
this.floating.stop();
|
|
311
|
+
this.awaited.stop();
|
|
291
312
|
super.disconnectedCallback();
|
|
292
313
|
}
|
|
293
314
|
declared() {
|
|
294
315
|
return this.resolvedStore?.get('meta')?.layers.find((l) => l.name === this.layer) ?? null;
|
|
295
316
|
}
|
|
296
|
-
/** The
|
|
297
|
-
|
|
317
|
+
/** The first declared level of a levelled layer, whose clusters are the top level; `undefined` on any other. */
|
|
318
|
+
firstLevel(layer) {
|
|
298
319
|
const levelled = layer.hierarchy.kind === 'stacked' || layer.hierarchy.kind === 'tiered';
|
|
299
|
-
return levelled
|
|
320
|
+
return levelled ? layer.levels[0]?.level : undefined;
|
|
300
321
|
}
|
|
301
322
|
/** One browse page of the layer. Counts come from the aggregate, so the page is asked unfiltered. */
|
|
302
323
|
browse(req) {
|
|
@@ -307,9 +328,17 @@ let MosaicaClusterFilter = (() => {
|
|
|
307
328
|
const { level, ...rest } = req;
|
|
308
329
|
return s.browse({ ...rest, layer: this.layer, filters: null, limit: Math.max(1, Math.min(req.limit, ceiling)), ...(level === undefined ? {} : { level }) });
|
|
309
330
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
331
|
+
/** Offer the rows of a page, and walk the parents of each. */
|
|
332
|
+
offer(rows, layer) {
|
|
333
|
+
const s = this.resolvedStore;
|
|
334
|
+
this.paths.meet(rows);
|
|
335
|
+
if (s)
|
|
336
|
+
this.paths.ask(s, layer, rows.map((r) => r.mosaicaId), PATH_DEPTH);
|
|
337
|
+
return rows.map((row) => ({ row }));
|
|
338
|
+
}
|
|
339
|
+
/** An offer's path of parents, once walked; empty for a cluster with none. */
|
|
340
|
+
pathOf(o) {
|
|
341
|
+
return this.paths.path(o.row.mosaicaId, PATH_DEPTH);
|
|
313
342
|
}
|
|
314
343
|
/** Fetch the top-level clusters, once per layer and view. */
|
|
315
344
|
fetchTops() {
|
|
@@ -322,12 +351,12 @@ let MosaicaClusterFilter = (() => {
|
|
|
322
351
|
return;
|
|
323
352
|
this.fetchedFor = key;
|
|
324
353
|
const epoch = this.epoch;
|
|
325
|
-
const level = this.
|
|
354
|
+
const level = this.firstLevel(layer);
|
|
326
355
|
this.browse({ limit: TOP_LEVEL_ROWS, ...(level === undefined ? {} : { level }) })
|
|
327
356
|
.then((page) => {
|
|
328
357
|
if (epoch !== this.epoch)
|
|
329
358
|
return;
|
|
330
|
-
this.tops = page.artifacts
|
|
359
|
+
this.tops = this.offer(page.artifacts, layer);
|
|
331
360
|
this.refusal = null;
|
|
332
361
|
})
|
|
333
362
|
.catch((error) => {
|
|
@@ -339,23 +368,22 @@ let MosaicaClusterFilter = (() => {
|
|
|
339
368
|
this.fetchedFor = '';
|
|
340
369
|
});
|
|
341
370
|
}
|
|
342
|
-
/**
|
|
371
|
+
/**
|
|
372
|
+
* Search the layer's names for `q`, and walk each match's path. One request with no level
|
|
373
|
+
* searches every level of a levelled layer.
|
|
374
|
+
*/
|
|
343
375
|
searchFor(q) {
|
|
344
376
|
const layer = this.declared();
|
|
345
377
|
if (!layer)
|
|
346
378
|
return;
|
|
347
379
|
const epoch = this.epoch;
|
|
348
380
|
const limit = this.resolvedStore?.get('meta')?.selection.maxBrowseRows ?? 50;
|
|
349
|
-
|
|
350
|
-
.then((
|
|
381
|
+
this.browse({ q, limit: Math.min(limit, 50) })
|
|
382
|
+
.then((page) => {
|
|
351
383
|
if (epoch !== this.epoch || this.search.trim() !== q)
|
|
352
384
|
return;
|
|
353
|
-
|
|
354
|
-
this.found = { q, offers, more: pages.some((p) => p.next !== null) };
|
|
385
|
+
this.found = { q, offers: this.offer(page.artifacts, layer), more: page.next !== null };
|
|
355
386
|
this.refusal = null;
|
|
356
|
-
const walkable = layer.hierarchy.kind === 'nested' || layer.hierarchy.kind === 'dag';
|
|
357
|
-
for (const o of offers)
|
|
358
|
-
void this.walk(o, walkable, epoch);
|
|
359
387
|
})
|
|
360
388
|
.catch((error) => {
|
|
361
389
|
if (epoch !== this.epoch)
|
|
@@ -364,38 +392,6 @@ let MosaicaClusterFilter = (() => {
|
|
|
364
392
|
this.found = { q, offers: [], more: false };
|
|
365
393
|
});
|
|
366
394
|
}
|
|
367
|
-
/**
|
|
368
|
-
* Fill in an offer's path, its first parent's name and so on up to {@link PATH_DEPTH} parents. On
|
|
369
|
-
* a layer whose children form needs no level, a missing parent is asked for; on a levelled layer
|
|
370
|
-
* only parents already met are named.
|
|
371
|
-
*/
|
|
372
|
-
async walk(o, ask, epoch) {
|
|
373
|
-
const names = [];
|
|
374
|
-
let at = o.row;
|
|
375
|
-
for (let i = 0; i < PATH_DEPTH && at.parentIds.length > 0; i++) {
|
|
376
|
-
const up = at.parentIds[0];
|
|
377
|
-
let parent = this.rows.get(up);
|
|
378
|
-
if (!parent && ask) {
|
|
379
|
-
let held = this.parentsOf.get(at.mosaicaId);
|
|
380
|
-
if (!held) {
|
|
381
|
-
held = this.browse({ parent: at.mosaicaId, limit: 1 }).then((p) => p.parents, () => []);
|
|
382
|
-
this.parentsOf.set(at.mosaicaId, held);
|
|
383
|
-
}
|
|
384
|
-
for (const r of await held)
|
|
385
|
-
this.rows.set(r.mosaicaId, r);
|
|
386
|
-
parent = this.rows.get(up);
|
|
387
|
-
}
|
|
388
|
-
if (!parent)
|
|
389
|
-
break;
|
|
390
|
-
names.unshift(artifactName(parent) ?? UNNAMED);
|
|
391
|
-
at = parent;
|
|
392
|
-
}
|
|
393
|
-
if (epoch !== this.epoch)
|
|
394
|
-
return;
|
|
395
|
-
o.path = names;
|
|
396
|
-
o.beyond = at.parentIds.length > 0;
|
|
397
|
-
this.requestUpdate();
|
|
398
|
-
}
|
|
399
395
|
/** The clauses this field holds: the layer's, in its position. */
|
|
400
396
|
held() {
|
|
401
397
|
return (this.resolvedStore?.get('filters').members ?? []).filter((m) => m.layer === this.layer && m.verb === 'filter');
|
|
@@ -409,15 +405,15 @@ let MosaicaClusterFilter = (() => {
|
|
|
409
405
|
this.search = '';
|
|
410
406
|
this.found = null;
|
|
411
407
|
this.listOpen = false;
|
|
412
|
-
|
|
408
|
+
const path = this.pathOf(offer);
|
|
409
|
+
this.choose({ id: idString(row.mosaicaId), name: artifactName(row), path: path && path.steps.length > 0 ? pathText(path, path.steps.length) : '', rung: row.rung });
|
|
413
410
|
return;
|
|
414
411
|
}
|
|
415
412
|
const members = s.get('filters').members;
|
|
416
413
|
const on = this.held().some((m) => m.artifact === row.mosaicaId && !m.outside);
|
|
417
|
-
const name = artifactName(row);
|
|
418
414
|
s.setMembers(on
|
|
419
415
|
? withoutMember(members, this.layer, row.mosaicaId, 'filter')
|
|
420
|
-
: withMember(members, { layer: this.layer, artifact: row.mosaicaId, outside: false, verb: 'filter', ...(name
|
|
416
|
+
: withMember(members, { layer: this.layer, artifact: row.mosaicaId, outside: false, verb: 'filter', ...labelOf(called(row.name, row.key)) }));
|
|
421
417
|
emit(this, 'mosaica-clausechange', { id: idString(row.mosaicaId), layer: this.layer, outside: false, verb: 'filter', on: !on });
|
|
422
418
|
this.search = '';
|
|
423
419
|
this.found = null;
|
|
@@ -459,30 +455,33 @@ let MosaicaClusterFilter = (() => {
|
|
|
459
455
|
/** The rows and width {@link fitPaths} last measured. */
|
|
460
456
|
pathsMeasured = '';
|
|
461
457
|
/**
|
|
462
|
-
*
|
|
463
|
-
* the left. Measured with the line's own font, after the list is
|
|
458
|
+
* Where a path is kept to one line, name as many of each listed path's nearest parents as fit
|
|
459
|
+
* it, dropping whole names from the left. Measured with the line's own font, after the list is
|
|
460
|
+
* placed. A path that wraps names them all.
|
|
464
461
|
*/
|
|
465
462
|
fitPaths() {
|
|
466
463
|
// Only while the list shows, and only when its rows or its width changed since last measured.
|
|
467
464
|
const shown = this.listOpen ? Array.from(this.renderRoot.querySelectorAll('[part="path"][data-id]')) : [];
|
|
468
|
-
const
|
|
465
|
+
const oneLine = shown[0] ? getComputedStyle(shown[0]).whiteSpace === 'nowrap' : false;
|
|
466
|
+
const key = `${oneLine}|${shown[0]?.clientWidth ?? 0}|${shown.map((el) => `${el.dataset.id}:${el.title}`).join(',')}`;
|
|
469
467
|
if (shown.length === 0 || key === this.pathsMeasured)
|
|
470
468
|
return;
|
|
471
469
|
this.pathsMeasured = key;
|
|
472
|
-
const context = typeof document === 'undefined' ? null : document.createElement('canvas').getContext('2d');
|
|
473
|
-
if (!context)
|
|
474
|
-
return;
|
|
470
|
+
const context = !oneLine || typeof document === 'undefined' ? null : document.createElement('canvas').getContext('2d');
|
|
475
471
|
let changed = false;
|
|
476
472
|
for (const el of shown) {
|
|
477
473
|
const id = BigInt(el.dataset.id);
|
|
478
474
|
const o = this.listed()?.find((x) => x.row.mosaicaId === id);
|
|
479
|
-
|
|
475
|
+
const path = o ? this.pathOf(o) : null;
|
|
476
|
+
if (!path)
|
|
480
477
|
continue;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
n
|
|
485
|
-
|
|
478
|
+
let n = path.steps.length;
|
|
479
|
+
if (context && el.clientWidth > 0) {
|
|
480
|
+
context.font = getComputedStyle(el).font;
|
|
481
|
+
while (n > 1 && context.measureText(pathText(path, n)).width > el.clientWidth)
|
|
482
|
+
n -= 1;
|
|
483
|
+
}
|
|
484
|
+
if ((this.pathFits.get(id) ?? path.steps.length) !== n) {
|
|
486
485
|
this.pathFits.set(id, n);
|
|
487
486
|
changed = true;
|
|
488
487
|
}
|
|
@@ -494,6 +493,9 @@ let MosaicaClusterFilter = (() => {
|
|
|
494
493
|
super.updated(changed);
|
|
495
494
|
this.floating.update();
|
|
496
495
|
this.fitPaths();
|
|
496
|
+
if (changed.has('activeId'))
|
|
497
|
+
this.renderRoot.querySelector('[part~="option"][data-active]')?.scrollIntoView({ block: 'nearest' });
|
|
498
|
+
this.awaited.track(this.listOpen && this.isConnected && this.refusal === null && this.listed() === null);
|
|
497
499
|
// Counted only while the list is open: a closed list shows no counts, so asks for none.
|
|
498
500
|
const layer = this.declared();
|
|
499
501
|
const limits = this.resolvedStore?.get('meta')?.selection;
|
|
@@ -514,30 +516,33 @@ let MosaicaClusterFilter = (() => {
|
|
|
514
516
|
const listed = this.listed();
|
|
515
517
|
// Largest first once the counts land; the server's order until then.
|
|
516
518
|
const offers = listed === null ? [] : counts === null ? listed : [...listed].sort((a, b) => (countOf(b) ?? -1) - (countOf(a) ?? -1));
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
519
|
+
// The row Enter acts on: the one the arrow keys reached. Until they reach one there is none, so
|
|
520
|
+
// Enter does not take whichever row the counts put first as they land.
|
|
521
|
+
const active = offers.find((o) => o.row.mosaicaId === this.activeId) ?? null;
|
|
522
|
+
const at = active ? offers.indexOf(active) : -1;
|
|
523
|
+
const reach = (i) => {
|
|
524
|
+
if (offers.length > 0)
|
|
525
|
+
this.activeId = offers[(i + offers.length) % offers.length].row.mosaicaId;
|
|
523
526
|
};
|
|
524
527
|
const open = this.listOpen && offers.length > 0;
|
|
525
528
|
const option = (o) => {
|
|
526
|
-
const name =
|
|
529
|
+
const name = called(o.row.name, o.row.key);
|
|
527
530
|
const count = countOf(o);
|
|
528
|
-
const
|
|
529
|
-
const
|
|
531
|
+
const walked = this.pathOf(o);
|
|
532
|
+
const full = walked && walked.steps.length > 0 ? pathText(walked, walked.steps.length) : '';
|
|
533
|
+
const path = walked && walked.steps.length > 0 ? pathText(walked, this.pathFits.get(o.row.mosaicaId) ?? walked.steps.length) : '';
|
|
530
534
|
return html `<button type="button" part="option" role="option" id=${`c-${o.row.mosaicaId}`} tabindex="-1" data-id=${idString(o.row.mosaicaId)} ?data-active=${o === active}
|
|
531
535
|
aria-selected=${on.has(o.row.mosaicaId) ? 'true' : 'false'}
|
|
532
536
|
@mousedown=${(e) => e.preventDefault()} @click=${() => this.toggle(o)}>
|
|
533
|
-
<span class="opt"><span part="name" ?data-
|
|
537
|
+
<span class="opt"><span part="name" ?data-key-name=${name.from === 'key'} ?data-unnamed=${name.from === 'none'}>${name.text}</span>${path ? html `<span part="path" data-id=${idString(o.row.mosaicaId)} title=${full}><bdi>${path}</bdi></span>` : nothing}</span>
|
|
534
538
|
${count === undefined ? nothing : html `<span part="value-count">${count.toLocaleString('en-GB')}</span>`}
|
|
535
539
|
</button>`;
|
|
536
540
|
};
|
|
541
|
+
const shown = this.placeholder || 'Type a name';
|
|
537
542
|
const box = html `<div class="combo">
|
|
538
|
-
<div class="input">${icon('search', 12)}<input id="ctl" part="entry" type="search" autocomplete="off" placeholder=${
|
|
539
|
-
role="combobox" aria-expanded=${open ? 'true' : 'false'} aria-controls="values" aria-activedescendant=${open && active ? `c-${active.row.mosaicaId}` : nothing}
|
|
540
|
-
aria-label=${`${title}:
|
|
543
|
+
<div class="input">${icon('search', 12)}<input id="ctl" part="entry" type="search" autocomplete="off" placeholder=${shown}
|
|
544
|
+
role="combobox" aria-autocomplete="list" aria-expanded=${open ? 'true' : 'false'} aria-controls="values" aria-activedescendant=${open && active ? `c-${active.row.mosaicaId}` : nothing}
|
|
545
|
+
aria-label=${`${title}: ${shown}`} .value=${this.search}
|
|
541
546
|
@focus=${() => {
|
|
542
547
|
this.fetchTops();
|
|
543
548
|
this.listOpen = true;
|
|
@@ -548,7 +553,12 @@ let MosaicaClusterFilter = (() => {
|
|
|
548
553
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
549
554
|
e.preventDefault();
|
|
550
555
|
this.listOpen = true;
|
|
551
|
-
|
|
556
|
+
reach(e.key === 'ArrowDown' ? at + 1 : Math.max(at, 0) - 1);
|
|
557
|
+
}
|
|
558
|
+
else if ((e.key === 'Home' || e.key === 'End') && open && active) {
|
|
559
|
+
// Before the arrow keys reach a row, Home and End move in the text.
|
|
560
|
+
e.preventDefault();
|
|
561
|
+
reach(e.key === 'Home' ? 0 : -1);
|
|
552
562
|
}
|
|
553
563
|
else if (e.key === 'Enter') {
|
|
554
564
|
if (open && active)
|
|
@@ -559,19 +569,26 @@ let MosaicaClusterFilter = (() => {
|
|
|
559
569
|
this.listOpen = false;
|
|
560
570
|
}
|
|
561
571
|
}} /></div>
|
|
562
|
-
${open ? html `<div part="values" id="values" popover="manual" role="listbox" aria-label=${title}>${repeat(offers, (o) => o.row.mosaicaId, option)}</div>` : nothing}
|
|
572
|
+
${open ? html `<div part="values" id="values" popover="manual" role="listbox" aria-multiselectable=${this.choose ? nothing : 'true'} aria-label=${title}>${repeat(offers, (o) => o.row.mosaicaId, option)}</div>` : nothing}
|
|
563
573
|
</div>`;
|
|
564
574
|
const typed = this.search.trim() !== '';
|
|
565
|
-
|
|
575
|
+
// How many clusters are listed, or why none is, for a reader as the list changes under the box.
|
|
576
|
+
const said = this.refusal
|
|
566
577
|
? html `<span part="refusal" data-code=${this.refusal.code}>Clusters unavailable</span>`
|
|
567
|
-
:
|
|
568
|
-
? this.
|
|
569
|
-
? html `<span
|
|
570
|
-
:
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
578
|
+
: listed === null
|
|
579
|
+
? this.listOpen
|
|
580
|
+
? html `<span class="skel" aria-hidden="true"></span>${this.awaited.named ? html `<span class="sr-only">Loading clusters</span>` : nothing}`
|
|
581
|
+
: nothing
|
|
582
|
+
: offers.length === 0
|
|
583
|
+
? typed
|
|
584
|
+
? html `<span part="more">No cluster by that name</span>`
|
|
585
|
+
: this.listOpen
|
|
586
|
+
? html `<span part="more">No clusters</span>`
|
|
587
|
+
: nothing
|
|
588
|
+
: !open
|
|
589
|
+
? nothing
|
|
590
|
+
: html `<span class="sr-only">${offers.length} ${offers.length === 1 ? 'cluster' : 'clusters'} listed. </span>${typed && this.found?.more ? html `<span part="more">Type more to narrow the list</span>` : nothing}`;
|
|
591
|
+
return html `${box}<div role="status">${said}</div>`;
|
|
575
592
|
}
|
|
576
593
|
};
|
|
577
594
|
})();
|
package/dist/colour-editor.d.ts
CHANGED
|
@@ -64,8 +64,12 @@ export declare class MosaicaColourEditor extends MosaicaElement {
|
|
|
64
64
|
static styles: import("lit").CSSResult[];
|
|
65
65
|
/** What the dialog colours: a rendered category column, or `cluster:<layer>`. */
|
|
66
66
|
accessor field: string;
|
|
67
|
-
/**
|
|
68
|
-
|
|
67
|
+
/**
|
|
68
|
+
* The level each `stacked` or `tiered` layer's clusters are listed at, keyed by layer name. A
|
|
69
|
+
* layer with no entry is listed at the level the map draws it at, which follows the zoom where its
|
|
70
|
+
* levels declare no zoom range. Set it as a property; it has no attribute.
|
|
71
|
+
*/
|
|
72
|
+
accessor clusterLevels: ReadonlyMap<string, number>;
|
|
69
73
|
/** Whether the dialog is open. @internal */
|
|
70
74
|
accessor isOpen: boolean;
|
|
71
75
|
/** The ticked rows, by key. @internal */
|
|
@@ -81,6 +85,8 @@ export declare class MosaicaColourEditor extends MosaicaElement {
|
|
|
81
85
|
private opener;
|
|
82
86
|
/** The frame the paths of the rows in sight are next asked for in, while one is waiting. */
|
|
83
87
|
private pathFrame;
|
|
88
|
+
/** The level the layer was listed at in the last update, chosen or followed. */
|
|
89
|
+
private listedAt;
|
|
84
90
|
/** Open the dialog. */
|
|
85
91
|
show(): Promise<void>;
|
|
86
92
|
/** Close the dialog and return focus to the element that had it before. */
|
|
@@ -89,11 +95,14 @@ export declare class MosaicaColourEditor extends MosaicaElement {
|
|
|
89
95
|
protected resetServerData(): void;
|
|
90
96
|
disconnectedCallback(): void;
|
|
91
97
|
/**
|
|
92
|
-
* A change of field or level
|
|
93
|
-
*
|
|
98
|
+
* A change of field or of the level its layer is listed at, chosen or followed as the map zooms,
|
|
99
|
+
* drops what was found and ticked and closes the picker, since each names the last field's
|
|
100
|
+
* values; the dialog closes where the field is now nothing it can edit.
|
|
94
101
|
*/
|
|
95
102
|
protected willUpdate(changed: PropertyValues<this>): void;
|
|
96
103
|
private get layerName();
|
|
104
|
+
/** The level `layer` is listed at: its entry in `clusterLevels`, else the level the map draws it at. */
|
|
105
|
+
private levelOf;
|
|
97
106
|
/** The layer listed, where `field` names one the points can be coloured by. */
|
|
98
107
|
private layerOf;
|
|
99
108
|
/** Whether `field` is a rendered category column. */
|
|
@@ -123,6 +132,8 @@ export declare class MosaicaColourEditor extends MosaicaElement {
|
|
|
123
132
|
private palette;
|
|
124
133
|
private pick;
|
|
125
134
|
private tick;
|
|
135
|
+
/** Untick every row. The bar Deselect is on goes with them, so focus moves to the first row's checkbox. */
|
|
136
|
+
private deselect;
|
|
126
137
|
/** A match the search box found: its row gets focus where the list holds it; else it goes at the top. */
|
|
127
138
|
private onFound;
|
|
128
139
|
private onKey;
|