@lavalogic/scoria 0.20.0 → 0.21.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.
@@ -2,7 +2,7 @@ import type { CellCoordinates } from '../Coordinates/CellCoordinates.js';
2
2
  import type { ToolbarPosition } from '../Toolbar/ToolbarPosition.js';
3
3
  export type Primitive = string | number;
4
4
  export interface TableInitOptions<T extends object, Identifier extends Primitive> {
5
- selectionExtractor: (item: T) => Promise<Identifier>;
5
+ selectionExtractor: (item: T) => Identifier | Promise<Identifier>;
6
6
  allowCopy?: boolean;
7
7
  enableResize?: boolean;
8
8
  allowColumnReordering?: boolean;
@@ -1,4 +1,3 @@
1
- import { devCatch } from '../../../../Helpers/Helpers.svelte.js';
2
1
  import { Colour } from '../../../../scss/colours.js';
3
2
  import { Validity } from '../../../../Types/Internal/Validity.js';
4
3
  export class TableFocusDetails {
@@ -12,25 +11,9 @@ export class TableFocusDetails {
12
11
  column: this.cell.column.columnDef.index,
13
12
  id: this.cell.column.columnDef.id,
14
13
  });
15
- $effect(() => {
16
- void this.tableContext.focusedColumnIds;
17
- void this.tableContext.focusedRowIndices;
18
- (async () => {
19
- this.isMultiFocused =
20
- (await this.tableContext.focusedColumnIds).has(this.cell.column.columnDef.id) &&
21
- (await this.tableContext.focusedRowIndices).has(this.cell.row.visibleIndex);
22
- })().catch(devCatch);
23
- });
24
- $effect(() => {
25
- void this.isMultiFocused;
26
- void this.tableContext.focusedColumnIds;
27
- void this.tableContext.focusedRowIndices;
28
- (async () => {
29
- this.hasMultipleFocus =
30
- (await this.tableContext.focusedColumnIds).size > 1 ||
31
- (await this.tableContext.focusedRowIndices).size > 1;
32
- })().catch(devCatch);
33
- });
14
+ this.isMultiFocused = $derived(this.tableContext.focusedColumnIds.has(this.cell.column.columnDef.id) &&
15
+ this.tableContext.focusedRowIndices.has(this.cell.row.visibleIndex));
16
+ this.hasMultipleFocus = $derived(this.tableContext.focusedColumnIds.size > 1 || this.tableContext.focusedRowIndices.size > 1);
34
17
  this.isMultiFocusStart = $derived.by(() => {
35
18
  void this.hasMultipleFocus;
36
19
  return (this.hasMultipleFocus &&
@@ -50,8 +33,8 @@ export class TableFocusDetails {
50
33
  });
51
34
  }
52
35
  coordinates;
53
- isMultiFocused = $state(false);
54
- hasMultipleFocus = $state(false);
36
+ isMultiFocused;
37
+ hasMultipleFocus;
55
38
  isMultiFocusStart;
56
39
  topToBottom;
57
40
  leftToRight;
@@ -38,14 +38,14 @@ export declare function isValidEmailAddress(emailAddress: string): RegExpMatchAr
38
38
  export declare function gridItem(element: HTMLElement): (() => void) | undefined;
39
39
  export type Milliseconds = number;
40
40
  export declare function asyncSleep(delay: Milliseconds): Promise<void>;
41
- export declare function asyncMap<T, R>(array: Array<T>, asyncCallback: (item: T, index: number) => Promise<R>): Promise<Awaited<R>[]>;
42
- export declare function asyncForEach<T>(array: Array<T>, asyncCallback: (item: T, index: number) => Promise<void>): Promise<void>;
43
- export declare function asyncFlatMap<T, R>(array: Array<T>, asyncCallback: (item: T, index: number) => Promise<R>): Promise<(Awaited<R> extends infer T_1 ? T_1 extends Awaited<R> ? T_1 extends readonly (infer InnerArr)[] ? InnerArr : T_1 : never : never)[]>;
44
- export declare function asyncSome<T>(array: Array<T>, asyncCallback: (item: T, index: number) => Promise<boolean>): Promise<boolean>;
45
- export declare function asyncAll<T>(array: Array<T>, asyncCallback: (item: T, index: number) => Promise<boolean>): Promise<boolean>;
41
+ export declare function asyncMap<T, R>(array: Array<T>, callback: (item: T, index: number) => R | Promise<R>): Array<R> | Promise<Array<R>>;
42
+ export declare function asyncForEach<T>(array: Array<T>, callback: (item: T, index: number) => void | Promise<void>): void | Promise<void>;
43
+ export declare function asyncFlatMap<T, R>(array: Array<T>, callback: (item: T, index: number) => Array<R> | Promise<Array<R>>): Array<R> | Promise<Array<R>>;
44
+ export declare function asyncSome<T>(array: Array<T>, callback: (item: T, index: number) => boolean | Promise<boolean>): boolean | Promise<boolean>;
45
+ export declare function asyncAll<T>(array: Array<T>, callback: (item: T, index: number) => boolean | Promise<boolean>): boolean | Promise<boolean>;
46
46
  export declare const asyncEvery: typeof asyncAll;
47
- export declare function asyncFilterIndices<T>(array: Array<T>, asyncCallback: (item: T, index: number) => Promise<boolean>): Promise<Array<number>>;
48
- export declare function asyncFilter<T>(array: Array<T>, asyncCallback: (item: T, index: number) => Promise<boolean>): Promise<Array<T>>;
47
+ export declare function asyncFilterIndices<T>(array: Array<T>, callback: (item: T, index: number) => boolean | Promise<boolean>): Array<number> | Promise<Array<number>>;
48
+ export declare function asyncFilter<T>(array: Array<T>, callback: (item: T, index: number) => boolean | Promise<boolean>): Array<T> | Promise<Array<T>>;
49
49
  export declare function devCatch(e: unknown): void;
50
50
  export declare function hasQuery<FilterValueType>(customFilter: CustomFilter<FilterValueType> | undefined): customFilter is CustomFilter<FilterValueType> & Required<Pick<CustomFilter<FilterValueType>, 'query'>>;
51
51
  export declare function isDateTuple(value: unknown): value is [Date | null, Date | null];
@@ -191,56 +191,137 @@ export async function asyncSleep(delay) {
191
191
  }, delay);
192
192
  }));
