@dosgato/dialog 1.2.8 → 1.3.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.
Files changed (58) hide show
  1. package/dist/Button.svelte +15 -13
  2. package/dist/ButtonGroup.svelte +44 -40
  3. package/dist/Checkbox.svelte +13 -12
  4. package/dist/Container.svelte +50 -46
  5. package/dist/Dialog.svelte +56 -41
  6. package/dist/FieldAutocomplete.svelte +77 -70
  7. package/dist/FieldCheckbox.svelte +25 -22
  8. package/dist/FieldChoices.svelte +74 -68
  9. package/dist/FieldChooserLink.svelte +148 -150
  10. package/dist/FieldDate.svelte +19 -18
  11. package/dist/FieldDateTime.svelte +36 -34
  12. package/dist/FieldDualListbox.svelte +80 -79
  13. package/dist/FieldHidden.svelte +16 -15
  14. package/dist/FieldIdentifier.svelte +18 -17
  15. package/dist/FieldMultiple.svelte +71 -74
  16. package/dist/FieldMultiselect.svelte +81 -72
  17. package/dist/FieldNumber.svelte +20 -19
  18. package/dist/FieldRadio.svelte +42 -41
  19. package/dist/FieldSelect.svelte +45 -45
  20. package/dist/FieldStandard.svelte +28 -27
  21. package/dist/FieldText.svelte +27 -24
  22. package/dist/FieldTextArea.svelte +24 -24
  23. package/dist/FileIcon.svelte +10 -8
  24. package/dist/Form.svelte +40 -18
  25. package/dist/Form.svelte.d.ts +15 -13
  26. package/dist/FormDialog.svelte +40 -25
  27. package/dist/FormDialog.svelte.d.ts +23 -17
  28. package/dist/Icon.svelte +38 -33
  29. package/dist/InlineMessage.svelte +31 -29
  30. package/dist/InlineMessages.svelte +10 -7
  31. package/dist/Input.svelte +40 -39
  32. package/dist/Listbox.svelte +102 -109
  33. package/dist/MaxLength.svelte +19 -18
  34. package/dist/Radio.svelte +18 -15
  35. package/dist/Switcher.svelte +37 -33
  36. package/dist/Tab.svelte +23 -21
  37. package/dist/Tabs.svelte +111 -110
  38. package/dist/Tooltip.svelte +7 -7
  39. package/dist/chooser/Chooser.svelte +83 -76
  40. package/dist/chooser/ChooserPreview.svelte +16 -14
  41. package/dist/chooser/Details.svelte +6 -4
  42. package/dist/chooser/Thumbnail.svelte +20 -16
  43. package/dist/chooser/UploadUI.svelte +78 -69
  44. package/dist/code/CodeEditor.svelte +63 -66
  45. package/dist/code/FieldCodeEditor.svelte +21 -19
  46. package/dist/colorpicker/FieldColorPicker.svelte +36 -35
  47. package/dist/cropper/FieldCropper.svelte +142 -141
  48. package/dist/iconpicker/FieldIconPicker.svelte +102 -94
  49. package/dist/imageposition/FieldImagePosition.svelte +107 -98
  50. package/dist/tagpicker/FieldTagPicker.svelte +63 -54
  51. package/dist/tree/LoadIcon.svelte +0 -1
  52. package/dist/tree/Tree.svelte +198 -192
  53. package/dist/tree/Tree.svelte.d.ts +5 -5
  54. package/dist/tree/TreeCell.svelte +10 -6
  55. package/dist/tree/TreeCell.svelte.d.ts +5 -5
  56. package/dist/tree/TreeNode.svelte +213 -241
  57. package/dist/tree/TreeNode.svelte.d.ts +5 -5
  58. package/package.json +8 -9
