@lavalogic/scoria 0.40.32 → 0.40.33

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.
Files changed (135) hide show
  1. package/README.md +58 -58
  2. package/dist/Components/AccordionGroup.svelte +28 -28
  3. package/dist/Components/AccordionGroupButton.svelte +60 -60
  4. package/dist/Components/Action.svelte +48 -48
  5. package/dist/Components/AlertModal.svelte +102 -102
  6. package/dist/Components/Base/ContextWrapper.svelte +45 -45
  7. package/dist/Components/Base/Pagination.svelte +135 -135
  8. package/dist/Components/Base/Snippets.svelte +25 -25
  9. package/dist/Components/BigRadioSet.svelte +34 -34
  10. package/dist/Components/Bubble.svelte +38 -38
  11. package/dist/Components/Button.svelte +83 -83
  12. package/dist/Components/Checkbox.svelte +71 -71
  13. package/dist/Components/CollapsibleCard.svelte +59 -59
  14. package/dist/Components/CollapsibleMenuGroup.svelte +43 -43
  15. package/dist/Components/ConditionBuilder/ConditionBuilder.svelte +101 -101
  16. package/dist/Components/Contexts/ContextMenu.svelte +76 -76
  17. package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -15
  18. package/dist/Components/Contexts/ContextMenuOption.svelte +38 -38
  19. package/dist/Components/Contexts/Toast.svelte +71 -71
  20. package/dist/Components/Contexts/ToastContainer.svelte +34 -34
  21. package/dist/Components/Contexts/Tooltip.svelte +32 -32
  22. package/dist/Components/DataMatrixIcon.svelte +37 -37
  23. package/dist/Components/DateInput.svelte +80 -80
  24. package/dist/Components/DatePicker.svelte +86 -86
  25. package/dist/Components/DesktopModal.svelte +65 -65
  26. package/dist/Components/Dial.svelte +35 -35
  27. package/dist/Components/DimensionInput.svelte +95 -95
  28. package/dist/Components/DragMenu/DragMenuHolder.svelte +53 -53
  29. package/dist/Components/DragMenu/DraggableCard.svelte +49 -49
  30. package/dist/Components/FileCard.svelte +18 -18
  31. package/dist/Components/FileUpload.svelte +115 -115
  32. package/dist/Components/GridInput.svelte +123 -123
  33. package/dist/Components/GridInputSet.svelte +21 -21
  34. package/dist/Components/HorizontalTabGroup.svelte +54 -54
  35. package/dist/Components/HorizontalTabGroupButton.svelte +32 -32
  36. package/dist/Components/Icon.svelte +68 -68
  37. package/dist/Components/InfoAlert.svelte +32 -32
  38. package/dist/Components/LoadingAnimation.svelte +228 -227
  39. package/dist/Components/LoadingModal.svelte +19 -19
  40. package/dist/Components/LoadingOverlay.svelte +20 -20
  41. package/dist/Components/Modal.svelte +127 -127
  42. package/dist/Components/MultiSelect.svelte +126 -126
  43. package/dist/Components/Nav/Nav.svelte +71 -71
  44. package/dist/Components/Nav/NavServiceMenuList.svelte +37 -37
  45. package/dist/Components/Nav/NavTab.svelte +46 -46
  46. package/dist/Components/Nav/ServiceMenuItem.svelte +62 -62
  47. package/dist/Components/Nav/ServicesNav.svelte +50 -50
  48. package/dist/Components/NumberInput.svelte +98 -98
  49. package/dist/Components/OptionCards.svelte +58 -58
  50. package/dist/Components/PageHeading.svelte +21 -21
  51. package/dist/Components/PasswordInput.svelte +61 -61
  52. package/dist/Components/PopoverButton.svelte +53 -53
  53. package/dist/Components/Portal.svelte +7 -7
  54. package/dist/Components/ProgressBubble.svelte +42 -42
  55. package/dist/Components/QuerySelect.svelte +102 -102
  56. package/dist/Components/ResourceCalendar/ResourceCalendar.svelte +241 -241
  57. package/dist/Components/SidebarPanel.svelte +32 -32
  58. package/dist/Components/SingleSelect.svelte +122 -122
  59. package/dist/Components/StepButton.svelte +48 -48
  60. package/dist/Components/StepForm.svelte +58 -58
  61. package/dist/Components/Switch.svelte +43 -43
  62. package/dist/Components/Table/ARCHITECTURE.md +298 -298
  63. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +30 -30
  64. package/dist/Components/Table/Body/Rows/ColumnList.svelte +24 -24
  65. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +165 -165
  66. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +13 -13
  67. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +120 -120
  68. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +81 -81
  69. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +92 -92
  70. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +110 -110
  71. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +54 -54
  72. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +35 -35
  73. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +64 -64
  74. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +82 -82
  75. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +88 -88
  76. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +90 -90
  77. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +96 -96
  78. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +71 -71
  79. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +124 -124
  80. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +91 -91
  81. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +88 -88
  82. package/dist/Components/Table/Body/Rows/TableRow.svelte +127 -127
  83. package/dist/Components/Table/Body/TableBody.svelte +92 -92
  84. package/dist/Components/Table/Headers/HighlightAllHeader.svelte +25 -25
  85. package/dist/Components/Table/Headers/SelectAllHeader.svelte +20 -20
  86. package/dist/Components/Table/Headers/TableHead.svelte +175 -175
  87. package/dist/Components/Table/Misc/ColumnPanel.svelte +32 -32
  88. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +29 -29
  89. package/dist/Components/Table/Misc/FilterInput.svelte +326 -326
  90. package/dist/Components/Table/Misc/FilterPanel.svelte +79 -79
  91. package/dist/Components/Table/Misc/FilterPanelModal.svelte +21 -21
  92. package/dist/Components/Table/Misc/ShareViewModal.svelte +118 -118
  93. package/dist/Components/Table/Misc/SidePanel.svelte +24 -24
  94. package/dist/Components/Table/Misc/TableConfigurationModal.svelte +318 -318
  95. package/dist/Components/Table/Misc/TableFooter.svelte +201 -201
  96. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +112 -112
  97. package/dist/Components/Table/Misc/TableSidebar.svelte +116 -116
  98. package/dist/Components/Table/Misc/TableViewDropdown.svelte +25 -25
  99. package/dist/Components/Table/Misc/ToolboxPanel.svelte +60 -60
  100. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +63 -63
  101. package/dist/Components/Table/Misc/UpdateViewOptionsModal.svelte +101 -101
  102. package/dist/Components/Table/NestedTable.svelte +29 -29
  103. package/dist/Components/Table/Table.svelte +140 -140
  104. package/dist/Components/Test/TestContextConsumer.svelte +8 -8
  105. package/dist/Components/TextArea.svelte +92 -90
  106. package/dist/Components/TextInput.svelte +128 -117
  107. package/dist/Components/ThreeStateRadio.svelte +101 -101
  108. package/dist/Components/Touch/BoolCard.svelte +27 -27
  109. package/dist/Components/Touch/DateModal.svelte +98 -98
  110. package/dist/Components/Touch/EditCard.svelte +44 -44
  111. package/dist/Components/Touch/InfoCard.svelte +25 -25
  112. package/dist/Components/Touch/InfoTextCard.svelte +22 -22
  113. package/dist/Components/Touch/ModalExplanation.svelte +13 -13
  114. package/dist/Components/Touch/NumberKeyboard.svelte +81 -81
  115. package/dist/Components/Touch/NumberModal.svelte +49 -49
  116. package/dist/Components/Touch/PlusMinusCard.svelte +40 -40
  117. package/dist/Components/Touch/SelectModal.svelte +86 -86
  118. package/dist/Components/Touch/SummaryCard.svelte +74 -74
  119. package/dist/Components/Touch/TextAreaModal.svelte +53 -53
  120. package/dist/Components/Touch/TextModal.svelte +56 -56
  121. package/dist/Components/Touch/TouchCard.svelte +28 -28
  122. package/dist/Components/Touch/TouchModal.svelte +141 -141
  123. package/dist/Components/Touch/UtilityButton.svelte +63 -63
  124. package/dist/Components/VerticalTabGroup.svelte +38 -38
  125. package/dist/Components/VerticalTabGroupButton.svelte +46 -46
  126. package/dist/Helpers/Datamatrix.d.ts +19 -19
  127. package/dist/Helpers/Datamatrix.js +291 -291
  128. package/dist/Types/Examples/ExampleModal.svelte +27 -27
  129. package/dist/paraglide/README.md +1 -1
  130. package/dist/scss/_basics.scss +12 -12
  131. package/dist/scss/_colours.scss +140 -140
  132. package/dist/scss/_motion.scss +57 -57
  133. package/dist/scss/_sizing.scss +85 -85
  134. package/dist/scss/_transitions.scss +8 -8
  135. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
