@itrocks/list 0.13.4 → 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 +60 -22
- package/build.js +12 -9
- package/cjs/access.d.ts +2 -0
- package/cjs/access.js.map +1 -1
- package/cjs/list.html +1 -1
- package/cjs/list.js +15 -8
- package/cjs/list.js.map +1 -1
- package/cjs/multiple-output.js +1 -3
- package/cjs/multiple-output.js.map +1 -1
- package/cjs/projection.d.ts +1 -0
- package/cjs/projection.js +15 -5
- package/cjs/projection.js.map +1 -1
- package/cjs/selector-state.d.ts +4 -0
- package/cjs/selector-state.js +10 -0
- package/cjs/selector-state.js.map +1 -1
- package/list.css +5 -5
- package/package.json +1 -1
- package/property-select.d.ts +10 -3
- package/property-select.js +183 -90
- package/selector-state.d.ts +6 -1
- package/selector-state.js +10 -1
package/README.md
CHANGED
|
@@ -152,11 +152,12 @@ missing representative fields fail explicitly. Plural paths through a relationsh
|
|
|
152
152
|
are refused before SQL.
|
|
153
153
|
|
|
154
154
|
Direct collections display representative values separated by commas. A collection decorated with
|
|
155
|
-
[`@Component()`](https://github.com/itrocks-ts/composition)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
[`@Component()`](https://github.com/itrocks-ts/composition) displays up to eight representative values separated by
|
|
156
|
+
commas. From nine values, it displays the count followed by the translated plural name of the destination type.
|
|
157
|
+
Component collections never render a nested table in a list cell. The optional
|
|
158
|
+
`@MultipleOutput('commas' | 'lines')` property decorator from `@itrocks/list/multiple-output` controls ordinary
|
|
159
|
+
collections; `'lines'` displays them as a list.
|
|
160
|
+
`multipleOutputOf(type, property)` returns the effective mode and inherits explicit property metadata. For example:
|
|
160
161
|
|
|
161
162
|
```ts
|
|
162
163
|
import { MultipleOutput } from '@itrocks/list/multiple-output'
|
|
@@ -184,16 +185,35 @@ the dependency hook. The configured data source remains responsible for storage
|
|
|
184
185
|
`GET /<business-route>/list?column=<path>&rowIds=<comma-separated-identifiers>` uses the HTML action and returns a JSON
|
|
185
186
|
column descriptor with rendered cells keyed by root id. It intersects visible ids with the current filters and mandatory
|
|
186
187
|
scope, makes one projection and performs no count. Invalid selections return 400. Full entity JSON remains available
|
|
187
|
-
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.
|
|
188
190
|
|
|
189
191
|
The browser entry `@itrocks/list/property-select.js` exports `ListPropertySelect`, with explicit metadata, table
|
|
190
192
|
addition, column persistence and narrow pane ports. The framework mounts it only on compatible lists and disposes it on
|
|
191
|
-
navigation. It reserves pending paths
|
|
192
|
-
and
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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.
|
|
215
|
+
Existing-column moves and removals only save the column configuration. The header contains each label and its filter
|
|
216
|
+
together, so it uses the table addition plugin's single-header contract.
|
|
197
217
|
|
|
198
218
|
`npm test` runs the server contracts; `test/property-select.test.mjs` covers projection, representation, scope, path
|
|
199
219
|
configuration, filters and ordering. Real framework/browser proof is tracked by the property-select L2-LIST delivery.
|
|
@@ -212,21 +232,25 @@ do not affect it. The rendered article supplies this opaque key to the browser.
|
|
|
212
232
|
Writes echo the opaque `X-Property-Select-Session` token returned by a read. It cannot identify or authenticate a user;
|
|
213
233
|
it prevents a mounted selector from writing its old state into a new session.
|
|
214
234
|
|
|
235
|
+
The initial HTML response carries the configured selector bootstrap, including metadata, template, state revision
|
|
236
|
+
and session generation. Mounting consumes this snapshot without application requests or state writes. Metadata is
|
|
237
|
+
shared by navigation, suggestions and selection; unchanged column paths do not rebuild suggestions.
|
|
238
|
+
|
|
215
239
|
The public browser `@itrocks/list/selector-state.js` exports `HttpStateStore`, using same-origin credentials and
|
|
216
|
-
no-store requests. `
|
|
217
|
-
|
|
240
|
+
no-store requests. Its optional third constructor argument `{ generation, key, record }` supplies the initial read;
|
|
241
|
+
subsequent reads, including conflict recovery, use the server. `ListSelectorOptions.advice` supplies configured
|
|
242
|
+
destinations. List column changes refresh used paths; only a confirmed new addition feeds the selector's recents.
|
|
243
|
+
The browser displays recoverable persistence errors.
|
|
218
244
|
|
|
219
245
|
## External column transport
|
|
220
246
|
|
|
221
|
-
The list consumer supplies
|
|
222
|
-
projection
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Admission is independent of the optional values request. A slow or failed values request leaves valid drops accepted;
|
|
226
|
-
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.
|
|
227
251
|
|
|
228
|
-
Pending paths and the active drag are reserved before asynchronous work. Used paths
|
|
229
|
-
|
|
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
|
|
230
254
|
cancellations do not. Row-view changes and view disposal cancel transport. The desktop pane stays open, and capture is
|
|
231
255
|
owned by the selector's surviving source controller. The consumer imports no home implementation, SQL or hydration code.
|
|
232
256
|
|
|
@@ -234,3 +258,17 @@ The consumer requests `pane.suspend({ mobileOnly: true })` only when a source ge
|
|
|
234
258
|
host owns its responsive breakpoint. The token is restored after acceptance and column persistence, or after
|
|
235
259
|
cancellation/rejection; it never focuses the search field. View disposal removes the registration and invalidates late
|
|
236
260
|
restoration. Custom pane adapters may implement the same optional suspension argument without importing `home`.
|
|
261
|
+
|
|
262
|
+
`node test/browser-requests.mjs` checks request counts and real mouse gestures in Chromium with synthetic HTTP data.
|
|
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.
|
|
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/access.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Request } from '@itrocks/action-request';
|
|
2
|
+
import type { StateRecord } from '@itrocks/property-select';
|
|
2
3
|
export type ListAccess = {
|
|
3
4
|
authorise(path: string): Promise<void>;
|
|
5
|
+
bootstrap?(paths: string[], state: StateRecord): Promise<object>;
|
|
4
6
|
root?: string;
|
|
5
7
|
};
|
|
6
8
|
export type ListDependencies = {
|
package/cjs/access.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":";;AAYA,sCAGC;AAED,gCAGC;AAVD,MAAM,YAAY,GAAqB,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC,EAAE,CAAC,EAAE,CAAA;AAE9F,SAAgB,aAAa,CAAC,SAAoC;IAEjE,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;AACvC,CAAC;AAED,SAAgB,UAAU,CAAC,OAAgB;IAE1C,OAAO,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AACpC,CAAC"}
|
package/cjs/list.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
<!--#main-->
|
|
17
17
|
<article data-action="list" data-class="{type.name}" data-route="{type.@route}" data-property-root="{?root}"
|
|
18
|
-
data-selector-key="{?selectorKey}">
|
|
18
|
+
data-selector-key="{?selectorKey}" data-selector-bootstrap="{?selectorBootstrap}">
|
|
19
19
|
<header>
|
|
20
20
|
<h2>{type.@output.ucFirst} list</h2>
|
|
21
21
|
<button class="add-column" hidden type="button" aria-label="Add column">+</button>
|
package/cjs/list.js
CHANGED
|
@@ -52,11 +52,12 @@ const persist_1 = require("./persist");
|
|
|
52
52
|
const persist_2 = require("./persist");
|
|
53
53
|
const projection_1 = require("./projection");
|
|
54
54
|
const selector_state_1 = require("./selector-state");
|
|
55
|
+
const selector_state_2 = require("./selector-state");
|
|
55
56
|
const sort_1 = require("./sort");
|
|
56
57
|
var access_2 = require("./access");
|
|
57
58
|
Object.defineProperty(exports, "listDependsOn", { enumerable: true, get: function () { return access_2.listDependsOn; } });
|
|
58
59
|
let List = (() => {
|
|
59
|
-
let _classDecorators = [(0,
|
|
60
|
+
let _classDecorators = [(0, action_2.Need)('Store', 'new'), (0, route_1.Route)('/list')];
|
|
60
61
|
let _classDescriptor;
|
|
61
62
|
let _classExtraInitializers = [];
|
|
62
63
|
let _classThis;
|
|
@@ -88,7 +89,7 @@ let List = (() => {
|
|
|
88
89
|
for (const field of column.fields ?? [])
|
|
89
90
|
await access.authorise(`${column.name}.${field}`);
|
|
90
91
|
}
|
|
91
|
-
if (request.request.data.columns !== undefined) {
|
|
92
|
+
if (request.request.data.columns !== undefined && typeof request.request.data.column !== 'string') {
|
|
92
93
|
(0, persist_2.save)(this, request.request, type);
|
|
93
94
|
return this.htmlResponse('', 204);
|
|
94
95
|
}
|
|
@@ -116,7 +117,7 @@ let List = (() => {
|
|
|
116
117
|
const ids = request.request.data.rowIds;
|
|
117
118
|
if (typeof ids !== 'string')
|
|
118
119
|
throw new Error('Visible row identities are required');
|
|
119
|
-
const result = await (0, columns_2.projectColumns)(type, [column], search, [new
|
|
120
|
+
const result = await (0, columns_2.projectColumns)(type, [column], search, [new storage_1.Identifiers(ids ? ids.split(',') : [])]);
|
|
120
121
|
return this.jsonResponse({
|
|
121
122
|
label: column.label, path, sortable: column.sortable,
|
|
122
123
|
rows: await Promise.all(result.rows.map(async (row) => ({
|
|
@@ -125,7 +126,7 @@ let List = (() => {
|
|
|
125
126
|
});
|
|
126
127
|
}
|
|
127
128
|
catch (error) {
|
|
128
|
-
const message = error instanceof
|
|
129
|
+
const message = error instanceof storage_2.SelectionError && error.code === 'multiple-values'
|
|
129
130
|
? 'This path has multiple values and cannot be displayed as a column.'
|
|
130
131
|
: error instanceof Error ? error.message : 'Column unavailable';
|
|
131
132
|
return this.jsonResponse({ error: message }, 400);
|
|
@@ -135,19 +136,25 @@ let List = (() => {
|
|
|
135
136
|
if (feed.offset) {
|
|
136
137
|
return this.htmlTemplateResponse({ objects, type }, request, feed.template);
|
|
137
138
|
}
|
|
138
|
-
const count = await (0,
|
|
139
|
+
const count = await (0, storage_3.dataSource)().count(type, search);
|
|
139
140
|
const route = (0, route_2.routeOf)(this);
|
|
140
141
|
const generalActions = [];
|
|
141
142
|
const selectionActions = [];
|
|
142
|
-
for (const action of (0,
|
|
143
|
+
for (const action of (0, action_3.getActions)(type, route.slice(route.lastIndexOf('/') + 1))) {
|
|
143
144
|
const actions = (action.need === 'object') ? selectionActions : generalActions;
|
|
144
145
|
actions.push(action);
|
|
145
146
|
}
|
|
147
|
+
let selectorBootstrap;
|
|
148
|
+
if (access.bootstrap) {
|
|
149
|
+
const state = await (0, selector_state_1.selectorStateBootstrap)(request);
|
|
150
|
+
const metadata = await access.bootstrap([...columns].map(column => column.name), state.record);
|
|
151
|
+
selectorBootstrap = encodeURIComponent(JSON.stringify({ ...state, ...metadata }));
|
|
152
|
+
}
|
|
146
153
|
return this.htmlTemplateResponse({ columns, count, generalActions, objects, root: access.root, search: filter.search,
|
|
147
|
-
selectionActions, selectorKey: (0,
|
|
154
|
+
selectionActions, selectorBootstrap, selectorKey: (0, selector_state_2.selectorStateKey)(request), type }, request, __dirname + '/list.html');
|
|
148
155
|
}
|
|
149
156
|
async json(request, scope) {
|
|
150
|
-
const objects = await (0,
|
|
157
|
+
const objects = await (0, storage_3.dataSource)().search(request.type, scope);
|
|
151
158
|
return this.jsonResponse(objects);
|
|
152
159
|
}
|
|
153
160
|
};
|
package/cjs/list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../src/list.ts"],"names":[],"mappings":"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"}
|
package/cjs/multiple-output.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MultipleOutput = MultipleOutput;
|
|
4
4
|
exports.multipleOutputOf = multipleOutputOf;
|
|
5
|
-
const composition_1 = require("@itrocks/composition");
|
|
6
5
|
const property_1 = require("@itrocks/decorator/property");
|
|
7
6
|
const property_2 = require("@itrocks/decorator/property");
|
|
8
7
|
const MULTIPLE_OUTPUT = Symbol('multiple-output');
|
|
@@ -10,7 +9,6 @@ function MultipleOutput(mode) {
|
|
|
10
9
|
return (0, property_1.decorate)(MULTIPLE_OUTPUT, mode);
|
|
11
10
|
}
|
|
12
11
|
function multipleOutputOf(target, property) {
|
|
13
|
-
return (0, property_2.decoratorOf)(target, property, MULTIPLE_OUTPUT)
|
|
14
|
-
?? ((0, composition_1.componentOf)(target, property) ? 'lines' : 'commas');
|
|
12
|
+
return (0, property_2.decoratorOf)(target, property, MULTIPLE_OUTPUT) ?? 'commas';
|
|
15
13
|
}
|
|
16
14
|
//# sourceMappingURL=multiple-output.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multiple-output.js","sourceRoot":"","sources":["../src/multiple-output.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"multiple-output.js","sourceRoot":"","sources":["../src/multiple-output.ts"],"names":[],"mappings":";;AAQA,wCAGC;AAED,4CAGC;AAfD,0DAA0D;AAC1D,0DAA0D;AAI1D,MAAM,eAAe,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAA;AAEjD,SAAgB,cAAc,CAAmB,IAAwB;IAExE,OAAO,IAAA,mBAAQ,EAAI,eAAe,EAAE,IAAI,CAAC,CAAA;AAC1C,CAAC;AAED,SAAgB,gBAAgB,CAAmB,MAAuB,EAAE,QAAiB;IAE5F,OAAO,IAAA,sBAAW,EAAoC,MAAM,EAAE,QAAQ,EAAE,eAAe,CAAC,IAAI,QAAQ,CAAA;AACrG,CAAC"}
|
package/cjs/projection.d.ts
CHANGED
package/cjs/projection.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.listRepresentationDependsOn = listRepresentationDependsOn;
|
|
|
5
5
|
exports.representationFields = representationFields;
|
|
6
6
|
const class_view_1 = require("@itrocks/class-view");
|
|
7
7
|
const class_view_2 = require("@itrocks/class-view");
|
|
8
|
+
const class_view_3 = require("@itrocks/class-view");
|
|
8
9
|
const composition_1 = require("@itrocks/composition");
|
|
9
10
|
const core_transformers_1 = require("@itrocks/core-transformers");
|
|
10
11
|
const core_transformers_2 = require("@itrocks/core-transformers");
|
|
@@ -29,7 +30,7 @@ function representationFields(type, ancestors = [], property) {
|
|
|
29
30
|
const fields = dependencies.fields?.(type, branch, property);
|
|
30
31
|
if (!fields && property && (0, transformer_4.resolveTransformer)(property.class.type, property.name, transformer_2.HTML, transformer_3.OUTPUT) !== core_transformers_2.storeOutput)
|
|
31
32
|
throw new storage_1.SelectionError('unsupported-selection', 'Undeclared custom object representation');
|
|
32
|
-
return (fields ?? (0,
|
|
33
|
+
return (fields ?? (0, class_view_2.representativeOf)(type).map(String)).flatMap(path => {
|
|
33
34
|
const resolved = (0, reflect_path_2.resolvePropertyPath)(type, path);
|
|
34
35
|
if (resolved.targetMultiplicity !== 'one')
|
|
35
36
|
throw new storage_1.SelectionError('unsupported-selection', path);
|
|
@@ -46,6 +47,7 @@ class ListColumn {
|
|
|
46
47
|
multiple;
|
|
47
48
|
property;
|
|
48
49
|
sortable;
|
|
50
|
+
target;
|
|
49
51
|
reverse = false;
|
|
50
52
|
search = '';
|
|
51
53
|
sort;
|
|
@@ -60,6 +62,7 @@ class ListColumn {
|
|
|
60
62
|
throw new storage_1.SelectionError(resolved.targetMultiplicity === 'many' ? 'multiple-values' : 'unsupported-selection', name);
|
|
61
63
|
}
|
|
62
64
|
this.property = new reflect_path_1.ReflectPropertyPath(root, name);
|
|
65
|
+
this.target = resolved.target;
|
|
63
66
|
this.label = resolved.segments.map(property => (0, translate_1.tr)((0, property_view_1.displayOf)(property.class.type, property.name))).join('.');
|
|
64
67
|
if (resolved.target && !this.multiple)
|
|
65
68
|
this.fields = representationFields(resolved.target, [], resolved.property);
|
|
@@ -71,11 +74,18 @@ class ListColumn {
|
|
|
71
74
|
return '<span class="unavailable">Unavailable</span>';
|
|
72
75
|
if (this.fields && this.fields.some(field => !cell.loadedFields?.includes(field)))
|
|
73
76
|
throw new storage_1.SelectionError('unsupported-selection', this.name);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
if (this.multiple && (0, composition_1.componentOf)(this.property.class.type, this.property.name)) {
|
|
78
|
+
const values = cell.value;
|
|
79
|
+
if (values.length >= 9) {
|
|
80
|
+
const name = this.target
|
|
81
|
+
? (0, class_view_1.displayOf)(this.target)
|
|
82
|
+
: (0, property_view_1.displayOf)(this.property.class.type, this.property.name);
|
|
83
|
+
const plural = name.endsWith('y') ? name.slice(0, -1) + 'ies' : name + 's';
|
|
84
|
+
return `${values.length} ${(0, translate_1.tr)(plural)}`;
|
|
85
|
+
}
|
|
86
|
+
return (await Promise.all(values.map(class_view_3.representativeValueOf))).sort().join(', ');
|
|
78
87
|
}
|
|
88
|
+
const mode = this.multiple ? (0, multiple_output_1.multipleOutputOf)(this.property.class.type, this.property.name) : undefined;
|
|
79
89
|
const lines = this.multiple && (mode === 'lines');
|
|
80
90
|
return await (0, transformer_1.applyTransformer)(cell.value, this.property.class.type, this.property.name, transformer_2.HTML, transformer_3.OUTPUT, new core_transformers_1.HtmlContainer(false, !this.multiple || lines));
|
|
81
91
|
}
|
package/cjs/projection.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../src/projection.ts"],"names":[],"mappings":"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"}
|
package/cjs/selector-state.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export declare function listStateDependsOn(dependencies: {
|
|
|
8
8
|
}): void;
|
|
9
9
|
/** Keep all router-resolved identity segments; format and query are presentation parameters. */
|
|
10
10
|
export declare function selectorStateKey(request: Request): string;
|
|
11
|
+
export declare function selectorStateBootstrap(request: Request): Promise<{
|
|
12
|
+
generation: string;
|
|
13
|
+
record: StateRecord;
|
|
14
|
+
}>;
|
|
11
15
|
export declare class SessionStateStore {
|
|
12
16
|
private readonly session;
|
|
13
17
|
private readonly run;
|
package/cjs/selector-state.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SelectorStateAction = exports.SessionStateStore = void 0;
|
|
4
4
|
exports.listStateDependsOn = listStateDependsOn;
|
|
5
5
|
exports.selectorStateKey = selectorStateKey;
|
|
6
|
+
exports.selectorStateBootstrap = selectorStateBootstrap;
|
|
6
7
|
const action_1 = require("@itrocks/action");
|
|
7
8
|
const action_request_1 = require("@itrocks/action-request");
|
|
8
9
|
const property_select_1 = require("@itrocks/property-select");
|
|
@@ -18,6 +19,15 @@ function listStateDependsOn(dependencies) {
|
|
|
18
19
|
function selectorStateKey(request) {
|
|
19
20
|
return request.route + (request.ids.length ? '/' + request.ids.join(',') : '') + '/' + request.action;
|
|
20
21
|
}
|
|
22
|
+
function selectorStateBootstrap(request) {
|
|
23
|
+
return transaction(request.request.session, data => {
|
|
24
|
+
data.propertySelectGeneration ??= (0, node_crypto_1.randomUUID)();
|
|
25
|
+
return {
|
|
26
|
+
generation: data.propertySelectGeneration,
|
|
27
|
+
record: structuredClone(data.propertySelect?.[selectorStateKey(request)] ?? { revision: 0 })
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
21
31
|
class SessionStateStore {
|
|
22
32
|
session;
|
|
23
33
|
run;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selector-state.js","sourceRoot":"","sources":["../src/selector-state.ts"],"names":[],"mappings":";;;AAgBA,gDAGC;AAGD,4CAGC;
|
|
1
|
+
{"version":3,"file":"selector-state.js","sourceRoot":"","sources":["../src/selector-state.ts"],"names":[],"mappings":"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"}
|
package/list.css
CHANGED
|
@@ -41,7 +41,7 @@ table.objects tr td, table.objects tr th {
|
|
|
41
41
|
padding: 0.6em;
|
|
42
42
|
white-space: nowrap;
|
|
43
43
|
}
|
|
44
|
-
table.objects tr td:last-child, table.objects tr th:last-child {
|
|
44
|
+
table.objects tr td:nth-last-child(1 of :not([hidden])), table.objects tr th:nth-last-child(1 of :not([hidden])) {
|
|
45
45
|
border-right: 1px solid #d8d8d8;
|
|
46
46
|
}
|
|
47
47
|
table.objects tr.properties > .select > input[name=select] {
|
|
@@ -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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PropertySelectView } from '@itrocks/property-select/front/property-select.js';
|
|
2
2
|
import type { ConsumerPort } from '@itrocks/property-select/property-select.js';
|
|
3
3
|
import type { MetadataProvider } from '@itrocks/property-select/property-select.js';
|
|
4
|
-
import type { PointerSample } from '@itrocks/property-select/property-select.js';
|
|
5
4
|
import type { Outcome } from '@itrocks/property-select/property-select.js';
|
|
5
|
+
import type { PointerSample } from '@itrocks/property-select/property-select.js';
|
|
6
6
|
import type { SelectionIntent } from '@itrocks/property-select/property-select.js';
|
|
7
7
|
import { TableColumnAdd } from '@itrocks/table/column-add.js';
|
|
8
8
|
export type PaneHandle = {
|
|
@@ -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,22 +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;
|
|
51
|
+
private columnSource;
|
|
46
52
|
cancel(id: string): void;
|
|
47
|
-
private
|
|
53
|
+
private displayedPaths;
|
|
48
54
|
dispose(): void;
|
|
49
55
|
end(id: string, sample: PointerSample): Promise<Outcome>;
|
|
50
56
|
mount(): Promise<boolean>;
|
|
51
57
|
move(id: string, sample: PointerSample): void;
|
|
52
58
|
paths(): string[];
|
|
59
|
+
private remove;
|
|
53
60
|
select(intent: SelectionIntent, signal: AbortSignal): Promise<Outcome>;
|
|
54
61
|
}
|
package/property-select.js
CHANGED
|
@@ -6,23 +6,36 @@ 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
|
-
|
|
24
|
+
const initial = article.dataset.selectorBootstrap
|
|
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
|
+
});
|
|
21
32
|
this.view = new PropertySelectView(content, {
|
|
22
33
|
consumer: this, metadata: options.metadata, root: article.dataset.propertyRoot,
|
|
23
34
|
advice: options.advice, stateKey: article.dataset.selectorKey, stateMode: 'session',
|
|
24
|
-
stateStore: new HttpStateStore(
|
|
25
|
-
|
|
35
|
+
stateStore: new HttpStateStore(undefined, undefined, initial && {
|
|
36
|
+
...initial, key: article.dataset.selectorKey
|
|
37
|
+
}), translate, usedPaths: this.paths()
|
|
38
|
+
}, { template: initial?.template, templateUrl: options.templateUrl });
|
|
26
39
|
this.observer = new MutationObserver(records => {
|
|
27
40
|
if (this.drag && records.some(record => record.target.tagName === 'TBODY'))
|
|
28
41
|
this.view.selector.cancel(this.drag.intent.id, 'row view changed');
|
|
@@ -37,10 +50,11 @@ export class ListPropertySelect {
|
|
|
37
50
|
const table = this.article.querySelector('table.objects');
|
|
38
51
|
const rows = [...table.tBodies[0].rows];
|
|
39
52
|
const ids = rows.map(row => row.querySelector('input[name=id]').value);
|
|
40
|
-
const query = new URLSearchParams({ column: path, rowIds: ids.join(',') });
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
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 });
|
|
44
58
|
const result = await response.json();
|
|
45
59
|
if (!response.ok)
|
|
46
60
|
throw new Error(result.error ?? 'Column unavailable');
|
|
@@ -60,6 +74,16 @@ export class ListPropertySelect {
|
|
|
60
74
|
input.name = `search.${encodeURIComponent(path).replaceAll('.', '%2E')}`;
|
|
61
75
|
input.dataset.propertyFilter = path;
|
|
62
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
|
+
});
|
|
63
87
|
header.append(result.label, input);
|
|
64
88
|
const cells = ids.map(id => {
|
|
65
89
|
const cell = document.createElement('td');
|
|
@@ -68,70 +92,94 @@ export class ListPropertySelect {
|
|
|
68
92
|
});
|
|
69
93
|
return { cells, header };
|
|
70
94
|
}
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
add(path, signal, options = {}) {
|
|
96
|
+
const source = this.columnSource(path);
|
|
97
|
+
const operation = this.options.addition.begin(source, options);
|
|
98
|
+
try {
|
|
99
|
+
signal.throwIfAborted();
|
|
100
|
+
if (this.disposed)
|
|
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
|
+
}
|
|
115
|
+
const outcome = operation.commit();
|
|
116
|
+
this.removedColumns.delete(path);
|
|
117
|
+
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
118
|
+
return outcome;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
operation.cancel();
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
77
124
|
}
|
|
78
125
|
begin(intent, sample) {
|
|
79
126
|
if (this.disposed || this.drag || this.pending.size)
|
|
80
127
|
throw new Error('A column operation is pending');
|
|
81
128
|
const path = normalisePath(intent.path);
|
|
82
|
-
const
|
|
83
|
-
const result = this.view.selector.search.results.find(result => result.property.path === path);
|
|
84
|
-
const property = result?.property || this.view.selector.context?.properties.find(property => property.path === path);
|
|
85
|
-
const label = result ? [...result.segments.map(segment => segment.label), result.property.label].join('.')
|
|
86
|
-
: property?.label || path;
|
|
129
|
+
const added = !this.paths().includes(path);
|
|
87
130
|
const suspension = this.pane.suspend({ mobileOnly: true });
|
|
88
131
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const query = new URLSearchParams({ column: path, validate: '1' });
|
|
95
|
-
const response = await fetch(`${form.action}?${query}`, {
|
|
96
|
-
signal: AbortSignal.any([signal, this.abort.signal])
|
|
97
|
-
});
|
|
98
|
-
const result = await response.json();
|
|
99
|
-
if (!response.ok || result.path !== path)
|
|
100
|
-
throw new Error(result.error ?? 'Column unavailable');
|
|
101
|
-
if (provisionalHeader?.isConnected && result.label)
|
|
102
|
-
provisionalHeader.textContent = result.label;
|
|
103
|
-
},
|
|
104
|
-
create: () => {
|
|
105
|
-
const header = document.createElement('th');
|
|
106
|
-
provisionalHeader = header;
|
|
107
|
-
header.dataset.property = path;
|
|
108
|
-
header.dataset.sortable = 'false';
|
|
109
|
-
header.textContent = label;
|
|
110
|
-
const cells = [...this.article.querySelectorAll('table.objects tbody tr')].map(() => {
|
|
111
|
-
const cell = document.createElement('td');
|
|
112
|
-
cell.textContent = 'Loading…';
|
|
113
|
-
return cell;
|
|
114
|
-
});
|
|
115
|
-
return { cells, header };
|
|
116
|
-
},
|
|
117
|
-
load: async (signal) => {
|
|
118
|
-
try {
|
|
119
|
-
return await this.loadColumn(path, signal);
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
if (!this.disposed && !signal.aborted)
|
|
123
|
-
this.previewError(path);
|
|
124
|
-
throw error;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}, sample);
|
|
128
|
-
this.drag = { 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() };
|
|
129
137
|
}
|
|
130
138
|
catch (error) {
|
|
131
139
|
suspension.restore();
|
|
132
140
|
throw error;
|
|
133
141
|
}
|
|
134
142
|
}
|
|
143
|
+
columnSource(path) {
|
|
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('.');
|
|
155
|
+
return {
|
|
156
|
+
id: path,
|
|
157
|
+
create: () => {
|
|
158
|
+
const header = document.createElement('th');
|
|
159
|
+
header.dataset.property = path;
|
|
160
|
+
header.dataset.sortable = 'false';
|
|
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);
|
|
167
|
+
const cells = [...this.article.querySelectorAll('table.objects tbody tr')]
|
|
168
|
+
.map(() => {
|
|
169
|
+
const cell = document.createElement('td');
|
|
170
|
+
cell.textContent = '\u00a0';
|
|
171
|
+
return cell;
|
|
172
|
+
});
|
|
173
|
+
return { cells, header };
|
|
174
|
+
},
|
|
175
|
+
load: async (signal) => {
|
|
176
|
+
// Commit the placeholder before reading the column configuration.
|
|
177
|
+
await Promise.resolve();
|
|
178
|
+
signal.throwIfAborted();
|
|
179
|
+
return this.loadColumn(path, signal);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
135
183
|
cancel(id) {
|
|
136
184
|
if (this.drag?.intent.id !== id)
|
|
137
185
|
return;
|
|
@@ -141,24 +189,10 @@ export class ListPropertySelect {
|
|
|
141
189
|
drag.restore();
|
|
142
190
|
this.view.selector.setConfiguration({ usedPaths: this.paths() });
|
|
143
191
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.
|
|
147
|
-
|
|
148
|
-
return;
|
|
149
|
-
for (const row of this.article.querySelectorAll('tbody tr'))
|
|
150
|
-
row.cells[header.cellIndex].textContent = 'Unable to load values';
|
|
151
|
-
if (header.querySelector('.column-retry'))
|
|
152
|
-
return;
|
|
153
|
-
const retry = document.createElement('button');
|
|
154
|
-
retry.type = 'button';
|
|
155
|
-
retry.className = 'column-retry';
|
|
156
|
-
retry.textContent = 'Retry values';
|
|
157
|
-
retry.addEventListener('click', event => {
|
|
158
|
-
event.stopPropagation();
|
|
159
|
-
void this.options.addition.retry(path);
|
|
160
|
-
}, { signal: this.abort.signal });
|
|
161
|
-
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);
|
|
162
196
|
}
|
|
163
197
|
dispose() {
|
|
164
198
|
if (this.disposed)
|
|
@@ -168,20 +202,30 @@ export class ListPropertySelect {
|
|
|
168
202
|
this.disposed = true;
|
|
169
203
|
this.abort.abort();
|
|
170
204
|
this.observer.disconnect();
|
|
205
|
+
this.unsubscribePane();
|
|
206
|
+
this.removedColumns.clear();
|
|
171
207
|
this.view.dispose();
|
|
172
208
|
this.pane.remove();
|
|
173
209
|
this.options.addition.dispose();
|
|
174
210
|
this.button.hidden = true;
|
|
175
211
|
this.pending.clear();
|
|
212
|
+
for (const removal of this.removals.values())
|
|
213
|
+
removal.abort();
|
|
214
|
+
this.removals.clear();
|
|
176
215
|
}
|
|
177
216
|
async end(id, sample) {
|
|
178
217
|
const drag = this.drag;
|
|
179
218
|
if (!drag || drag.intent.id !== id)
|
|
180
219
|
throw new Error('Unknown column transport');
|
|
181
220
|
try {
|
|
182
|
-
|
|
183
|
-
if (status !== 'cancelled')
|
|
184
|
-
|
|
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
|
+
}
|
|
185
229
|
if (this.disposed)
|
|
186
230
|
return { ...drag.intent, status: 'cancelled' };
|
|
187
231
|
if (this.drag === drag)
|
|
@@ -210,10 +254,42 @@ export class ListPropertySelect {
|
|
|
210
254
|
this.drag.operation.move(sample);
|
|
211
255
|
}
|
|
212
256
|
paths() {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
}
|
|
217
293
|
}
|
|
218
294
|
async select(intent, signal) {
|
|
219
295
|
const path = normalisePath(intent.path);
|
|
@@ -223,13 +299,27 @@ export class ListPropertySelect {
|
|
|
223
299
|
let status;
|
|
224
300
|
const pending = this.pending.get(path);
|
|
225
301
|
if (pending) {
|
|
226
|
-
|
|
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);
|
|
227
314
|
status = 'existing';
|
|
228
315
|
}
|
|
229
|
-
else if (this.paths().includes(path))
|
|
230
|
-
status = 'existing';
|
|
231
316
|
else {
|
|
232
|
-
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));
|
|
233
323
|
this.pending.set(path, operation);
|
|
234
324
|
try {
|
|
235
325
|
status = await operation;
|
|
@@ -242,8 +332,11 @@ export class ListPropertySelect {
|
|
|
242
332
|
signal.throwIfAborted();
|
|
243
333
|
if (this.disposed)
|
|
244
334
|
throw new Error('List disposed');
|
|
245
|
-
this.
|
|
246
|
-
|
|
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
|
+
}
|
|
247
340
|
return { ...intent, status };
|
|
248
341
|
}
|
|
249
342
|
catch (error) {
|
package/selector-state.d.ts
CHANGED
|
@@ -5,8 +5,13 @@ import type { StateStore } from '@itrocks/property-select/property-select.js';
|
|
|
5
5
|
export declare class HttpStateStore implements StateStore {
|
|
6
6
|
private readonly endpoint;
|
|
7
7
|
private readonly fetcher;
|
|
8
|
+
private initial?;
|
|
8
9
|
private generation?;
|
|
9
|
-
constructor(endpoint?: string, fetcher?: typeof fetch
|
|
10
|
+
constructor(endpoint?: string, fetcher?: typeof fetch, initial?: {
|
|
11
|
+
generation: string;
|
|
12
|
+
key: string;
|
|
13
|
+
record: StateRecord;
|
|
14
|
+
} | undefined);
|
|
10
15
|
read(key: string, signal?: AbortSignal): Promise<StateRecord>;
|
|
11
16
|
remove(key: string, revision: number): Promise<StateRecord>;
|
|
12
17
|
write(key: string, state: SelectorState, revision: number): Promise<StateRecord>;
|
package/selector-state.js
CHANGED
|
@@ -3,12 +3,21 @@ import { StateConflictError } from '@itrocks/property-select/property-select.js'
|
|
|
3
3
|
export class HttpStateStore {
|
|
4
4
|
endpoint;
|
|
5
5
|
fetcher;
|
|
6
|
+
initial;
|
|
6
7
|
generation;
|
|
7
|
-
constructor(endpoint = '/property-select/state/json', fetcher = globalThis.fetch.bind(globalThis)) {
|
|
8
|
+
constructor(endpoint = '/property-select/state/json', fetcher = globalThis.fetch.bind(globalThis), initial) {
|
|
8
9
|
this.endpoint = endpoint;
|
|
9
10
|
this.fetcher = fetcher;
|
|
11
|
+
this.initial = initial;
|
|
10
12
|
}
|
|
11
13
|
read(key, signal) {
|
|
14
|
+
if (this.initial?.key === key) {
|
|
15
|
+
signal?.throwIfAborted();
|
|
16
|
+
const initial = this.initial;
|
|
17
|
+
this.initial = undefined;
|
|
18
|
+
this.generation = initial.generation;
|
|
19
|
+
return Promise.resolve(structuredClone(initial.record));
|
|
20
|
+
}
|
|
12
21
|
return this.request('GET', key, undefined, undefined, signal);
|
|
13
22
|
}
|
|
14
23
|
remove(key, revision) {
|