@@ -1,87 +1,89 @@
1
- <script>import menuRight from '@iconify-icons/mdi/menu-right.js';
2
- import menuLeft from '@iconify-icons/mdi/menu-left.js';
3
- import { ScreenReaderOnly, modifierKey } from '@txstate-mws/svelte-components';
4
- import { arraySerialize } from '@txstate-mws/svelte-forms';
5
- import { randomid } from 'txstate-utils';
6
- import FieldStandard from './FieldStandard.svelte';
7
- import Icon from './Icon.svelte';
8
- import Listbox from './Listbox.svelte';
9
- import { getDescribedBy } from './';
10
- export let id = undefined;
11
- export let path;
12
- export let label = '';
13
- export let sourceLabel = 'Available';
14
- export let selectedLabel = 'Selected';
15
- export let multiselect = false;
16
- export let choices;
17
- export let defaultValue = [];
18
- export let conditional = undefined;
19
- export let required = false;
20
- export let related = 0;
21
- export let extradescid = undefined;
22
- export let helptext = undefined;
23
- let itemsToAdd = []; // the items selected in the left listbox
24
- let itemsToRemove = []; // the items selected in the right listbox
25
- let instructions = 'test';
26
- $: {
27
- if (itemsToAdd.length === 1)
28
- instructions = `Press right arrow key to move selected ${sourceLabel} items to ${selectedLabel} items list.`;
29
- else
30
- instructions = '';
31
- }
32
- $: {
33
- if (itemsToRemove.length === 1)
34
- instructions = `Press left arrow key to move selected ${selectedLabel} items to ${sourceLabel} list.`;
35
- else
36
- instructions = '';
37
- }
38
- const descid = randomid();
39
- function addToSelected(value, setVal) {
1
+ <script lang="ts">
2
+ import menuRight from '@iconify-icons/mdi/menu-right.js'
3
+ import menuLeft from '@iconify-icons/mdi/menu-left.js'
4
+ import { type PopupMenuItem, ScreenReaderOnly, modifierKey } from '@txstate-mws/svelte-components'
5
+ import { arraySerialize } from '@txstate-mws/svelte-forms'
6
+ import { randomid } from 'txstate-utils'
7
+ import FieldStandard from './FieldStandard.svelte'
8
+ import Icon from './Icon.svelte'
9
+ import Listbox from './Listbox.svelte'
10
+ import { getDescribedBy } from './'
11
+
12
+ export let id: string | undefined = undefined
13
+ export let path: string
14
+ export let label: string = ''
15
+ export let sourceLabel: string = 'Available'
16
+ export let selectedLabel: string = 'Selected'
17
+ export let multiselect: boolean = false
18
+ export let choices: PopupMenuItem[]
19
+ export let defaultValue: string[] = []
20
+ export let conditional: boolean | undefined = undefined
21
+ export let required = false
22
+ export let related: true | number = 0
23
+ export let extradescid: string | undefined = undefined
24
+ export let helptext: string | undefined = undefined
25
+
26
+ let itemsToAdd: PopupMenuItem[] = [] // the items selected in the left listbox
27
+ let itemsToRemove: PopupMenuItem[] = [] // the items selected in the right listbox
28
+ let instructions: string = 'test'
29
+
30
+ $: {
31
+ if (itemsToAdd.length === 1) instructions = `Press right arrow key to move selected ${sourceLabel} items to ${selectedLabel} items list.`
32
+ else instructions = ''
33
+ }
34
+
35
+ $: {
36
+ if (itemsToRemove.length === 1) instructions = `Press left arrow key to move selected ${selectedLabel} items to ${sourceLabel} list.`
37
+ else instructions = ''
38
+ }
39
+
40
+ const descid = randomid()
41
+
42
+ function addToSelected (value: string[], setVal: (val: any) => void) {
40
43
  return () => {
41
- const selected = value.concat(itemsToAdd.map(item => item.value));
42
- itemsToAdd = [];
43
- setVal(selected);
44
- };
45
- }
46
- function addToAvailable(value, setVal) {
44
+ const selected = value.concat(itemsToAdd.map(item => item.value))
45
+ itemsToAdd = []
46
+ setVal(selected)
47
+ }
48
+ }
49
+
50
+ function addToAvailable (value: string[], setVal: (val: any) => void) {
47
51
  return () => {
48
- const itemsToRemoveSet = new Set(itemsToRemove.map(i => i.value));
49
- const selected = value.filter(v => !itemsToRemoveSet.has(v));
50
- itemsToRemove = [];
51
- setVal(selected);
52
- };
53
- }
54
- function valueToSelectedChoices(value) {
52
+ const itemsToRemoveSet = new Set(itemsToRemove.map(i => i.value))
53
+ const selected = value.filter(v => !itemsToRemoveSet.has(v))
54
+ itemsToRemove = []
55
+ setVal(selected)
56
+ }
57
+ }
58
+
59
+ function valueToSelectedChoices (value: string[]) {
55
60
  // keep the selected options ordered as they were in the available options
56
- const valueSet = new Set(value);
57
- const ret = [];
61
+ const valueSet = new Set(value)
62
+ const ret: PopupMenuItem[] = []
58
63
  for (const choice of choices) {
59
- if (valueSet.has(choice.value))
60
- ret.push({ value: choice.value, label: choice.label || choice.value });
64
+ if (valueSet.has(choice.value)) ret.push({ value: choice.value, label: choice.label || choice.value })
61
65
  }
62
- return ret;
63
- }
64
- function getAvailable(value) {
65
- return choices.filter(choice => !value.includes(choice.value));
66
- }
67
- function onkeydown(value, setVal) {
66
+ return ret
67
+ }
68
+
69
+ function getAvailable (value: string[]) {
70
+ return choices.filter(choice => !value.includes(choice.value))
71
+ }
72
+
73
+ function onkeydown (value: string[], setVal: (val: any) => void) {
68
74
  return (e) => {
69
- if (modifierKey(e))
70
- return;
71
- if (e.key === 'ArrowRight') {
72
- e.preventDefault();
73
- if (itemsToAdd.length === 0)
74
- return;
75
- addToSelected(value, setVal)();
76
- }
77
- else if (e.key === 'ArrowLeft') {
78
- e.preventDefault();
79
- if (itemsToRemove.length === 0)
80
- return;
81
- addToAvailable(value, setVal)();
82
- }
83
- };
84
- }
75
+ if (modifierKey(e)) return
76
+ if (e.key === 'ArrowRight') {
77
+ e.preventDefault()
78
+ if (itemsToAdd.length === 0) return
79
+ addToSelected(value, setVal)()
80
+ } else if (e.key === 'ArrowLeft') {
81
+ e.preventDefault()
82
+ if (itemsToRemove.length === 0) return
83
+ addToAvailable(value, setVal)()
84
+ }
85
+ }
86
+ }
85
87
  </script>