- <svelte:options runes />
2
-
3
- <script
4
- lang="ts"
5
- generics="T extends object, IdType extends Primitive, FilterValueType"
1
+ <svelte:options runes />
2
+
3
+ <script
4
+ lang="ts"
5
+ generics="T extends object, IdType extends Primitive, FilterValueType"
6
6
  >import { dev } from "$app/environment";
7
7
  import Button from "../../Button.svelte";
8
8
  import MultiSelect from "../../MultiSelect.svelte";
@@ -264,326 +264,326 @@ function generateOnChangePrimitive(_label) {
264
264
  value = newValue ?? null;
265
265
  };
266
266
  }
267
- </script>
268
-
269
- {#if isSearchable}
270
- <!-- TODO remove this check once datePicker is supported -->
271
- {#if def instanceof DateInputDef || def instanceof CheckboxDef}
272
- <label for={inputId}>{displayLabel}</label>
273
- {/if}
274
- <div class="row">
275
- {#if def instanceof DateInputDef}
276
- {@const dateValues =
277
- tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value ?? []}
278
- {#if isDateTuple(dateValues)}
279
- {@const [fromDate, toDate] = dateValues}
280
- <div class="spaced-around">
281
- <Button
282
- onclick={() => {
283
- showFromDateModal = true;
284
- }}>{i18n['scoria.label.filter_date_from']({
285
- date: fromDate?.toLocaleDateString() ?? i18n['scoria.label.any_date'](),
286
- })}</Button
287
- >
288
-
289
- <Button
290
- onclick={() => {
291
- showToDateModal = true;
292
- }}>{i18n['scoria.label.filter_date_to']({
293
- date: toDate?.toLocaleDateString() ?? i18n['scoria.label.any_date'](),
294
- })}</Button
295
- >
296
- </div>
297
-
298
- {#if showFromDateModal}
299
- <DateModal
300
- header={i18n['scoria.label.edit_date_from']()}
301
- value={fromDate}
302
- cancelEditing={() => {
303
- showFromDateModal = false;
304
- }}
305
- confirm={(e) => {
306
- tableContext.paginationRepo?.filterBy(def.id, [e, toDate]);
307
- showFromDateModal = false;
308
- }}
309
- />
310
- {/if}
311
-
312
- {#if showToDateModal}
313
- <DateModal
314
- inclusive
315
- header={i18n['scoria.label.edit_date_to']()}
316
- value={toDate}
317
- cancelEditing={() => {
318
- showToDateModal = false;
319
- }}
320
- confirm={(e) => {
321
- tableContext.paginationRepo?.filterBy(def.id, [fromDate, e]);
322
- showToDateModal = false;
323
- }}
324
- />
325
- {/if}
326
- {/if}
327
- {:else if def instanceof SelectDef && !isRemoteSelect}
328
- <div class="maxwidth">
329
- {#await optionsPromise then options}
330
- {@const selectedPrimitives = selectedOptions
331
- .map((it) => it.value)
332
- .filter((v): v is string | number => typeof v === 'string' || typeof v === 'number')}
333
- <MultiSelect
334
- label={customFilter?.id ??
335
- customFilter?.label ??
336
- customFilter?.objectName ??
337
- customFilter?.queryKey ??
338
- def.id}
339
- labelProp="label"
340
- valueProp="value"
341
- name={customFilter?.objectName ?? 'Item'}
342
- {options}
343
- valueAsObject={false}
344
- onchange={(e: Array<unknown>) => {
345
- // Phase 7.6: `valueAsObject={false}` returns
346
- // primitives directly.
347
- tableContext.paginationRepo?.filterBy(def.id, e);
348
- }}
349
- selectedValues={selectedPrimitives}
350
- collapseSelection
351
- />
352
- {:catch e}
353
- <span>{i18n['scoria.text.error_retrieving_options']({ message: e.message })}</span>
354
- <Button
355
- onclick={() => {
356
- retryOptionsPromise();
357
- }}>{i18n['scoria.action.retry_load']()}</Button
358
- >
359
- {/await}
360
- </div>
361
- {:else if customFilter && isRemoteSelect}
362
- <div class="contents">
363
- {#await options then resolvedOptions}
364
- {@const options = resolvedOptions as Array<SelectOption<unknown>>}
365
- {#if hasQuery(customFilter)}
366
- {#if customFilter.valueAsObject !== true}
367
- {@const label = displayLabel}
368
-
369
- <!-- TODO: multiselect -->
370
-
371
- <QuerySelect
372
- valueAsObject={false}
373
- {label}
374
- clearable
375
- name={customFilter.objectName ?? 'Item'}
376
- valueProp={'value' as never}
377
- labelProp={'label' as never}
378
- value={value as never}
379
- query={customFilter.query}
380
- bind:options={lastQueryOptions as never}
381
- onchange={generateOnChangePrimitive(label)}
382
- />
383
- {:else}
384
- {@const label = displayLabel}
385
-
386
- <!-- TODO: multiselect -->
387
-
388
- <QuerySelect
389
- valueAsObject={true}
390
- {label}
391
- clearable
392
- name={customFilter.objectName ?? 'Item'}
393
- valueProp="value"
394
- labelProp="label"
395
- value={value as null}
396
- query={customFilter.query}
397
- bind:options={lastQueryOptions}
398
- onchange={generateOnChangeObject(label)}
399
- />
400
- {/if}
401
- {:else}
402
- {@const selectedPrimitives = selectedOptions
403
- .map((it) => it.value)
404
- .filter(
405
- (v): v is string | number => typeof v === 'string' || typeof v === 'number'
406
- )}
407
- <!--
408
- With `valueAsObject={false}`, MultiSelect compares
409
- its `selectedValues` against options by primitive
410
- value, so we map `selectedOptions` to their
411
- primitive `.value` rather than passing the
412
- SelectOption objects directly.
413
- -->
414
- <MultiSelect
415
- label={displayLabel}
416
- labelProp="label"
417
- valueProp="value"
418
- name={customFilter.objectName ?? 'Item'}
419
- {options}
420
- valueAsObject={false}
421
- onchange={(e: Array<unknown>) => {
422
- // Phase 7.6: `valueAsObject={false}` returns
423
- // primitives directly.
424
- tableContext.paginationRepo?.filterBy(def.id, e);
425
- }}
426
- selectedValues={selectedPrimitives}
427
- collapseSelection
428
- />
429
- {/if}
430
- {/await}
431
- </div>
432
- {:else if def instanceof CheckboxDef}
433
- <div class="inner-row">
434
- <label>
435
- <input
436
- value={false}
437
- type="radio"
438
- bind:group={value}
439
- onchange={() => {
440
- tableContext.paginationRepo?.filterBy(def.id, value);
441
- }}
442
- />
443
- {i18n['scoria.action.no']()}
444
- </label>
445
- <label>
446
- <input
447
- value={undefined}
448
- type="radio"
449
- bind:group={value}
450
- onchange={() => {
451
- tableContext.paginationRepo?.filterBy(def.id, value);
452
- }}
453
- />
454
- {i18n['scoria.label.any']()}
455
- </label>
456
- <label>
457
- <input
458
- value={true}
459
- type="radio"
460
- bind:group={value}
461
- onchange={() => {
462
- tableContext.paginationRepo?.filterBy(def.id, value);
463
- }}
464
- />
465
- {i18n['scoria.action.yes']()}
466
- </label>
467
- </div>
468
- {:else if isNumeric}
469
- {@const label = typeof def.header === 'string' ? def.header : undefined}
470
- <div class="left">
471
- <SingleSelect
472
- {label}
473
- name="Filter Type"
474
- placeholder={i18n['scoria.label.select_filter_type']()}
475
- options={numberDateTypes}
476
- labelProp="label"
477
- valueProp="value"
478
- valueAsObject
479
- value={selectedNumberFilterType}
480
- onchange={(newValue: SelectOption<NumberDateFilterMode> | null) => {
481
- const oldFilterType = selectedNumberFilterType;
482
-
483
- const newFilterType = newValue?.value ?? NumberDateFilterMode.In;
484
- tableContext.paginationRepo?.setFilterMode(def.id, newFilterType);
485
-
486
- if (selectedNumberFilterType?.value !== oldFilterType?.value) {
487
- if (oldFilterType?.value === NumberDateFilterMode.Between) {
488
- valueMax = null;
489
- tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMin]);
490
- }
491
-
492
- if (newFilterType === NumberDateFilterMode.In) {
493
- tableContext.paginationRepo?.filterBy(def.id, [value]);
494
- }
495
-
496
- // TODO what about the other modes?
497
- }
498
- }}
499
- onkeydown={(e: KeyboardEvent) => {
500
- e.stopPropagation();
501
- }}
502
- />
503
- </div>
504
- <div class="right">
505
- {#if selectedNumberFilterType?.value === NumberDateFilterMode.Between}
506
- <NumberInput
507
- id={inputId}
508
- bind:value={valueMin}
509
- oninput={() => {
510
- debouncedFilterBy(def.id, [valueMin, valueMax]);
511
- }}
512
- onkeydown={(e: KeyboardEvent) => {
513
- e.stopPropagation();
514
- }}
515
- />
516
- <span>{i18n['scoria.label.between_and']()}</span>
517
- <NumberInput
518
- id={inputId}
519
- bind:value={valueMax}
520
- oninput={() => {
521
- debouncedFilterBy(def.id, [valueMin, valueMax]);
522
- }}
523
- onkeydown={(e: KeyboardEvent) => {
524
- e.stopPropagation();
525
- }}
526
- />
527
- {:else if value == null || typeof value == 'string'}
528
- <TextInput
529
- id={inputId}
530
- bind:value
531
- oninput={() => {
532
- debouncedFilterBy(def.id, [value]);
533
- }}
534
- onkeydown={(e: KeyboardEvent) => {
535
- e.stopPropagation();
536
- }}
537
- />
538
- {:else if dev}
539
- error: {value?.toString()}
540
- <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
541
- {@debug value}
542
- {/if}
543
- </div>
544
- {:else}
545
- {@const label = typeof def.header === 'string' ? def.header : undefined}
546
- <div class="left">
547
- <SingleSelect
548
- name="Filter Type"
549
- placeholder={i18n['scoria.label.select_filter_type']()}
550
- valueAsObject
551
- {label}
552
- options={stringTypes}
553
- labelProp="label"
554
- valueProp="value"
555
- value={selectedStringFilterType}
556
- onchange={(newValue: SelectOption<StringFilterMode> | null) => {
557
- const filterType = newValue?.value ?? StringFilterMode.Contains;
558
- tableContext.paginationRepo?.setFilterMode(def.id, filterType);
559
- }}
560
- onkeydown={(e: KeyboardEvent) => {
561
- e.stopPropagation();
562
- }}
563
- />
564
- </div>
565
- <div class="right">
566
- {#if value == null || typeof value == 'string'}
567
- <TextInput
568
- id={inputId}
569
- bind:value
570
- oninput={() => {
571
- debouncedFilterBy(def.id, value);
572
- }}
573
- onkeydown={(e: KeyboardEvent) => {
574
- e.stopPropagation();
575
- }}
576
- />
577
- {:else if dev}
578
- error: {value?.toString()}
579
- <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
580
- {@debug value}
581
- {/if}
582
- </div>
583
- {/if}
584
- </div>
585
- {/if}
586
-
267
+ </script>
268
+
269
+ {#if isSearchable}
270
+ <!-- TODO remove this check once datePicker is supported -->
271
+ {#if def instanceof DateInputDef || def instanceof CheckboxDef}
272
+ <label for={inputId}>{displayLabel}</label>
273
+ {/if}
274
+ <div class="row">
275
+ {#if def instanceof DateInputDef}
276
+ {@const dateValues =
277
+ tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value ?? []}
278
+ {#if isDateTuple(dateValues)}
279
+ {@const [fromDate, toDate] = dateValues}
280
+ <div class="spaced-around">
281
+ <Button
282
+ onclick={() => {
283
+ showFromDateModal = true;
284
+ }}>{i18n['scoria.label.filter_date_from']({
285
+ date: fromDate?.toLocaleDateString() ?? i18n['scoria.label.any_date'](),
286
+ })}</Button
287
+ >
288
+
289
+ <Button
290
+ onclick={() => {
291
+ showToDateModal = true;
292
+ }}>{i18n['scoria.label.filter_date_to']({
293
+ date: toDate?.toLocaleDateString() ?? i18n['scoria.label.any_date'](),
294
+ })}</Button
295
+ >
296
+ </div>
297
+
298
+ {#if showFromDateModal}
299
+ <DateModal
300
+ header={i18n['scoria.label.edit_date_from']()}
301
+ value={fromDate}
302
+ cancelEditing={() => {
303
+ showFromDateModal = false;
304
+ }}
305
+ confirm={(e) => {
306
+ tableContext.paginationRepo?.filterBy(def.id, [e, toDate]);
307
+ showFromDateModal = false;
308
+ }}
309
+ />
310
+ {/if}
311
+
312
+ {#if showToDateModal}
313
+ <DateModal
314
+ inclusive
315
+ header={i18n['scoria.label.edit_date_to']()}
316
+ value={toDate}
317
+ cancelEditing={() => {
318
+ showToDateModal = false;
319
+ }}
320
+ confirm={(e) => {
321
+ tableContext.paginationRepo?.filterBy(def.id, [fromDate, e]);
322
+ showToDateModal = false;
323
+ }}
324
+ />
325
+ {/if}
326
+ {/if}
327
+ {:else if def instanceof SelectDef && !isRemoteSelect}
328
+ <div class="maxwidth">
329
+ {#await optionsPromise then options}
330
+ {@const selectedPrimitives = selectedOptions
331
+ .map((it) => it.value)
332
+ .filter((v): v is string | number => typeof v === 'string' || typeof v === 'number')}
333
+ <MultiSelect
334
+ label={customFilter?.id ??
335
+ customFilter?.label ??
336
+ customFilter?.objectName ??
337
+ customFilter?.queryKey ??
338
+ def.id}
339
+ labelProp="label"
340
+ valueProp="value"
341
+ name={customFilter?.objectName ?? 'Item'}
342
+ {options}
343
+ valueAsObject={false}
344
+ onchange={(e: Array<unknown>) => {
345
+ // Phase 7.6: `valueAsObject={false}` returns
346
+ // primitives directly.
347
+ tableContext.paginationRepo?.filterBy(def.id, e);
348
+ }}
349
+ selectedValues={selectedPrimitives}
350
+ collapseSelection
351
+ />
352
+ {:catch e}
353
+ <span>{i18n['scoria.text.error_retrieving_options']({ message: e.message })}</span>
354
+ <Button
355
+ onclick={() => {
356
+ retryOptionsPromise();
357
+ }}>{i18n['scoria.action.retry_load']()}</Button
358
+ >
359
+ {/await}
360
+ </div>
361
+ {:else if customFilter && isRemoteSelect}
362
+ <div class="contents">
363
+ {#await options then resolvedOptions}
364
+ {@const options = resolvedOptions as Array<SelectOption<unknown>>}
365
+ {#if hasQuery(customFilter)}
366
+ {#if customFilter.valueAsObject !== true}
367
+ {@const label = displayLabel}
368
+
369
+ <!-- TODO: multiselect -->
370
+
371
+ <QuerySelect
372
+ valueAsObject={false}
373
+ {label}
374
+ clearable
375
+ name={customFilter.objectName ?? 'Item'}
376
+ valueProp={'value' as never}
377
+ labelProp={'label' as never}
378
+ value={value as never}
379
+ query={customFilter.query}
380
+ bind:options={lastQueryOptions as never}
381
+ onchange={generateOnChangePrimitive(label)}
382
+ />
383
+ {:else}
384
+ {@const label = displayLabel}
385
+
386
+ <!-- TODO: multiselect -->
387
+
388
+ <QuerySelect
389
+ valueAsObject={true}
390
+ {label}
391
+ clearable
392
+ name={customFilter.objectName ?? 'Item'}
393
+ valueProp="value"
394
+ labelProp="label"
395
+ value={value as null}
396
+ query={customFilter.query}
397
+ bind:options={lastQueryOptions}
398
+ onchange={generateOnChangeObject(label)}
399
+ />
400
+ {/if}
401
+ {:else}
402
+ {@const selectedPrimitives = selectedOptions
403
+ .map((it) => it.value)
404
+ .filter(
405
+ (v): v is string | number => typeof v === 'string' || typeof v === 'number'
406
+ )}
407
+ <!--
408
+ With `valueAsObject={false}`, MultiSelect compares
409
+ its `selectedValues` against options by primitive
410
+ value, so we map `selectedOptions` to their
411
+ primitive `.value` rather than passing the
412
+ SelectOption objects directly.
413
+ -->
414
+ <MultiSelect
415
+ label={displayLabel}
416
+ labelProp="label"
417
+ valueProp="value"
418
+ name={customFilter.objectName ?? 'Item'}
419
+ {options}
420
+ valueAsObject={false}
421
+ onchange={(e: Array<unknown>) => {
422
+ // Phase 7.6: `valueAsObject={false}` returns
423
+ // primitives directly.
424
+ tableContext.paginationRepo?.filterBy(def.id, e);
425
+ }}
426
+ selectedValues={selectedPrimitives}
427
+ collapseSelection
428
+ />
429
+ {/if}
430
+ {/await}
431
+ </div>
432
+ {:else if def instanceof CheckboxDef}
433
+ <div class="inner-row">
434
+ <label>
435
+ <input
436
+ value={false}
437
+ type="radio"
438
+ bind:group={value}
439
+ onchange={() => {
440
+ tableContext.paginationRepo?.filterBy(def.id, value);
441
+ }}
442
+ />
443
+ {i18n['scoria.action.no']()}
444
+ </label>
445
+ <label>
446
+ <input
447
+ value={undefined}
448
+ type="radio"
449
+ bind:group={value}
450
+ onchange={() => {
451
+ tableContext.paginationRepo?.filterBy(def.id, value);
452
+ }}
453
+ />
454
+ {i18n['scoria.label.any']()}
455
+ </label>
456
+ <label>
457
+ <input
458
+ value={true}
459
+ type="radio"
460
+ bind:group={value}
461
+ onchange={() => {
462
+ tableContext.paginationRepo?.filterBy(def.id, value);
463
+ }}
464
+ />
465
+ {i18n['scoria.action.yes']()}
466
+ </label>
467
+ </div>
468
+ {:else if isNumeric}
469
+ {@const label = typeof def.header === 'string' ? def.header : undefined}
470
+ <div class="left">
471
+ <SingleSelect
472
+ {label}
473
+ name="Filter Type"
474
+ placeholder={i18n['scoria.label.select_filter_type']()}
475
+ options={numberDateTypes}
476
+ labelProp="label"
477
+ valueProp="value"
478
+ valueAsObject
479
+ value={selectedNumberFilterType}
480
+ onchange={(newValue: SelectOption<NumberDateFilterMode> | null) => {
481
+ const oldFilterType = selectedNumberFilterType;
482
+
483
+ const newFilterType = newValue?.value ?? NumberDateFilterMode.In;
484
+ tableContext.paginationRepo?.setFilterMode(def.id, newFilterType);
485
+
486
+ if (selectedNumberFilterType?.value !== oldFilterType?.value) {
487
+ if (oldFilterType?.value === NumberDateFilterMode.Between) {
488
+ valueMax = null;
489
+ tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMin]);
490
+ }
491
+
492
+ if (newFilterType === NumberDateFilterMode.In) {
493
+ tableContext.paginationRepo?.filterBy(def.id, [value]);
494
+ }
495
+
496
+ // TODO what about the other modes?
497
+ }
498
+ }}
499
+ onkeydown={(e: KeyboardEvent) => {
500
+ e.stopPropagation();
501
+ }}
502
+ />
503
+ </div>
504
+ <div class="right">
505
+ {#if selectedNumberFilterType?.value === NumberDateFilterMode.Between}
506
+ <NumberInput
507
+ id={inputId}
508
+ bind:value={valueMin}
509
+ oninput={() => {
510
+ debouncedFilterBy(def.id, [valueMin, valueMax]);
511
+ }}
512
+ onkeydown={(e: KeyboardEvent) => {
513
+ e.stopPropagation();
514
+ }}
515
+ />
516
+ <span>{i18n['scoria.label.between_and']()}</span>
517
+ <NumberInput
518
+ id={inputId}
519
+ bind:value={valueMax}
520
+ oninput={() => {
521
+ debouncedFilterBy(def.id, [valueMin, valueMax]);
522
+ }}
523
+ onkeydown={(e: KeyboardEvent) => {
524
+ e.stopPropagation();
525
+ }}
526
+ />
527
+ {:else if value == null || typeof value == 'string'}
528
+ <TextInput
529
+ id={inputId}
530
+ bind:value
531
+ oninput={() => {
532
+ debouncedFilterBy(def.id, [value]);
533
+ }}
534
+ onkeydown={(e: KeyboardEvent) => {
535
+ e.stopPropagation();
536
+ }}
537
+ />
538
+ {:else if dev}
539
+ error: {value?.toString()}
540
+ <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
541
+ {@debug value}
542
+ {/if}
543
+ </div>
544
+ {:else}
545
+ {@const label = typeof def.header === 'string' ? def.header : undefined}
546
+ <div class="left">
547
+ <SingleSelect
548
+ name="Filter Type"
549
+ placeholder={i18n['scoria.label.select_filter_type']()}
550
+ valueAsObject
551
+ {label}
552
+ options={stringTypes}
553
+ labelProp="label"
554
+ valueProp="value"
555
+ value={selectedStringFilterType}
556
+ onchange={(newValue: SelectOption<StringFilterMode> | null) => {
557
+ const filterType = newValue?.value ?? StringFilterMode.Contains;
558
+ tableContext.paginationRepo?.setFilterMode(def.id, filterType);
559
+ }}
560
+ onkeydown={(e: KeyboardEvent) => {
561
+ e.stopPropagation();
562
+ }}
563
+ />
564
+ </div>
565
+ <div class="right">
566
+ {#if value == null || typeof value == 'string'}
567
+ <TextInput
568
+ id={inputId}
569
+ bind:value
570
+ oninput={() => {
571
+ debouncedFilterBy(def.id, value);
572
+ }}
573
+ onkeydown={(e: KeyboardEvent) => {
574
+ e.stopPropagation();
575
+ }}
576
+ />
577
+ {:else if dev}
578
+ error: {value?.toString()}
579
+ <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
580
+ {@debug value}
581
+ {/if}
582
+ </div>
583
+ {/if}
584
+ </div>
585
+ {/if}
586
+
587
587
  <style>.contents {
588
588
  display: contents;
589
589
  --input-width: 100%;
@@ -654,4 +654,4 @@ function generateOnChangePrimitive(_label) {
654
654
  .inner-row label input {
655
655
  cursor: pointer;
656
656
  width: 2rem;
657
- }</style>
657
+ }</style>