@lavalogic/scoria 0.40.31 → 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 (137) 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/Icons.d.ts +3 -3
  38. package/dist/Components/Icons.js +11 -11
  39. package/dist/Components/InfoAlert.svelte +32 -32
  40. package/dist/Components/LoadingAnimation.svelte +228 -227
  41. package/dist/Components/LoadingModal.svelte +19 -19
  42. package/dist/Components/LoadingOverlay.svelte +20 -20
  43. package/dist/Components/Modal.svelte +127 -127
  44. package/dist/Components/MultiSelect.svelte +126 -126
  45. package/dist/Components/Nav/Nav.svelte +71 -71
  46. package/dist/Components/Nav/NavServiceMenuList.svelte +37 -37
  47. package/dist/Components/Nav/NavTab.svelte +46 -46
  48. package/dist/Components/Nav/ServiceMenuItem.svelte +62 -62
  49. package/dist/Components/Nav/ServicesNav.svelte +50 -50
  50. package/dist/Components/NumberInput.svelte +98 -98
  51. package/dist/Components/OptionCards.svelte +58 -58
  52. package/dist/Components/PageHeading.svelte +21 -21
  53. package/dist/Components/PasswordInput.svelte +61 -61
  54. package/dist/Components/PopoverButton.svelte +53 -53
  55. package/dist/Components/Portal.svelte +7 -7
  56. package/dist/Components/ProgressBubble.svelte +42 -42
  57. package/dist/Components/QuerySelect.svelte +102 -102
  58. package/dist/Components/ResourceCalendar/ResourceCalendar.svelte +241 -241
  59. package/dist/Components/SidebarPanel.svelte +32 -32
  60. package/dist/Components/SingleSelect.svelte +122 -122
  61. package/dist/Components/StepButton.svelte +48 -48
  62. package/dist/Components/StepForm.svelte +58 -58
  63. package/dist/Components/Switch.svelte +43 -43
  64. package/dist/Components/Table/ARCHITECTURE.md +298 -298
  65. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +30 -30
  66. package/dist/Components/Table/Body/Rows/ColumnList.svelte +24 -24
  67. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +165 -165
  68. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +13 -13
  69. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +120 -120
  70. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +81 -81
  71. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +92 -92
  72. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +110 -110
  73. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +54 -54
  74. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +35 -35
  75. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +64 -64
  76. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +82 -82
  77. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +88 -88
  78. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +90 -90
  79. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +96 -96
  80. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +71 -71
  81. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +124 -124
  82. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +91 -91
  83. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +88 -88
  84. package/dist/Components/Table/Body/Rows/TableRow.svelte +127 -127
  85. package/dist/Components/Table/Body/TableBody.svelte +92 -92
  86. package/dist/Components/Table/Headers/HighlightAllHeader.svelte +25 -25
  87. package/dist/Components/Table/Headers/SelectAllHeader.svelte +20 -20
  88. package/dist/Components/Table/Headers/TableHead.svelte +175 -175
  89. package/dist/Components/Table/Misc/ColumnPanel.svelte +32 -32
  90. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +29 -29
  91. package/dist/Components/Table/Misc/FilterInput.svelte +326 -326
  92. package/dist/Components/Table/Misc/FilterPanel.svelte +79 -79
  93. package/dist/Components/Table/Misc/FilterPanelModal.svelte +21 -21
  94. package/dist/Components/Table/Misc/ShareViewModal.svelte +118 -118
  95. package/dist/Components/Table/Misc/SidePanel.svelte +24 -24
  96. package/dist/Components/Table/Misc/TableConfigurationModal.svelte +318 -318
  97. package/dist/Components/Table/Misc/TableFooter.svelte +201 -201
  98. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +112 -112
  99. package/dist/Components/Table/Misc/TableSidebar.svelte +116 -116
  100. package/dist/Components/Table/Misc/TableViewDropdown.svelte +25 -25
  101. package/dist/Components/Table/Misc/ToolboxPanel.svelte +60 -60
  102. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +63 -63
  103. package/dist/Components/Table/Misc/UpdateViewOptionsModal.svelte +101 -101
  104. package/dist/Components/Table/NestedTable.svelte +29 -29
  105. package/dist/Components/Table/Table.svelte +140 -140
  106. package/dist/Components/Test/TestContextConsumer.svelte +8 -8
  107. package/dist/Components/TextArea.svelte +92 -90
  108. package/dist/Components/TextInput.svelte +128 -117
  109. package/dist/Components/ThreeStateRadio.svelte +101 -101
  110. package/dist/Components/Touch/BoolCard.svelte +27 -27
  111. package/dist/Components/Touch/DateModal.svelte +98 -98
  112. package/dist/Components/Touch/EditCard.svelte +44 -44
  113. package/dist/Components/Touch/InfoCard.svelte +25 -25
  114. package/dist/Components/Touch/InfoTextCard.svelte +22 -22
  115. package/dist/Components/Touch/ModalExplanation.svelte +13 -13
  116. package/dist/Components/Touch/NumberKeyboard.svelte +81 -81
  117. package/dist/Components/Touch/NumberModal.svelte +49 -49
  118. package/dist/Components/Touch/PlusMinusCard.svelte +40 -40
  119. package/dist/Components/Touch/SelectModal.svelte +86 -86
  120. package/dist/Components/Touch/SummaryCard.svelte +74 -74
  121. package/dist/Components/Touch/TextAreaModal.svelte +53 -53
  122. package/dist/Components/Touch/TextModal.svelte +56 -56
  123. package/dist/Components/Touch/TouchCard.svelte +28 -28
  124. package/dist/Components/Touch/TouchModal.svelte +141 -141
  125. package/dist/Components/Touch/UtilityButton.svelte +63 -63
  126. package/dist/Components/VerticalTabGroup.svelte +38 -38
  127. package/dist/Components/VerticalTabGroupButton.svelte +46 -46
  128. package/dist/Helpers/Datamatrix.d.ts +19 -19
  129. package/dist/Helpers/Datamatrix.js +291 -291
  130. package/dist/Types/Examples/ExampleModal.svelte +27 -27
  131. package/dist/paraglide/README.md +1 -1
  132. package/dist/scss/_basics.scss +12 -12
  133. package/dist/scss/_colours.scss +140 -140
  134. package/dist/scss/_motion.scss +57 -57
  135. package/dist/scss/_sizing.scss +85 -85
  136. package/dist/scss/_transitions.scss +8 -8
  137. 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>