86
88
 
87
89
  <FieldStandard bind:id {label} {path} {required} {defaultValue} {conditional} {descid} {related} {helptext} let:value let:valid let:invalid let:id let:onBlur let:setVal let:helptextid let:messagesid serialize={arraySerialize}>
@@ -117,5 +119,4 @@ function onkeydown(value, setVal) {
117
119
  border: 0;
118
120
  padding: 0;
119
121
  }
120
-
121
122
  </style>
@@ -1,19 +1,20 @@
1
- <script>import { Field, nullableSerialize, nullableDeserialize, FORM_CONTEXT, FORM_INHERITED_PATH } from '@txstate-mws/svelte-forms';
2
- import { getContext } from 'svelte';
3
- import { isNotBlank } from 'txstate-utils';
4
- export let id = undefined;
5
- export let path;
6
- export let value = '';
7
- export let notNull = false;
8
- export let boolean = false;
9
- export let number = false;
10
- export let conditional = undefined;
11
- const store = getContext(FORM_CONTEXT);
12
- const inheritedPath = getContext(FORM_INHERITED_PATH);
13
- const finalPath = [inheritedPath, path].filter(isNotBlank).join('.');
14
- $: void store.setField(finalPath, value);
1
+ <script lang="ts">
2
+ import { Field, type FormStore, FORM_CONTEXT, FORM_INHERITED_PATH } from '@txstate-mws/svelte-forms'
3
+ import { getContext } from 'svelte'
4
+ import { isNotBlank } from 'txstate-utils'
5
+ export let id: string | undefined = undefined
6
+ export let path: string
7
+ export let value: string | boolean | number = ''
8
+ export let notNull = false
9
+ export let boolean = false
10
+ export let number = false
11
+ export let conditional: boolean | undefined = undefined
12
+ const store = getContext<FormStore>(FORM_CONTEXT)
13
+ const inheritedPath = getContext<string>(FORM_INHERITED_PATH)
14
+ const finalPath = [inheritedPath, path].filter(isNotBlank).join('.')
15
+ $: void store.setField(finalPath, value)
15
16
  </script>
