@itrocks/list 0.13.6 → 0.16.3
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 +41 -17
- package/build.js +12 -9
- package/cjs/list.js +1 -1
- package/cjs/list.js.map +1 -1
- package/list.css +4 -4
- package/package.json +1 -1
- package/property-select.d.ts +8 -3
- package/property-select.js +149 -80
package/README.md
CHANGED
|
@@ -185,16 +185,33 @@ the dependency hook. The configured data source remains responsible for storage
|
|
|
185
185
|
`GET /<business-route>/list?column=<path>&rowIds=<comma-separated-identifiers>` uses the HTML action and returns a JSON
|
|
186
186
|
column descriptor with rendered cells keyed by root id. It intersects visible ids with the current filters and mandatory
|
|
187
187
|
scope, makes one projection and performs no count. Invalid selections return 400. Full entity JSON remains available
|
|
188
|
-
through `/list/json`.
|
|
188
|
+
through `/list/json`. Adding `columns=<ordered-paths>` saves the column configuration in the same request.
|
|
189
|
+
Configuration-only requests return 204. The browser serialises both kinds of request to preserve the latest order.
|
|
189
190
|
|
|
190
191
|
The browser entry `@itrocks/list/property-select.js` exports `ListPropertySelect`, with explicit metadata, table
|
|
191
192
|
addition, column persistence and narrow pane ports. The framework mounts it only on compatible lists and disposes it on
|
|
192
|
-
navigation. It reserves pending paths
|
|
193
|
-
and
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
navigation. It reserves pending paths and confirms accepted additions. The checkbox, its cell and the property name
|
|
194
|
+
toggle terminal and relation columns. The checkbox clears immediately before the red removal flash. Reselecting during that flash cancels removal,
|
|
195
|
+
checks the property again and highlights the retained column green, then saves the restored configuration.
|
|
196
|
+
The removal configuration is sent at the initial click, independently of the animation; deleting the cells sends no
|
|
197
|
+
additional request.
|
|
198
|
+
After a completed removal, a later click returns the column immediately right of its former left neighbour, or left of its former right neighbour if the left one is absent. If neither remains,
|
|
199
|
+
it is appended. Neighbours are kept only in this pane instance's memory and cleared on folding, closure or disposal;
|
|
200
|
+
they never enter session state. Drag placement continues to follow the drop position.
|
|
201
|
+
An explicit addition intention always requests addition: an existing column is revealed and highlighted green without removal.
|
|
202
|
+
Property names and checkbox cells toggle the same selection; chevrons and intermediate path segments navigate.
|
|
203
|
+
The consumer checks row identity before insertion. Failed selection leaves the context intact. It loads only currently
|
|
204
|
+
visible row ids through the current feed position; changing the row view while loading rejects the obsolete response with a
|
|
205
|
+
discreet red background. Clicks commit the translated full-path heading and filter locally, before any server response.
|
|
206
|
+
One asynchronous request
|
|
207
|
+
then saves the column order and loads the values; there is no separate admission request. A new drag calls the same
|
|
208
|
+
addition code as a click with `{ effects: false, hidden: true }`, then moves the committed column through the standard
|
|
209
|
+
table reorder operation with `{ adding: true, removal: true }`. Its combined request loads values immediately while
|
|
210
|
+
cells remain hidden, without width animation, flash or automatic scroll. On first pointer entry, the column appears
|
|
211
|
+
with any values already received and uses a green background and plus indicator. An outside drop deletes it through
|
|
212
|
+
the standard header-origin removal and saves once, even if the pointer never entered. Cancelling a new addition also
|
|
213
|
+
removes it. Dropping inside saves only a changed column order; it never reloads values.
|
|
214
|
+
The optional `request(query, signal)` port shares the list persistence queue.
|
|
198
215
|
Existing-column moves and removals only save the column configuration. The header contains each label and its filter
|
|
199
216
|
together, so it uses the table addition plugin's single-header contract.
|
|
200
217
|
|
|
@@ -227,15 +244,13 @@ The browser displays recoverable persistence errors.
|
|
|
227
244
|
|
|
228
245
|
## External column transport
|
|
229
246
|
|
|
230
|
-
The list consumer supplies
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
Admission is independent of the optional values request. A slow or failed values request leaves valid drops accepted;
|
|
235
|
-
failed cells expose `Retry values` without another addition or recent entry.
|
|
247
|
+
The list consumer supplies cells containing a non-breaking space and loads all currently loaded row ids through
|
|
248
|
+
one combined configuration/projection request after the local commit. A failed values request leaves the column
|
|
249
|
+
in place, with a discreet red background on its data cells. The spaces remain; no error text, retry button or automatic
|
|
250
|
+
retry is added. The translated full path comes from the selector's displayed row and available branch metadata.
|
|
236
251
|
|
|
237
|
-
Pending paths and the active drag are reserved before asynchronous work. Used paths
|
|
238
|
-
|
|
252
|
+
Pending paths and the active drag are reserved before asynchronous work. Used paths include the new column immediately
|
|
253
|
+
and retain their committed order throughout the drag. New accepted drops feed recents; moves and
|
|
239
254
|
cancellations do not. Row-view changes and view disposal cancel transport. The desktop pane stays open, and capture is
|
|
240
255
|
owned by the selector's surviving source controller. The consumer imports no home implementation, SQL or hydration code.
|
|
241
256
|
|
|
@@ -245,6 +260,15 @@ cancellation/rejection; it never focuses the search field. View disposal removes
|
|
|
245
260
|
restoration. Custom pane adapters may implement the same optional suspension argument without importing `home`.
|
|
246
261
|
|
|
247
262
|
`node test/browser-requests.mjs` checks request counts and real mouse gestures in Chromium with synthetic HTTP data.
|
|
248
|
-
It covers initial mount, click placeholders, hidden drag
|
|
249
|
-
|
|
263
|
+
It covers initial mount, click placeholders, hidden drag additions, values for 80 loaded rows before entry or drop,
|
|
264
|
+
green addition indicators, outside removal before and after entry, terminal toggles and reordering.
|
|
265
|
+
`node test/browser-removal-cancel.mjs` checks immediate unchecking and cancellation during the red flash through
|
|
266
|
+
checkbox, cell and name clicks, for terminal and relation columns, with an immediate removal save followed by a restored configuration save.
|
|
267
|
+
`node test/browser-column-positions.mjs` checks neighbour-based reinsertion in the real table, both fallbacks,
|
|
268
|
+
and memory expiry on closure, including closure during a pending removal. It starts its own synthetic HTTP fixture.
|
|
250
269
|
Set `PS_PLAYWRIGHT_MODULE` to an installed Playwright module when using a different runtime.
|
|
270
|
+
|
|
271
|
+
`node test/browser-toggle.mjs` exercises the real framework list against the synthetic recipe database on port 13316
|
|
272
|
+
(`PS_DATABASE_PORT` overrides it). It starts and stops its own application instance and checks desktop keyboard, touch,
|
|
273
|
+
reduced motion, horizontal reveal, removal and temporary-row transitions. `PS_TOGGLE_URL` reuses an existing instance
|
|
274
|
+
of that recipe instead. The synthetic database must already be running; this test never resets it.
|
package/build.js
CHANGED
|
@@ -11,24 +11,27 @@ import { tableByElement } from '@itrocks/table/table.js';
|
|
|
11
11
|
import { xTargetCall } from '@itrocks/xtarget/xtarget.js';
|
|
12
12
|
build('article[data-action="list"] > form > table.objects', table => {
|
|
13
13
|
let saving = Promise.resolve();
|
|
14
|
-
const
|
|
14
|
+
const request = (query, signal) => {
|
|
15
|
+
const form = table.closest('form');
|
|
16
|
+
const result = saving.then(() => fetch(`${form.action}?${query}`, { keepalive: true, signal }));
|
|
17
|
+
saving = result.then(() => { }, () => { });
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
20
|
+
const saveColumns = (paths) => {
|
|
15
21
|
const columns = table.tHead?.rows[0].querySelectorAll(':scope > th[data-property]:not(.column-add-provisional)');
|
|
16
22
|
const form = table.closest('form');
|
|
17
23
|
if (!columns || !form)
|
|
18
24
|
return Promise.resolve();
|
|
19
|
-
const properties = Array.from(columns, column =>
|
|
20
|
-
|
|
21
|
-
saving = saving.catch(() => { }).then(async () => {
|
|
22
|
-
const response = await fetch(url, { keepalive: true });
|
|
25
|
+
const properties = paths ?? Array.from(columns, column => column.dataset.property ?? '');
|
|
26
|
+
return request(new URLSearchParams({ columns: properties.join(',') })).then(response => {
|
|
23
27
|
if (!response.ok)
|
|
24
28
|
throw new Error('Unable to save columns');
|
|
25
29
|
});
|
|
26
|
-
return saving;
|
|
27
30
|
};
|
|
28
31
|
const tableColumnReorder = new TableColumnReorder({
|
|
29
32
|
isReorderable: header => header.dataset.property !== undefined,
|
|
30
|
-
onRemove: saveColumns,
|
|
31
|
-
onReorder: saveColumns
|
|
33
|
+
onRemove: () => saveColumns(),
|
|
34
|
+
onReorder: () => saveColumns()
|
|
32
35
|
});
|
|
33
36
|
const tableLink = new TableLink({
|
|
34
37
|
call: url => xTargetCall(url, 'main'),
|
|
@@ -46,7 +49,7 @@ build('article[data-action="list"] > form > table.objects', table => {
|
|
|
46
49
|
]
|
|
47
50
|
});
|
|
48
51
|
table.dispatchEvent(new CustomEvent('itrocks:list-ready', {
|
|
49
|
-
bubbles: true, detail: { addition, instance, saveColumns }
|
|
52
|
+
bubbles: true, detail: { addition, instance, request, saveColumns }
|
|
50
53
|
}));
|
|
51
54
|
table.addEventListener('click', event => {
|
|
52
55
|
const element = event.target;
|
package/cjs/list.js
CHANGED
|
@@ -89,7 +89,7 @@ let List = (() => {
|
|
|
89
89
|
for (const field of column.fields ?? [])
|
|
90
90
|
await access.authorise(`${column.name}.${field}`);
|
|
91
91
|
}
|
|
92
|
-
if (request.request.data.columns !== undefined) {
|
|
92
|
+
if (request.request.data.columns !== undefined && typeof request.request.data.column !== 'string') {
|
|
93
93
|
(0, persist_2.save)(this, request.request, type);
|
|
94
94
|
return this.htmlResponse('', 204);
|
|
95
95
|
}
|
package/cjs/list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAAwD;AAExD,4CAAwD;AACxD,4CAAwD;AAExD,0CAAuD;AACvD,0CAAuD;AACvD,8CAAyD;AAEzD,8CAAyD;AACzD,8CAAyD;AACzD,qCAAiD;AACjD,uCAAkD;AAClD,uCAAkD;AAClD,iCAA+C;AAC/C,qCAAiD;AACjD,uCAAkD;AAClD,uCAAkD;AAClD,6CAAqD;AACrD,qDAAyD;AACzD,qDAAyD;AACzD,iCAA+C;AAE/C,mCAAwC;AAA/B,uGAAA,aAAa,OAAA;IAKT,IAAI;4BAFhB,IAAA,aAAI,EAAC,OAAO,EAAE,KAAK,CAAC,EACpB,IAAA,aAAK,EAAC,OAAO,CAAC;;;;sBACsC,eAAM;oBAAd,SAAQ,WAAS;;;;YAA9D,6KAuGC;;;YAvGY,uDAAI;;QAGhB,OAAO,GAAG,IAAI,iBAAO,CAAA;QACrB,IAAI,GAAM,IAAI,WAAI,CAAA;QAClB,MAAM,GAAI,IAAI,eAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,GAAM,IAAI,WAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAEhC,KAAK,CAAC,IAAI,CAAC,OAAmB,EAAE,KAAqB;YAEpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;YAC5B,MAAM,IAAI,GAAM,IAAI,CAAC,IAAI,CAAA;YACzB,MAAM,MAAM,GAAI,IAAI,CAAC,MAAM,CAAA;YAC3B,MAAM,IAAI,GAAM,IAAI,CAAC,IAAI,CAAA;YACzB,MAAM,IAAI,GAAM,OAAO,CAAC,IAAI,CAAA;YAE5B,IAAA,cAAI,EAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YAEjC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YACxC,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,EAAC,OAAO,CAAC,CAAA;YACxC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBACnC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,IAAI,EAAE;oBAAE,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,CAAA;YAC3F,CAAC;YACD,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":"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"}
|
package/list.css
CHANGED
|
@@ -51,7 +51,7 @@ table.objects > * > tr > .column-reorder {
|
|
|
51
51
|
--column-reorder-tint: inset 0 0 0 100vmax rgb(0 0 0 / 2%);
|
|
52
52
|
box-shadow: var(--column-reorder-tint);
|
|
53
53
|
}
|
|
54
|
-
table.objects > * > tr > .column-reorder.
|
|
54
|
+
table.objects > * > tr > .column-reorder.adding {
|
|
55
55
|
--column-reorder-tint: inset 0 0 0 100vmax rgb(47 137 67 / 5%);
|
|
56
56
|
}
|
|
57
57
|
table.objects > * > tr > .column-reorder.moving {
|
|
@@ -84,10 +84,10 @@ table.objects > tbody:first-child > tr:nth-child(odd) > td {
|
|
|
84
84
|
table.objects > thead > tr.properties > th {
|
|
85
85
|
user-select: none;
|
|
86
86
|
}
|
|
87
|
-
table.objects > thead > tr.properties > th.moving, table.objects > thead > tr.properties > th.removing {
|
|
87
|
+
table.objects > thead > tr.properties > th.adding, table.objects > thead > tr.properties > th.moving, table.objects > thead > tr.properties > th.removing {
|
|
88
88
|
z-index: 2;
|
|
89
89
|
}
|
|
90
|
-
table.objects > thead > tr.properties > th.
|
|
90
|
+
table.objects > thead > tr.properties > th.adding::before, table.objects > thead > tr.properties > th.moving::before, table.objects > thead > tr.properties > th.removing::before {
|
|
91
91
|
content: "";
|
|
92
92
|
display: inline-block;
|
|
93
93
|
height: 1em;
|
|
@@ -108,7 +108,7 @@ table.objects > thead > tr.properties > th.removing::before {
|
|
|
108
108
|
background: #d51d2c;
|
|
109
109
|
mask-image: url("column-remove.svg");
|
|
110
110
|
}
|
|
111
|
-
table.objects > thead > tr.properties > th.
|
|
111
|
+
table.objects > thead > tr.properties > th.adding::before {
|
|
112
112
|
background: #2f8943;
|
|
113
113
|
mask-image: url("column-add.svg");
|
|
114
114
|
}
|
package/package.json
CHANGED
package/property-select.d.ts
CHANGED
|
@@ -25,7 +25,8 @@ export type ListSelectorOptions = {
|
|
|
25
25
|
addition: TableColumnAdd;
|
|
26
26
|
metadata: MetadataProvider;
|
|
27
27
|
mountSelector(content: HTMLElement, title: string): PaneHandle;
|
|
28
|
-
|
|
28
|
+
request?(query: URLSearchParams, signal: AbortSignal): Promise<Response>;
|
|
29
|
+
saveColumns(paths?: readonly string[]): Promise<void>;
|
|
29
30
|
templateUrl?: string;
|
|
30
31
|
};
|
|
31
32
|
export declare class ListPropertySelect implements ConsumerPort {
|
|
@@ -33,24 +34,28 @@ export declare class ListPropertySelect implements ConsumerPort {
|
|
|
33
34
|
private readonly options;
|
|
34
35
|
private readonly abort;
|
|
35
36
|
private readonly button;
|
|
37
|
+
private readonly content;
|
|
36
38
|
private disposed;
|
|
37
39
|
private drag?;
|
|
38
40
|
private readonly observer;
|
|
39
41
|
private readonly pane;
|
|
40
42
|
private readonly pending;
|
|
43
|
+
private readonly removals;
|
|
44
|
+
private removedColumns;
|
|
45
|
+
private readonly unsubscribePane;
|
|
41
46
|
readonly view: PropertySelectView;
|
|
42
47
|
constructor(article: HTMLElement, options: ListSelectorOptions);
|
|
43
48
|
private loadColumn;
|
|
44
49
|
private add;
|
|
45
50
|
begin(intent: SelectionIntent, sample: PointerSample): void;
|
|
46
51
|
private columnSource;
|
|
47
|
-
private validateColumn;
|
|
48
52
|
cancel(id: string): void;
|
|
49
|
-
private
|
|
53
|
+
private displayedPaths;
|
|
50
54
|
dispose(): void;
|
|
51
55
|
end(id: string, sample: PointerSample): Promise<Outcome>;
|
|
52
56
|
mount(): Promise<boolean>;
|
|
53
57
|
move(id: string, sample: PointerSample): void;
|
|
54
58
|
paths(): string[];
|
|
59
|
+
private remove;
|
|
55
60
|
select(intent: SelectionIntent, signal: AbortSignal): Promise<Outcome>;
|
|
56
61
|
}
|
package/property-select.js
CHANGED
|
@@ -6,26 +6,35 @@ export class ListPropertySelect {
|
|
|
6
6
|
options;
|
|
7
7
|
abort = new AbortController();
|
|
8
8
|
button;
|
|
9
|
+
content = document.createElement('div');
|
|
9
10
|
disposed = false;
|
|
10
11
|
drag;
|
|
11
12
|
observer;
|
|
12
13
|
pane;
|
|
13
14
|
pending = new Map();
|
|
15
|
+
removals = new Map();
|
|
16
|
+
removedColumns = new Map();
|
|
17
|
+
unsubscribePane;
|
|
14
18
|
view;
|
|
15
19
|
constructor(article, options) {
|
|
16
20
|
this.article = article;
|
|
17
21
|
this.options = options;
|
|
18
|
-
const content =
|
|
22
|
+
const content = this.content;
|
|
19
23
|
this.button = article.querySelector('.add-column');
|
|
20
|
-
this.pane = options.mountSelector(content, 'Add columns');
|
|
21
24
|
const initial = article.dataset.selectorBootstrap
|
|
22
25
|
? JSON.parse(decodeURIComponent(article.dataset.selectorBootstrap)) : undefined;
|
|
26
|
+
const translate = (text) => initial?.translations?.[text] ?? text;
|
|
27
|
+
this.pane = options.mountSelector(content, translate('Add columns'));
|
|
28
|
+
this.unsubscribePane = this.pane.subscribe(state => {
|
|
29
|
+
if (!state.open || !state.present)
|
|
30
|
+
this.removedColumns = new Map();
|
|
31
|
+
});
|
|
23
32
|
this.view = new PropertySelectView(content, {
|
|
24
33
|
consumer: this, metadata: options.metadata, root: article.dataset.propertyRoot,
|
|
25
34
|
advice: options.advice, stateKey: article.dataset.selectorKey, stateMode: 'session',
|
|
26
35
|
stateStore: new HttpStateStore(undefined, undefined, initial && {
|
|
27
36
|
...initial, key: article.dataset.selectorKey
|
|
28
|
-
}), usedPaths: this.paths()
|
|
37
|
+
}), translate, usedPaths: this.paths()
|
|
29
38
|
}, { template: initial?.template, templateUrl: options.templateUrl });
|
|
30
39
|
this.observer = new MutationObserver(records => {
|
|
31
40
|
if (this.drag && records.some(record => record.target.tagName === 'TBODY'))
|
|
@@ -41,10 +50,11 @@ export class ListPropertySelect {
|
|
|
41
50
|
const table = this.article.querySelector('table.objects');
|
|
42
51
|
const rows = [...table.tBodies[0].rows];
|
|
43
52
|
const ids = rows.map(row => row.querySelector('input[name=id]').value);
|
|
44
|
-
const query = new URLSearchParams({ column: path, rowIds: ids.join(',') });
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
const query = new URLSearchParams({ column: path, columns: this.paths().join(','), rowIds: ids.join(',') });
|
|
54
|
+
const combined = AbortSignal.any([signal, this.abort.signal]);
|
|
55
|
+
const response = this.options.request
|
|
56
|
+
? await this.options.request(query, combined)
|
|
57
|
+
: await fetch(`${form.action}?${query}`, { signal: combined });
|
|
48
58
|
const result = await response.json();
|
|
49
59
|
if (!response.ok)
|
|
50
60
|
throw new Error(result.error ?? 'Column unavailable');
|
|
@@ -64,6 +74,16 @@ export class ListPropertySelect {
|
|
|
64
74
|
input.name = `search.${encodeURIComponent(path).replaceAll('.', '%2E')}`;
|
|
65
75
|
input.dataset.propertyFilter = path;
|
|
66
76
|
input.setAttribute('aria-label', `Filter ${result.label}`);
|
|
77
|
+
const previous = [...table.tHead.rows[0].cells].find(header => header.dataset.property === path);
|
|
78
|
+
const previousInput = previous?.querySelector('input[data-property-filter]');
|
|
79
|
+
input.value = previousInput?.value ?? '';
|
|
80
|
+
if (previousInput && document.activeElement === previousInput)
|
|
81
|
+
requestAnimationFrame(() => {
|
|
82
|
+
if (!input.isConnected || document.activeElement !== document.body)
|
|
83
|
+
return;
|
|
84
|
+
input.focus();
|
|
85
|
+
input.setSelectionRange(previousInput.selectionStart, previousInput.selectionEnd);
|
|
86
|
+
});
|
|
67
87
|
header.append(result.label, input);
|
|
68
88
|
const cells = ids.map(id => {
|
|
69
89
|
const cell = document.createElement('td');
|
|
@@ -72,16 +92,28 @@ export class ListPropertySelect {
|
|
|
72
92
|
});
|
|
73
93
|
return { cells, header };
|
|
74
94
|
}
|
|
75
|
-
|
|
95
|
+
add(path, signal, options = {}) {
|
|
76
96
|
const source = this.columnSource(path);
|
|
77
|
-
const operation = this.options.addition.begin(source);
|
|
97
|
+
const operation = this.options.addition.begin(source, options);
|
|
78
98
|
try {
|
|
79
|
-
await this.validateColumn(path, signal);
|
|
80
99
|
signal.throwIfAborted();
|
|
81
100
|
if (this.disposed)
|
|
82
101
|
throw new Error('List disposed');
|
|
102
|
+
const position = this.removedColumns.get(path);
|
|
103
|
+
if (position) {
|
|
104
|
+
const headers = [...this.article.querySelectorAll('thead [data-property]:not(.column-add-provisional)')];
|
|
105
|
+
const left = headers.find(header => header.dataset.property === position.left);
|
|
106
|
+
const right = headers.find(header => header.dataset.property === position.right);
|
|
107
|
+
const anchor = left ?? right;
|
|
108
|
+
if (anchor) {
|
|
109
|
+
let index = anchor.cellIndex + (left ? 1 : 0);
|
|
110
|
+
if (operation.header.cellIndex < index)
|
|
111
|
+
index--;
|
|
112
|
+
operation.moveTo(index);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
83
115
|
const outcome = operation.commit();
|
|
84
|
-
|
|
116
|
+
this.removedColumns.delete(path);
|
|
85
117
|
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
86
118
|
return outcome;
|
|
87
119
|
}
|
|
@@ -94,21 +126,14 @@ export class ListPropertySelect {
|
|
|
94
126
|
if (this.disposed || this.drag || this.pending.size)
|
|
95
127
|
throw new Error('A column operation is pending');
|
|
96
128
|
const path = normalisePath(intent.path);
|
|
97
|
-
const
|
|
129
|
+
const added = !this.paths().includes(path);
|
|
98
130
|
const suspension = this.pane.suspend({ mobileOnly: true });
|
|
99
131
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
source.accept = async (signal) => {
|
|
106
|
-
await dropped;
|
|
107
|
-
signal.throwIfAborted();
|
|
108
|
-
await this.validateColumn(path, signal);
|
|
109
|
-
};
|
|
110
|
-
const operation = this.options.addition.beginExternal(source, sample);
|
|
111
|
-
this.drag = { drop, intent, operation, paths, restore: () => suspension.restore() };
|
|
132
|
+
if (added)
|
|
133
|
+
this.add(path, this.abort.signal, { effects: false, hidden: true });
|
|
134
|
+
const paths = this.paths();
|
|
135
|
+
const operation = this.options.addition.beginExternal(this.columnSource(path), sample, { adding: added, removal: true });
|
|
136
|
+
this.drag = { added, intent, operation, paths, restore: () => suspension.restore() };
|
|
112
137
|
}
|
|
113
138
|
catch (error) {
|
|
114
139
|
suspension.restore();
|
|
@@ -116,10 +141,17 @@ export class ListPropertySelect {
|
|
|
116
141
|
}
|
|
117
142
|
}
|
|
118
143
|
columnSource(path) {
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
144
|
+
const selector = this.view.selector;
|
|
145
|
+
const row = this.content.querySelector(`.ps-property[data-path="${CSS.escape(path)}"]`);
|
|
146
|
+
const result = selector.search.results.find(result => result.property.path === path);
|
|
147
|
+
const properties = selector.branchContexts.flatMap(context => context.properties);
|
|
148
|
+
const label = row?.title || selector.usedProperties.find(property => property.path === path)?.label
|
|
149
|
+
|| path.split('.').map((name, index, names) => {
|
|
150
|
+
const prefix = names.slice(0, index + 1).join('.');
|
|
151
|
+
return properties.find(property => property.path === prefix)?.label
|
|
152
|
+
|| result?.segments.find(segment => segment.path === prefix)?.label
|
|
153
|
+
|| (prefix === path ? result?.property.label || row?.querySelector('.ps-main')?.textContent : '') || name;
|
|
154
|
+
}).join('.');
|
|
123
155
|
return {
|
|
124
156
|
id: path,
|
|
125
157
|
create: () => {
|
|
@@ -127,33 +159,27 @@ export class ListPropertySelect {
|
|
|
127
159
|
header.dataset.property = path;
|
|
128
160
|
header.dataset.sortable = 'false';
|
|
129
161
|
header.textContent = label;
|
|
162
|
+
const input = document.createElement('input');
|
|
163
|
+
input.name = `search.${encodeURIComponent(path).replaceAll('.', '%2E')}`;
|
|
164
|
+
input.dataset.propertyFilter = path;
|
|
165
|
+
input.setAttribute('aria-label', `Filter ${label}`);
|
|
166
|
+
header.append(input);
|
|
130
167
|
const cells = [...this.article.querySelectorAll('table.objects tbody tr')]
|
|
131
|
-
.map(() =>
|
|
168
|
+
.map(() => {
|
|
169
|
+
const cell = document.createElement('td');
|
|
170
|
+
cell.textContent = '\u00a0';
|
|
171
|
+
return cell;
|
|
172
|
+
});
|
|
132
173
|
return { cells, header };
|
|
133
174
|
},
|
|
134
175
|
load: async (signal) => {
|
|
176
|
+
// Commit the placeholder before reading the column configuration.
|
|
177
|
+
await Promise.resolve();
|
|
135
178
|
signal.throwIfAborted();
|
|
136
|
-
|
|
137
|
-
return await this.loadColumn(path, signal);
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
if (!this.disposed && !signal.aborted)
|
|
141
|
-
this.previewError(path);
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
179
|
+
return this.loadColumn(path, signal);
|
|
144
180
|
}
|
|
145
181
|
};
|
|
146
182
|
}
|
|
147
|
-
async validateColumn(path, signal) {
|
|
148
|
-
const form = this.article.querySelector('form');
|
|
149
|
-
const query = new URLSearchParams({ column: path, validate: '1' });
|
|
150
|
-
const response = await fetch(`${form.action}?${query}`, {
|
|
151
|
-
signal: AbortSignal.any([signal, this.abort.signal])
|
|
152
|
-
});
|
|
153
|
-
const result = await response.json();
|
|
154
|
-
if (!response.ok || result.path !== path)
|
|
155
|
-
throw new Error(result.error ?? 'Column unavailable');
|
|
156
|
-
}
|
|
157
183
|
cancel(id) {
|
|
158
184
|
if (this.drag?.intent.id !== id)
|
|
159
185
|
return;
|
|
@@ -163,24 +189,10 @@ export class ListPropertySelect {
|
|
|
163
189
|
drag.restore();
|
|
164
190
|
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
165
191
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
return;
|
|
171
|
-
for (const row of this.article.querySelectorAll('tbody tr'))
|
|
172
|
-
row.cells[header.cellIndex].textContent = 'Unable to load values';
|
|
173
|
-
if (header.querySelector('.column-retry'))
|
|
174
|
-
return;
|
|
175
|
-
const retry = document.createElement('button');
|
|
176
|
-
retry.type = 'button';
|
|
177
|
-
retry.className = 'column-retry';
|
|
178
|
-
retry.textContent = 'Retry values';
|
|
179
|
-
retry.addEventListener('click', event => {
|
|
180
|
-
event.stopPropagation();
|
|
181
|
-
void this.options.addition.retry(path);
|
|
182
|
-
}, { signal: this.abort.signal });
|
|
183
|
-
header.append(retry);
|
|
192
|
+
displayedPaths() {
|
|
193
|
+
return [...this.article.querySelectorAll('thead [data-property]:not(.column-add-provisional)')]
|
|
194
|
+
.map(header => header.dataset.property)
|
|
195
|
+
.filter(path => !this.removals.has(path) || this.removals.get(path).signal.aborted);
|
|
184
196
|
}
|
|
185
197
|
dispose() {
|
|
186
198
|
if (this.disposed)
|
|
@@ -190,22 +202,30 @@ export class ListPropertySelect {
|
|
|
190
202
|
this.disposed = true;
|
|
191
203
|
this.abort.abort();
|
|
192
204
|
this.observer.disconnect();
|
|
205
|
+
this.unsubscribePane();
|
|
206
|
+
this.removedColumns.clear();
|
|
193
207
|
this.view.dispose();
|
|
194
208
|
this.pane.remove();
|
|
195
209
|
this.options.addition.dispose();
|
|
196
210
|
this.button.hidden = true;
|
|
197
211
|
this.pending.clear();
|
|
212
|
+
for (const removal of this.removals.values())
|
|
213
|
+
removal.abort();
|
|
214
|
+
this.removals.clear();
|
|
198
215
|
}
|
|
199
216
|
async end(id, sample) {
|
|
200
217
|
const drag = this.drag;
|
|
201
218
|
if (!drag || drag.intent.id !== id)
|
|
202
219
|
throw new Error('Unknown column transport');
|
|
203
220
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
221
|
+
let status = await drag.operation.finish(sample);
|
|
222
|
+
if (status !== 'cancelled') {
|
|
223
|
+
this.removedColumns.delete(drag.intent.path);
|
|
224
|
+
if (status === 'moved' && this.displayedPaths().join(',') !== drag.paths.join(','))
|
|
225
|
+
await this.options.saveColumns();
|
|
226
|
+
if (drag.added && status === 'moved')
|
|
227
|
+
status = 'added';
|
|
228
|
+
}
|
|
209
229
|
if (this.disposed)
|
|
210
230
|
return { ...drag.intent, status: 'cancelled' };
|
|
211
231
|
if (this.drag === drag)
|
|
@@ -234,10 +254,42 @@ export class ListPropertySelect {
|
|
|
234
254
|
this.drag.operation.move(sample);
|
|
235
255
|
}
|
|
236
256
|
paths() {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
257
|
+
return this.drag ? [...this.drag.paths] : this.displayedPaths();
|
|
258
|
+
}
|
|
259
|
+
async remove(path, signal) {
|
|
260
|
+
const positions = this.removedColumns;
|
|
261
|
+
const paths = this.paths();
|
|
262
|
+
const index = paths.indexOf(path);
|
|
263
|
+
const position = { left: paths[index - 1], right: paths[index + 1] };
|
|
264
|
+
const removal = new AbortController();
|
|
265
|
+
this.removals.set(path, removal);
|
|
266
|
+
// Publish the unchecked state before starting the table's removal flash.
|
|
267
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
268
|
+
try {
|
|
269
|
+
const saving = this.options.saveColumns(this.paths());
|
|
270
|
+
const animation = this.options.addition.remove(path, AbortSignal.any([signal, removal.signal]));
|
|
271
|
+
const [removed] = await Promise.all([animation, saving]);
|
|
272
|
+
if (!removed) {
|
|
273
|
+
if (removal.signal.aborted && !signal.aborted && !this.disposed) {
|
|
274
|
+
await this.options.saveColumns(this.paths());
|
|
275
|
+
return 'existing';
|
|
276
|
+
}
|
|
277
|
+
throw new Error('Column removal cancelled');
|
|
278
|
+
}
|
|
279
|
+
if (!this.disposed && positions === this.removedColumns && index >= 0)
|
|
280
|
+
positions.set(path, position);
|
|
281
|
+
return 'removed';
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
removal.abort();
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
if (this.removals.get(path) === removal)
|
|
289
|
+
this.removals.delete(path);
|
|
290
|
+
if (!this.disposed)
|
|
291
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
292
|
+
}
|
|
241
293
|
}
|
|
242
294
|
async select(intent, signal) {
|
|
243
295
|
const path = normalisePath(intent.path);
|
|
@@ -247,13 +299,27 @@ export class ListPropertySelect {
|
|
|
247
299
|
let status;
|
|
248
300
|
const pending = this.pending.get(path);
|
|
249
301
|
if (pending) {
|
|
250
|
-
|
|
302
|
+
const removal = this.removals.get(path);
|
|
303
|
+
if (removal && !removal.signal.aborted) {
|
|
304
|
+
removal.abort();
|
|
305
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
306
|
+
}
|
|
307
|
+
else if (removal && intent.action !== 'add') {
|
|
308
|
+
await pending;
|
|
309
|
+
return this.select(intent, signal);
|
|
310
|
+
}
|
|
311
|
+
const outcome = await pending;
|
|
312
|
+
if (removal && outcome === 'removed')
|
|
313
|
+
return this.select({ ...intent, action: 'add' }, signal);
|
|
251
314
|
status = 'existing';
|
|
252
315
|
}
|
|
253
|
-
else if (this.paths().includes(path))
|
|
254
|
-
status = 'existing';
|
|
255
316
|
else {
|
|
256
|
-
const
|
|
317
|
+
const existing = this.paths().includes(path);
|
|
318
|
+
const operation = existing && (intent.action !== 'add')
|
|
319
|
+
? this.remove(path, signal)
|
|
320
|
+
: existing
|
|
321
|
+
? Promise.resolve('existing')
|
|
322
|
+
: Promise.resolve(this.add(path, signal));
|
|
257
323
|
this.pending.set(path, operation);
|
|
258
324
|
try {
|
|
259
325
|
status = await operation;
|
|
@@ -266,8 +332,11 @@ export class ListPropertySelect {
|
|
|
266
332
|
signal.throwIfAborted();
|
|
267
333
|
if (this.disposed)
|
|
268
334
|
throw new Error('List disposed');
|
|
269
|
-
this.
|
|
270
|
-
|
|
335
|
+
if (status !== 'removed' && this.paths().includes(path)) {
|
|
336
|
+
this.options.addition.reveal(path);
|
|
337
|
+
if (status === 'existing')
|
|
338
|
+
this.options.addition.highlight(path);
|
|
339
|
+
}
|
|
271
340
|
return { ...intent, status };
|
|
272
341
|
}
|
|
273
342
|
catch (error) {
|