193
193
  }
194
- export async function asyncMap(array, asyncCallback) {
195
- return Promise.all(array.map(asyncCallback));
194
+ export function asyncMap(array, callback) {
195
+ const results = new Array(array.length);
196
+ const pending = [];
197
+ for (let i = array.length - 1; i > -1; i--) {
198
+ const r = callback(array[i], i);
199
+ if (r instanceof Promise) {
200
+ const idx = i;
201
+ pending.push(r.then((v) => {
202
+ results[idx] = v;
203
+ }));
204
+ }
205
+ else {
206
+ results[i] = r;
207
+ }
208
+ }
209
+ if (pending.length === 0) {
210
+ return results;
211
+ }
212
+ return Promise.all(pending).then(() => results);
196
213
  }
197
- export async function asyncForEach(array, asyncCallback) {
198
- await Promise.all(array.map(asyncCallback));
214
+ export function asyncForEach(array, callback) {
215
+ const pending = [];
216
+ for (let i = array.length - 1; i > -1; i--) {
217
+ const r = callback(array[i], i);
218
+ if (r !== undefined) {
219
+ pending.push(r);
220
+ }
221
+ }
222
+ if (pending.length === 0) {
223
+ return;
224
+ }
225
+ return Promise.all(pending).then(() => { });
199
226
  }
200
- export async function asyncFlatMap(array, asyncCallback) {
201
- return (await Promise.all(array.map(asyncCallback))).flat();
227
+ export function asyncFlatMap(array, callback) {
228
+ const results = new Array(array.length);
229
+ const pending = [];
230
+ for (let i = 0; i < array.length; i++) {
231
+ const r = callback(array[i], i);
232
+ if (r instanceof Promise) {
233
+ const idx = i;
234
+ pending.push(r.then((v) => {
235
+ results[idx] = v;
236
+ }));
237
+ }
238
+ else {
239
+ results[i] = r;
240
+ }
241
+ }
242
+ if (pending.length === 0) {
243
+ return results.flat();
244
+ }
245
+ return Promise.all(pending).then(() => results.flat());
202
246
  }
203
- const emptyPromise = new Promise(() => null);
204
- export async function asyncSome(array, asyncCallback) {
205
- const promises = array.map(asyncCallback);
247
+ export function asyncSome(array, callback) {
248
+ const asyncItems = [];
249
+ for (let i = array.length - 1; i > -1; i--) {
250
+ const r = callback(array[i], i);
251
+ if (r === true) {
252
+ return true;
253
+ }
254
+ if (r instanceof Promise) {
255
+ asyncItems.push({ index: i, promise: r });
256
+ }
257
+ }
258
+ if (asyncItems.length === 0) {
259
+ return false;
260
+ }
261
+ // Fall back to parallel race for async results
262
+ const emptyPromise = new Promise(() => { });
263
+ const promises = asyncItems.map((it) => it.promise);
206
264
  return Promise.race([
207
- Promise.race(
208
- // empty promise is required here
209
- // because otherwise the first false would immediately end the race without checking the rest of the array
210
- promises.map(async (p) => (await p) || emptyPromise)),
265
+ Promise.race(promises.map(async (p) => (await p) || emptyPromise)),
211
266
  Promise.all(promises).then((r) => r.some(Boolean)),
212
267
  ]);
213
268
  }
214
- export async function asyncAll(array, asyncCallback) {
215
- const promises = array.map(asyncCallback);
216
- return promises.length == 0
217
- ? true
218
- : Promise.race([
219
- Promise.race(
220
- // empty promise is required here
221
- // because otherwise the first false would immediately end the race without checking the rest of the array
222
- promises.map(async (p) => (await p) || emptyPromise)),
223
- Promise.all(promises).then((r) => r.every(Boolean)),
224
- ]);
269
+ export function asyncAll(array, callback) {
270
+ if (array.length === 0) {
271
+ return true;
272
+ }
273
+ const asyncItems = [];
274
+ for (let i = array.length - 1; i > -1; i--) {
275
+ const r = callback(array[i], i);
276
+ if (r === false) {
277
+ return false;
278
+ } // sync short-circuit
279
+ if (r instanceof Promise) {
280
+ asyncItems.push({ index: i, promise: r });
281
+ }
282
+ // r === true: continue
283
+ }
284
+ if (asyncItems.length === 0) {
285
+ return true;
286
+ } // all sync and all true
287
+ // Fall back to parallel: short-circuit on first false
288
+ const emptyPromise = new Promise(() => { });
289
+ const promises = asyncItems.map((it) => it.promise);
290
+ return Promise.race([
291
+ // Return false as soon as any resolves to false
292
+ Promise.race(promises.map(async (p) => ((await p) ? emptyPromise : false))),
293
+ Promise.all(promises).then((r) => r.every(Boolean)),
294
+ ]);
225
295
  }
226
296
  export const asyncEvery = asyncAll;
227
- export async function asyncFilterIndices(array, asyncCallback) {
297
+ export function asyncFilterIndices(array, callback) {
228
298
  const filtered = [];
229
- await asyncForEach(array, async (item, index) => {
230
- if (await asyncCallback(item, index)) {
231
- filtered.push(index);
299
+ const pending = [];
300
+ for (let i = array.length - 1; i > -1; i--) {
301
+ const r = callback(array[i], i);
302
+ if (r === true) {
303
+ filtered.push(i);
232
304
  }
233
- });
234
- return filtered.sort((a, b) => a - b);
235
- }
236
- export async function asyncFilter(array, asyncCallback) {
237
- const filtered = [];
238
- await asyncForEach(array, async (item, index) => {
239
- if (await asyncCallback(item, index)) {
240
- filtered.push(index);
305
+ else if (r instanceof Promise) {
306
+ const idx = i;
307
+ pending.push(r.then((v) => {
308
+ if (v) {
309
+ filtered.push(idx);
310
+ }
311
+ }));
241
312
  }
242
- });
243
- return filtered.sort((a, b) => a - b).map((i) => array[i]);
313
+ }
314
+ if (pending.length === 0) {
315
+ return filtered.sort((a, b) => a - b);
316
+ }
317
+ return Promise.all(pending).then(() => filtered.sort((a, b) => a - b));
318
+ }
319
+ export function asyncFilter(array, callback) {
320
+ const filtered = asyncFilterIndices(array, callback);
321
+ if (Array.isArray(filtered)) {
322
+ return filtered.sort((a, b) => a - b).map((i) => array[i]);
323
+ }
324
+ return filtered.then((f) => f.sort((a, b) => a - b).map((i) => array[i]));
244
325
  }
245
326
  export function devCatch(e) {
246
327
  if (dev) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lavalogic/scoria",
3
3
  "description": "Svelte components used for the FloWMS Web Frontend",
4
- "version": "0.20.0",
4
+ "version": "0.21.0",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },
@@ -33,24 +33,24 @@
33
33
  "@eslint/compat": "^2.0.3",
34
34
  "@eslint/js": "^10.0.1",
35
35
  "@playwright/test": "^1.58.2",
36
- "@storybook/addon-a11y": "^10.2.17",
37
- "@storybook/addon-docs": "^10.2.17",
36
+ "@storybook/addon-a11y": "^10.2.19",
37
+ "@storybook/addon-docs": "^10.2.19",
38
38
  "@storybook/addon-svelte-csf": "^5.0.11",
39
- "@storybook/addon-vitest": "^10.2.17",
40
- "@storybook/sveltekit": "^10.2.17",
39
+ "@storybook/addon-vitest": "^10.2.19",
40
+ "@storybook/sveltekit": "^10.2.19",
41
41
  "@sveltejs/adapter-node": "^5.5.4",
42
- "@sveltejs/kit": "^2.54.0",
42
+ "@sveltejs/kit": "^2.55.0",
43
43
  "@sveltejs/package": "^2.5.7",
44
44
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
45
45
  "@types/eslint": "^9.6.1",
46
- "@types/node": "^25.4.0",
46
+ "@types/node": "^25.5.0",
47
47
  "@typescript-eslint/eslint-plugin": "^8.57.0",
48
48
  "@typescript-eslint/parser": "^8.57.0",
49
49
  "@vitest/browser": "^4.1.0",
50
50
  "@vitest/browser-playwright": "^4.1.0",
51
51
  "eslint": "^10.0.3",
52
52
  "eslint-config-prettier": "^10.1.8",
53
- "eslint-plugin-storybook": "^10.2.17",
53
+ "eslint-plugin-storybook": "^10.2.19",
54
54
  "eslint-plugin-svelte": "^3.15.2",
55
55
  "globals": "^17.4.0",
56
56
  "mdsvex": "^0.12.7",
@@ -59,8 +59,8 @@
59
59
  "prettier-plugin-svelte": "^3.5.1",
60
60
  "publint": "^0.3.18",
61
61
  "sass-embedded": "^1.98.0",
62
- "storybook": "^10.2.17",
63
- "svelte": "^5.53.11",
62
+ "storybook": "^10.2.19",
63
+ "svelte": "^5.53.12",
64
64
  "svelte-check": "^4.4.5",
65
65
  "svelte-render-scan": "^1.1.0",
66
66
  "typescript": "^5.9.3",
@@ -68,7 +68,7 @@
68
68
  "vite": "^7.3.1",
69
69
  "vite-plugin-devtools-json": "^1.0.0",
70
70
  "vitest": "^4.1.0",
71
- "vitest-browser-svelte": "^2.0.2"
71
+ "vitest-browser-svelte": "^2.1.0"
72
72
  },
73
73
  "keywords": [
74
74
  "svelte"
@@ -86,8 +86,7 @@
86
86
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
87
87
  "format": "prettier --write .",
88
88
  "lint": "prettier --check . && eslint .",
89
- "test:unit": "vitest",
90
- "test": "npm run test:unit -- --run && npm run test:e2e",
89
+ "test": "vitest run --project server",
91
90
  "test:e2e": "playwright test",
92
91
  "storybook": "storybook dev -p 6006",
93
92
  "build-storybook": "storybook build"