16
17
 
17
- <Field {path} {conditional} {boolean} {number} serialize={!notNull ? nullableSerialize : undefined} deserialize={!notNull ? nullableDeserialize : undefined} let:value>
18
+ <Field {path} {conditional} {boolean} {number} {notNull} let:value>
18
19
  <input type="hidden" name={path} {value} {id}>
19
20
  </Field>
@@ -1,21 +1,22 @@
1
- <script>import { Field, nullableSerialize, nullableDeserialize } from '@txstate-mws/svelte-forms';
2
- import { randomid } from 'txstate-utils';
3
- import { onMount } from 'svelte';
4
- export let path;
5
- export let conditional = undefined;
6
- export let length = 10;
7
- let val, stVal;
8
- function updateValue(valu, sVal) {
9
- val = valu;
10
- stVal = sVal;
11
- }
12
- onMount(() => {
13
- if (!val)
14
- stVal(randomid(length));
15
- });
1
+ <script lang="ts">
2
+ import { Field, FORM_INHERITED_PATH, type FormStore, FORM_CONTEXT } from '@txstate-mws/svelte-forms'
3
+ import { getContext, onMount } from 'svelte'
4
+ import { isNotBlank, randomid } from 'txstate-utils'
5
+
6
+ export let path: string
7
+ export let conditional: boolean | undefined = undefined
8
+ export let length: number = 10
9
+
10
+ const store = getContext<FormStore>(FORM_CONTEXT)
11
+ const inheritedPath = getContext<string>(FORM_INHERITED_PATH)
12
+ const finalPath = [inheritedPath, path].filter(isNotBlank).join('.')
13
+ const valueStore = store.getField<string | null>(finalPath)
14
+
15
+ onMount(() => {
16
+ if (!$valueStore) store.setField(finalPath, randomid(length))
17
+ })
16
18
  </script>
17
19
 
18
- <Field {path} {conditional} defaultValue={randomid(length)} serialize={nullableSerialize} deserialize={nullableDeserialize} let:value let:setVal>
19
- {@const _ = updateValue(value, setVal)}
20
+ <Field {path} {conditional} defaultValue={randomid(length)} notNull let:value>
20
21
  <input type="hidden" name={path} {value}>
21
22
  </Field>
@@ -1,84 +1,82 @@
1
- <script context="module">export const DG_DIALOG_FIELD_MULTIPLE = {};
2
- function noOp(..._) { return ''; }
1
+ <script lang="ts" context="module">
2
+ export const DG_DIALOG_FIELD_MULTIPLE = {}
3
+ function noOp (..._: any[]) { return '' }
3
4
  </script>
4
- <script>import caretCircleDown from '@iconify-icons/ph/caret-circle-down-fill';
5
- import caretCircleUp from '@iconify-icons/ph/caret-circle-up-fill';
6
- import plusCircleLight from '@iconify-icons/ph/plus-circle-light';
7
- import xCircle from '@iconify-icons/ph/x-circle-fill';
8
- import { AddMore, FORM_CONTEXT, FORM_INHERITED_PATH } from '@txstate-mws/svelte-forms';
9
- import { derivedStore } from '@txstate-mws/svelte-store';
10
- import { getContext, setContext } from 'svelte';
11
- import { isNotNull } from 'txstate-utils';
12
- import Button from './Button.svelte';
13
- import Container from './Container.svelte';
14
- import Icon from './Icon.svelte';
15
- export let path;
16
- export let label;
17
- export let initialState = undefined;
18
- export let minLength = 1;
19
- export let maxLength = undefined;
20
- export let compact = false;
21
- export let removable = false;
22
- export let reorder = false;
23
- export let conditional = undefined;
24
- export let addMoreText = 'Add';
25
- export let maxedText = addMoreText;
26
- export let addMoreClass = undefined;
27
- export let related = 0;
28
- export let helptext = undefined;
29
- /**
30
- * If you want to ask users if they're sure before removing an array element, fill this
31
- * prop with the question that should be in the confirmation dialog.
32
- *
33
- * Alternatively, you can provide a function to generate the question from the item being
34
- * deleted. e.g. (item) => `Are you sure you want to delete ${item.name}?`
35
- */
36
- export let confirmDelete = undefined;
37
- const fieldMultipleContext = { helptextid: undefined };
38
- setContext(DG_DIALOG_FIELD_MULTIPLE, fieldMultipleContext);
39
- const inheritedPath = getContext(FORM_INHERITED_PATH);
40
- const finalPath = [inheritedPath, path].filter(isNotNull).join('.');
41
- const store = getContext(FORM_CONTEXT);
42
- const messageStore = derivedStore(store, ({ messages }) => messages.all.filter(m => m.path?.startsWith(finalPath)));
43
- const reorderupelements = [];
44
- const reorderdownelements = [];
45
- function moveUpAndFocus(onMove, idx) {
5
+ <script lang="ts">
6
+ import caretCircleDown from '@iconify-icons/ph/caret-circle-down-fill'
7
+ import caretCircleUp from '@iconify-icons/ph/caret-circle-up-fill'
8
+ import plusCircleLight from '@iconify-icons/ph/plus-circle-light'
9
+ import xCircle from '@iconify-icons/ph/x-circle-fill'
10
+ import { AddMore, type Feedback } from '@txstate-mws/svelte-forms'
11
+ import { setContext } from 'svelte'
12
+ import { writable } from 'svelte/store'
13
+ import Button from './Button.svelte'
14
+ import Container from './Container.svelte'
15
+ import Icon from './Icon.svelte'
16
+
17
+ export let path: string
18
+ export let label: string
19
+ export let initialState: any | ((index: number) => any) = undefined
20
+ export let minLength = 1
21
+ export let maxLength: number | undefined = undefined
22
+ export let compact = false
23
+ export let removable = false
24
+ export let reorder = false
25
+ export let conditional: boolean | undefined = undefined
26
+ export let addMoreText = 'Add'
27
+ export let maxedText = addMoreText
28
+ export let addMoreClass: string | undefined = undefined
29
+ export let related: true | number = 0
30
+ export let helptext: string | undefined = undefined
31
+ /**
32
+ * If you want to ask users if they're sure before removing an array element, fill this
33
+ * prop with the question that should be in the confirmation dialog.
34
+ *
35
+ * Alternatively, you can provide a function to generate the question from the item being
36
+ * deleted. e.g. (item) => `Are you sure you want to delete ${item.name}?`
37
+ */
38
+ export let confirmDelete: string | ((item: any) => string) | undefined = undefined
39
+
40
+ const fieldMultipleContext: { helptextid: string | undefined } = { helptextid: undefined }
41
+ setContext(DG_DIALOG_FIELD_MULTIPLE, fieldMultipleContext)
42
+
43
+ const reorderupelements: HTMLButtonElement[] = []
44
+ const reorderdownelements: HTMLButtonElement[] = []
45
+ function moveUpAndFocus (onMove: () => void, idx: number) {
46
46
  return () => {
47
- onMove();
48
- let newFocus = reorderupelements[idx - 1];
49
- if (newFocus) {
50
- if (newFocus.disabled)
51
- newFocus = reorderdownelements[idx - 1];
52
- newFocus.focus();
53
- }
54
- };
55
- }
56
- function moveDownAndFocus(onMove, idx) {
47
+ onMove()
48
+ let newFocus = reorderupelements[idx - 1]
49
+ if (newFocus) {
50
+ if (newFocus.disabled) newFocus = reorderdownelements[idx - 1]
51
+ newFocus.focus()
52
+ }
53
+ }
54
+ }
55
+ function moveDownAndFocus (onMove: () => void, idx: number) {
57
56
  return () => {
58
- onMove();
59
- let newFocus = reorderdownelements[idx + 1];
60
- if (newFocus) {
61
- if (newFocus.disabled)
62
- newFocus = reorderupelements[idx + 1];
63
- newFocus.focus();
64
- }
65
- };
66
- }
67
- function confirmedDelete(onDelete, item) {
57
+ onMove()
58
+ let newFocus = reorderdownelements[idx + 1]
59
+ if (newFocus) {
60
+ if (newFocus.disabled) newFocus = reorderupelements[idx + 1]
61
+ newFocus.focus()
62
+ }
63
+ }
64
+ }
65
+
66
+ function confirmedDelete (onDelete: () => void, item: any) {
68
67
  return () => {
69
- if (confirmDelete == null)
70
- return onDelete();
71
- const msg = typeof confirmDelete === 'string' ? confirmDelete : confirmDelete(item);
72
- if (confirm(msg))
73
- onDelete();
74
- };
75
- }
76
- $: messages = compact ? $messageStore : [];
68
+ if (confirmDelete == null) return onDelete()
69
+ const msg = typeof confirmDelete === 'string' ? confirmDelete : confirmDelete(item)
70
+ if (confirm(msg)) onDelete()
71
+ }
72
+ }
73
+
74
+ let messages: Feedback[] = []
77
75
  </script>
78
76
 
79
77
  <Container {path} {label} {messages} {conditional} {related} {helptext} let:helptextid>
80
78
  {noOp(fieldMultipleContext.helptextid = helptextid)}
81
- <AddMore {path} {initialState} {minLength} {maxLength} {conditional} let:path let:currentLength let:maxLength let:index let:minned let:maxed let:value let:onDelete let:onMoveUp let:onMoveDown>
79
+ <AddMore bind:messages {path} {initialState} {minLength} {maxLength} {conditional} let:path let:currentLength let:maxLength let:index let:minned let:maxed let:value let:onDelete let:onMoveUp let:onMoveDown>
82
80
  {@const showDelete = removable && !minned}
83
81
  {@const showMove = reorder && currentLength > 1}
84
82
  <div class="dialog-multiple" class:has-delete-icon={showDelete} class:has-move-icon={showMove} class:first={index === 0}>
@@ -138,5 +136,4 @@ $: messages = compact ? $messageStore : [];
138
136
  .dialog-multiple-buttons button:disabled {
139
137
  color: #6d6d6d;
140
138
  }
141
-
142
139
  </style>
@@ -6,82 +6,92 @@
6
6
  controlled by the parent component via the `getOptions` function that will be used as a
7
7
  debounced callback on the contents of the text input.
8
8
  -->
9
- <script>import { MultiSelect } from '@txstate-mws/svelte-components';
10
- import { arraySerialize } from '@txstate-mws/svelte-forms';
11
- import { Store } from '@txstate-mws/svelte-store';
12
- import { onMount } from 'svelte';
13
- import { isNotBlank } from 'txstate-utils';
14
- import FieldStandard from './FieldStandard.svelte';
15
- import { getDescribedBy } from './helpers';
16
- export let path;
17
- /** Function to pass to the component that tells it how to use the text
18
- in the text input to determine what `PopupMenuItem[]` should be displayed
19
- in the `<PopupMenu>`. Items already 'selected' from the popup menu will be
20
- tracked and automatically filtered from the popup if returned as one of the
21
- `PopupMenuItem[]` by `getOptions`. */
22
- export let getOptions;
23
- export let id = undefined;
24
- export let label = '';
25
- /** Text to display in the text input when it's empty. */
26
- export let placeholder = '';
27
- /** When there are no items (e.g. it's a filtered search and there were no results), we still display one
28
- disabled item in the menu to let the user know what is going on. Use this prop to specify the message. */
29
- export let emptyText = undefined;
30
- export let disabled = false;
31
- export let defaultValue = [];
32
- export let conditional = undefined;
33
- export let required = false;
34
- /** Max number of selections to be allowed before disabling the input - 0 for unlimited. */
35
- export let maxSelections = 0;
36
- export let lookupByValue = async (val) => { const options = await getOptions(val); return options.find((opt) => opt.value === val); };
37
- export let related = 0;
38
- export let extradescid = undefined;
39
- export let helptext = undefined;
40
- export let menuContainerClass = '';
41
- export let menuClass = '';
42
- export let menuItemClass = '';
43
- export let menuItemHilitedClass = '';
44
- export let menuCategoryClass = '';
45
- /** Add a Delete All button to clear all selected items */
46
- export let includeDeleteAll = false;
47
- /** Show a confirmation message before clearing all selections */
48
- export let confirmDelete = undefined;
49
- export let selectedItemLabel = (item) => item.label || item.value;
50
- /** Each time we run getOptions we will save the value -> label mappings that it finds, so that we can display labels on pills. */
51
- const valueToLabel = {};
52
- const valueToGroup = {};
53
- async function wrapGetOptions(search) {
54
- const opts = await getOptions(search);
9
+ <script lang="ts">
10
+ import { MultiSelect } from '@txstate-mws/svelte-components'
11
+ import type { PopupMenuItem } from '@txstate-mws/svelte-components'
12
+ import { arraySerialize } from '@txstate-mws/svelte-forms'
13
+ import { Store } from '@txstate-mws/svelte-store'
14
+ import { onMount } from 'svelte'
15
+ import { isNotBlank } from 'txstate-utils'
16
+ import FieldStandard from './FieldStandard.svelte'
17
+ import { getDescribedBy } from './helpers'
18
+
19
+ export let path: string
20
+ /** Function to pass to the component that tells it how to use the text
21
+ in the text input to determine what `PopupMenuItem[]` should be displayed
22
+ in the `<PopupMenu>`. Items already 'selected' from the popup menu will be
23
+ tracked and automatically filtered from the popup if returned as one of the
24
+ `PopupMenuItem[]` by `getOptions`. */
25
+ export let getOptions: (search: string) => Promise<PopupMenuItem[]>
26
+ export let id: string | undefined = undefined
27
+ export let label: string = ''
28
+ /** Text to display in the text input when it's empty. */
29
+ export let placeholder = ''
30
+ /** When there are no items (e.g. it's a filtered search and there were no results), we still display one
31
+ disabled item in the menu to let the user know what is going on. Use this prop to specify the message. */
32
+ export let emptyText: string | undefined = undefined
33
+ export let disabled = false
34
+ export let defaultValue: string[] = []
35
+ export let conditional: boolean | undefined = undefined
36
+ export let required = false
37
+ /** Max number of selections to be allowed before disabling the input - 0 for unlimited. */
38
+ export let maxSelections = 0
39
+ export let lookupByValue: (val: string) => Promise<PopupMenuItem | undefined> = async (val) => { const options = await getOptions(val); return options.find((opt) => opt.value === val) }
40
+ export let related: true | number = 0
41
+ export let extradescid: string | undefined = undefined
42
+ export let helptext: string | undefined = undefined
43
+ export let menuContainerClass = ''
44
+ export let menuClass = ''
45
+ export let menuItemClass = ''
46
+ export let menuItemHilitedClass = ''
47
+ export let menuCategoryClass = ''
48
+ /** Add a Delete All button to clear all selected items */
49
+ export let includeDeleteAll = false
50
+ /** Show a confirmation message before clearing all selections */
51
+ export let confirmDelete: string | undefined = undefined
52
+
53
+ export let selectedItemLabel: ((item: PopupMenuItem) => string) | undefined = (item) => item.label || item.value
54
+
55
+ /** Each time we run getOptions we will save the value -> label mappings that it finds, so that we can display labels on pills. */
56
+ const valueToLabel: Record<string, string> = {}
57
+ const valueToGroup: Record<string, string | undefined> = {}
58
+
59
+ async function wrapGetOptions (search: string) {
60
+ const opts = await getOptions(search)
55
61
  /* If no options are returned with the search term, we can end up with an infinite loop the first time reactToValue calls wrapGetOptions */
56
62
  // if (opts.length === 0) opts = await getOptions('')
57
63
  for (const opt of opts) {
58
- valueToLabel[opt.value] = opt.label || opt.value;
59
- valueToGroup[opt.value] = opt.group;
64
+ valueToLabel[opt.value] = opt.label || opt.value
65
+ valueToGroup[opt.value] = opt.group
60
66
  }
61
- return opts;
62
- }
63
- const selectedStore = new Store([]);
64
- let hasInit = !defaultValue.length;
65
- let inputelement;
66
- onMount(async () => {
67
- await reactToValue(defaultValue);
68
- hasInit = true;
69
- });
70
- /**
71
- If we get a value from the form that we haven't seen in the popup menu yet, we won't have a label for it.
72
- This function runs getOptions on any selected values for which the label is currently unknown. */
73
- async function reactToValue(value) {
74
- await Promise.all(value.map(async (v) => {
75
- if (!valueToLabel[v]) {
76
- const item = await lookupByValue(v);
77
- if (item) {
78
- valueToLabel[item.value] = item.label ?? item.value;
79
- valueToGroup[item.value] = item.group;
80
- }
67
+ return opts
68
+ }
69
+
70
+ const selectedStore = new Store<{ value: string, label: string, group?: string }[]>([])
71
+
72
+ let hasInit = !defaultValue.length
73
+
74
+ let inputelement: HTMLElement
75
+ onMount(async () => {
76
+ await reactToValue(defaultValue)
77
+ hasInit = true
78
+ })
79
+
80
+ /**
81
+ If we get a value from the form that we haven't seen in the popup menu yet, we won't have a label for it.
82
+ This function runs getOptions on any selected values for which the label is currently unknown. */
83
+ async function reactToValue (value: string[]) {
84
+ await Promise.all(value.map(async v => {
85
+ if (!valueToLabel[v]) {
86
+ const item = await lookupByValue(v)
87
+ if (item) {
88
+ valueToLabel[item.value] = item.label ?? item.value
89
+ valueToGroup[item.value] = item.group
81
90
  }
82
- }));
83
- selectedStore.set(value.map(v => ({ value: v, label: valueToLabel[v], group: valueToGroup[v] })).filter(v => isNotBlank(v.label)));
84
- }
91
+ }
92
+ }))
93
+ selectedStore.set(value.map(v => ({ value: v, label: valueToLabel[v], group: valueToGroup[v] })).filter(v => isNotBlank(v.label)))
94
+ }
85
95
  </script>
86
96
 
87
97
  <div bind:this={inputelement}></div>
@@ -109,5 +119,4 @@ async function reactToValue(value) {
109
119
  :global(.multiselect-input) {
110
120
  width: 100%
111
121
  }
112
-
113
122
  </style>