@stubber/form-fields 2.2.5 → 3.0.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 (126) hide show
  1. package/README.md +63 -25
  2. package/dist/form-fields/Autocomplete.svelte +261 -0
  3. package/dist/form-fields/Autocomplete.svelte.d.ts +43 -0
  4. package/dist/form-fields/CollapsibleSection.svelte +127 -0
  5. package/dist/form-fields/CollapsibleSection.svelte.d.ts +26 -0
  6. package/dist/form-fields/FieldLabel.svelte +18 -3
  7. package/dist/form-fields/FieldLabel.svelte.d.ts +1 -0
  8. package/dist/form-fields/FieldMessage.svelte +20 -9
  9. package/dist/form-fields/Form.svelte +19 -2
  10. package/dist/form-fields/Form.svelte.d.ts +2 -0
  11. package/dist/form-fields/UploadStatus.svelte +76 -0
  12. package/dist/form-fields/UploadStatus.svelte.d.ts +18 -0
  13. package/dist/form-fields/fields/_field-builder-reorder.svelte +35 -0
  14. package/dist/form-fields/fields/_field-builder-reorder.svelte.d.ts +22 -0
  15. package/dist/form-fields/{sub → fields}/array-builder-field.svelte +104 -38
  16. package/dist/form-fields/fields/checkbox-autocomplete.svelte +153 -0
  17. package/dist/form-fields/fields/checkbox-field.svelte +69 -0
  18. package/dist/form-fields/{sub → fields}/contact-selector-field.svelte +9 -6
  19. package/dist/form-fields/{sub → fields}/currency-field.svelte +108 -70
  20. package/dist/form-fields/fields/data-indication-field.svelte +39 -0
  21. package/dist/form-fields/fields/date-field.svelte +31 -0
  22. package/dist/form-fields/fields/date-time-field.svelte +31 -0
  23. package/dist/form-fields/{sub → fields}/email-field.svelte +25 -11
  24. package/dist/form-fields/fields/field-builder-field.svelte +568 -0
  25. package/dist/form-fields/fields/file-field.svelte +284 -0
  26. package/dist/form-fields/{sub → fields}/file-field.svelte.d.ts +2 -0
  27. package/dist/form-fields/fields/grid-field.svelte +152 -0
  28. package/dist/form-fields/{sub → fields}/heading-field.svelte +7 -4
  29. package/dist/form-fields/{sub → fields}/html-field.svelte +6 -4
  30. package/dist/form-fields/{sub → fields}/map-field.svelte +122 -78
  31. package/dist/form-fields/{sub → fields}/multi-checkbox-field.svelte +35 -20
  32. package/dist/form-fields/fields/multistep-field.svelte +135 -0
  33. package/dist/form-fields/fields/note-field.svelte +35 -0
  34. package/dist/form-fields/{sub → fields}/number-field.svelte +29 -14
  35. package/dist/form-fields/fields/object-builder-field.svelte +176 -0
  36. package/dist/form-fields/fields/qr-code-scanner-field.svelte +203 -0
  37. package/dist/form-fields/fields/radio-field.svelte +82 -0
  38. package/dist/form-fields/{sub → fields}/radio-field.svelte.d.ts +1 -1
  39. package/dist/form-fields/fields/screenrecorder-field.svelte +255 -0
  40. package/dist/form-fields/{sub → fields}/screenshot-field.svelte +101 -40
  41. package/dist/form-fields/{sub → fields}/scroll-and-read-display-field.svelte +43 -27
  42. package/dist/form-fields/fields/section-field.svelte +73 -0
  43. package/dist/form-fields/fields/select-field.svelte +104 -0
  44. package/dist/form-fields/{sub → fields}/signature-field.svelte +78 -22
  45. package/dist/form-fields/fields/slider-field.svelte +53 -0
  46. package/dist/form-fields/{sub → fields}/smart-text-field.svelte +62 -82
  47. package/dist/form-fields/fields/telephone-field.svelte +108 -0
  48. package/dist/form-fields/{sub → fields}/telephone-field.svelte.d.ts +1 -1
  49. package/dist/form-fields/{sub → fields}/text-field.svelte +28 -11
  50. package/dist/form-fields/{sub → fields}/voicenote-field.svelte +117 -46
  51. package/dist/form-fields/file_creation_options.d.ts +1 -1
  52. package/dist/form-fields/form-field.svelte +8 -1
  53. package/dist/form-fields/icons/ChevronDownIcon.svelte +21 -0
  54. package/dist/form-fields/icons/ChevronDownIcon.svelte.d.ts +20 -0
  55. package/dist/form-fields/icons/QrCodeScannerIcon.svelte +25 -0
  56. package/dist/form-fields/icons/QrCodeScannerIcon.svelte.d.ts +19 -0
  57. package/dist/form-fields/icons/ScreenRecordingIcon.svelte +21 -0
  58. package/dist/form-fields/icons/ScreenRecordingIcon.svelte.d.ts +19 -0
  59. package/dist/form-fields/icons/ScreenshotIcon.svelte +22 -0
  60. package/dist/form-fields/icons/ScreenshotIcon.svelte.d.ts +19 -0
  61. package/dist/form-fields/index.d.ts +5 -1
  62. package/dist/form-fields/index.js +3 -1
  63. package/dist/form-fields/interfaces.d.ts +63 -63
  64. package/dist/form-fields/interfaces.js +42 -39
  65. package/dist/form-fields/intl-tel-input.css +1 -0
  66. package/dist/form-fields/styles.d.ts +17 -0
  67. package/dist/form-fields/styles.js +4 -0
  68. package/package.json +2 -4
  69. package/dist/form-fields/sub/checkbox-autocomplete.svelte +0 -56
  70. package/dist/form-fields/sub/checkbox-field.svelte +0 -42
  71. package/dist/form-fields/sub/data-indication-field.svelte +0 -19
  72. package/dist/form-fields/sub/date-field.svelte +0 -31
  73. package/dist/form-fields/sub/date-time-field.svelte +0 -31
  74. package/dist/form-fields/sub/field-builder-field.svelte +0 -657
  75. package/dist/form-fields/sub/file-field.svelte +0 -159
  76. package/dist/form-fields/sub/grid-field.svelte +0 -69
  77. package/dist/form-fields/sub/multistep-field.svelte +0 -70
  78. package/dist/form-fields/sub/note-field.svelte +0 -18
  79. package/dist/form-fields/sub/object-builder-field.svelte +0 -134
  80. package/dist/form-fields/sub/qr-code-scanner-field.svelte +0 -84
  81. package/dist/form-fields/sub/radio-field.svelte +0 -69
  82. package/dist/form-fields/sub/screenrecorder-field.svelte +0 -178
  83. package/dist/form-fields/sub/section-field.svelte +0 -50
  84. package/dist/form-fields/sub/select-field.svelte +0 -150
  85. package/dist/form-fields/sub/slider-field.svelte +0 -28
  86. package/dist/form-fields/sub/telephone-field.svelte +0 -64
  87. /package/dist/form-fields/{sub → fields}/array-builder-field.svelte.d.ts +0 -0
  88. /package/dist/form-fields/{sub → fields}/checkbox-autocomplete.svelte.d.ts +0 -0
  89. /package/dist/form-fields/{sub → fields}/checkbox-field.svelte.d.ts +0 -0
  90. /package/dist/form-fields/{sub → fields}/code-field.svelte +0 -0
  91. /package/dist/form-fields/{sub → fields}/code-field.svelte.d.ts +0 -0
  92. /package/dist/form-fields/{sub → fields}/contact-selector-field.svelte.d.ts +0 -0
  93. /package/dist/form-fields/{sub → fields}/currency-field.svelte.d.ts +0 -0
  94. /package/dist/form-fields/{sub → fields}/data-indication-field.svelte.d.ts +0 -0
  95. /package/dist/form-fields/{sub → fields}/date-field.svelte.d.ts +0 -0
  96. /package/dist/form-fields/{sub → fields}/date-time-field.svelte.d.ts +0 -0
  97. /package/dist/form-fields/{sub → fields}/email-field.svelte.d.ts +0 -0
  98. /package/dist/form-fields/{sub → fields}/field-builder-field.svelte.d.ts +0 -0
  99. /package/dist/form-fields/{sub → fields}/grid-field.svelte.d.ts +0 -0
  100. /package/dist/form-fields/{sub → fields}/heading-field.svelte.d.ts +0 -0
  101. /package/dist/form-fields/{sub → fields}/hidden-field.svelte +0 -0
  102. /package/dist/form-fields/{sub → fields}/hidden-field.svelte.d.ts +0 -0
  103. /package/dist/form-fields/{sub → fields}/hidden-location-field.svelte +0 -0
  104. /package/dist/form-fields/{sub → fields}/hidden-location-field.svelte.d.ts +0 -0
  105. /package/dist/form-fields/{sub → fields}/html-field.svelte.d.ts +0 -0
  106. /package/dist/form-fields/{sub → fields}/jsoneditor-field.svelte +0 -0
  107. /package/dist/form-fields/{sub → fields}/jsoneditor-field.svelte.d.ts +0 -0
  108. /package/dist/form-fields/{sub → fields}/map-field.svelte.d.ts +0 -0
  109. /package/dist/form-fields/{sub → fields}/multi-checkbox-field.svelte.d.ts +0 -0
  110. /package/dist/form-fields/{sub → fields}/multistep-field.svelte.d.ts +0 -0
  111. /package/dist/form-fields/{sub → fields}/note-field.svelte.d.ts +0 -0
  112. /package/dist/form-fields/{sub → fields}/number-field.svelte.d.ts +0 -0
  113. /package/dist/form-fields/{sub → fields}/object-builder-field.svelte.d.ts +0 -0
  114. /package/dist/form-fields/{sub → fields}/qr-code-scanner-field.svelte.d.ts +0 -0
  115. /package/dist/form-fields/{sub → fields}/screenrecorder-field.svelte.d.ts +0 -0
  116. /package/dist/form-fields/{sub → fields}/screenshot-field.svelte.d.ts +0 -0
  117. /package/dist/form-fields/{sub → fields}/scroll-and-read-display-field.svelte.d.ts +0 -0
  118. /package/dist/form-fields/{sub → fields}/section-field.svelte.d.ts +0 -0
  119. /package/dist/form-fields/{sub → fields}/select-field.svelte.d.ts +0 -0
  120. /package/dist/form-fields/{sub → fields}/selectresource-field.svelte +0 -0
  121. /package/dist/form-fields/{sub → fields}/selectresource-field.svelte.d.ts +0 -0
  122. /package/dist/form-fields/{sub → fields}/signature-field.svelte.d.ts +0 -0
  123. /package/dist/form-fields/{sub → fields}/slider-field.svelte.d.ts +0 -0
  124. /package/dist/form-fields/{sub → fields}/smart-text-field.svelte.d.ts +0 -0
  125. /package/dist/form-fields/{sub → fields}/text-field.svelte.d.ts +0 -0
  126. /package/dist/form-fields/{sub → fields}/voicenote-field.svelte.d.ts +0 -0
@@ -0,0 +1,104 @@
1
+ <script context="module">export const select_field_param_spec = {
2
+ spec: {
3
+ fields: {
4
+ options: {
5
+ fieldtype: "arraybuilder",
6
+ params: {
7
+ new_entry_field: {
8
+ fieldtype: "section",
9
+ fields: {
10
+ label: {
11
+ fieldtype: "text"
12
+ },
13
+ value: {
14
+ fieldtype: "jsoneditor"
15
+ }
16
+ }
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ };
23
+ </script>
24
+
25
+ <script>import { isEqual } from "lodash-es";
26
+ import Autocomplete, {} from "../Autocomplete.svelte";
27
+ import FieldLabel from "../FieldLabel.svelte";
28
+ import FieldMessage from "../FieldMessage.svelte";
29
+ import { clear_value_details, set_value_details } from "../utils";
30
+ export let fieldStore;
31
+ export let load_options = void 0;
32
+ $: dropdownLabel = items.find((item) => isEqual(item.value, $fieldStore.value))?.label ?? $fieldStore.value?._default_label;
33
+ $: update_value_details_label(dropdownLabel);
34
+ const update_value_details_label = (label) => {
35
+ if (!label) {
36
+ clear_value_details($fieldStore, "label");
37
+ return;
38
+ }
39
+ set_value_details($fieldStore, "label", label);
40
+ };
41
+ const map_field_options = (options) => {
42
+ if (!options || !Array.isArray(options)) {
43
+ return [];
44
+ }
45
+ return options.filter(Boolean).map((option, index) => {
46
+ const key = `${option.label}${index}`;
47
+ return {
48
+ label: option.label,
49
+ value: option.value,
50
+ key
51
+ };
52
+ });
53
+ };
54
+ $: items = map_field_options($fieldStore.params?.options);
55
+ async function loadAutocompleteOptions(search) {
56
+ return map_field_options(await load_options(search));
57
+ }
58
+ function select_item(item) {
59
+ $fieldStore.value = item.value;
60
+ }
61
+ </script>
62
+
63
+ <div class="select-field">
64
+ <FieldLabel {fieldStore} />
65
+ <Autocomplete
66
+ id={$fieldStore.id}
67
+ options={items}
68
+ value={$fieldStore.value}
69
+ placeholder={dropdownLabel ?? "Select..."}
70
+ loadOptions={load_options ? loadAutocompleteOptions : undefined}
71
+ invalid={$fieldStore.validation_result?.type === "error"}
72
+ ariaDescribedby="message-{$fieldStore.id}"
73
+ on:select={(event) => select_item(event.detail)}
74
+ />
75
+ <div class="select-footer">
76
+ <div class="select-message">
77
+ <FieldMessage {fieldStore} />
78
+ </div>
79
+ <div class="select-actions">
80
+ <slot />
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <style>
86
+ .select-field {
87
+ width: 100%;
88
+ }
89
+
90
+ .select-footer {
91
+ display: flex;
92
+ width: 100%;
93
+ align-items: center;
94
+ }
95
+
96
+ .select-message {
97
+ min-width: 0;
98
+ }
99
+
100
+ .select-actions {
101
+ flex: none;
102
+ margin-inline-start: auto;
103
+ }
104
+ </style>
@@ -9,29 +9,47 @@ export const signature_field_param_spec = {
9
9
  };
10
10
  </script>
11
11
 
12
- <script>import { Textarea } from "@stubber/ui/textarea";
13
- import { Button } from "@stubber/ui/button";
14
- import SignaturePad from "signature_pad";
12
+ <script>import SignaturePad from "signature_pad";
15
13
  import FieldLabel from "../FieldLabel.svelte";
16
14
  import FieldMessage from "../FieldMessage.svelte";
17
- import { onMount } from "svelte";
15
+ import UploadStatus from "../UploadStatus.svelte";
16
+ import { getContext, onDestroy, onMount } from "svelte";
18
17
  import { append_attachment, remove_attachment, uploadFiles } from "../fileserver";
19
18
  import { debounce, snakeCase } from "lodash-es";
20
19
  import { file_creation_options_field_spec } from "../file_creation_options";
20
+ import { STYLES_CONTEXT, classNames } from "../styles";
21
21
  export let fieldStore;
22
- let canvasContainer;
22
+ const styles = getContext(STYLES_CONTEXT);
23
23
  let pad;
24
24
  let signaturePad;
25
+ let resizeObserver;
25
26
  let file_creation_options = $fieldStore.params?.file_creation_options;
26
27
  onMount(() => {
27
28
  signaturePad = new SignaturePad(pad);
28
29
  signaturePad.addEventListener("endStroke", handleStroke);
30
+ resizeObserver = new ResizeObserver(resizeCanvas);
31
+ resizeObserver.observe(pad);
32
+ resizeCanvas();
29
33
  if ($fieldStore.value?.data) {
30
34
  setTimeout(() => {
31
35
  signaturePad.fromData($fieldStore.value?.data);
32
36
  }, 0);
33
37
  }
34
38
  });
39
+ onDestroy(() => {
40
+ debounced_uploadFile.cancel();
41
+ resizeObserver?.disconnect();
42
+ signaturePad?.removeEventListener("endStroke", handleStroke);
43
+ signaturePad?.off();
44
+ });
45
+ function resizeCanvas() {
46
+ const data = signaturePad.toData();
47
+ const ratio = Math.max(window.devicePixelRatio || 1, 1);
48
+ pad.width = pad.offsetWidth * ratio;
49
+ pad.height = pad.offsetHeight * ratio;
50
+ pad.getContext("2d")?.scale(ratio, ratio);
51
+ if (data.length) signaturePad.fromData(data);
52
+ }
35
53
  function clear() {
36
54
  signaturePad.clear();
37
55
  if ($fieldStore.value) {
@@ -51,7 +69,6 @@ function handleStroke() {
51
69
  debounced_uploadFile();
52
70
  }
53
71
  let is_uploading = false;
54
- $: canvasWidth = canvasContainer?.clientWidth;
55
72
  async function uploadFile() {
56
73
  const fileURI = signaturePad.toDataURL();
57
74
  const fileBlob = await (await fetch(fileURI)).blob();
@@ -78,21 +95,60 @@ async function uploadFile() {
78
95
  const debounced_uploadFile = debounce(uploadFile, 1e3);
79
96
  </script>
80
97
 
81
- <FieldLabel {fieldStore} />
82
- <div bind:this={canvasContainer}>
83
- <canvas
84
- bind:this={pad}
85
- id="signature-pad"
86
- class="signature-pad mt-2 rounded-md border"
87
- width={canvasWidth}
88
- height={200}
89
- />
90
- <div class="mt-1 flex items-center justify-between">
91
- <Button variant="ghost" on:click={clear} type="button">Clear</Button>
92
-
93
- {#if is_uploading}
94
- <i class="fa fa-pulse fa-spinner text-surface-500" />
95
- {/if}
98
+ <div class="signature-field">
99
+ <FieldLabel {fieldStore} />
100
+ <div class="signature-pad">
101
+ <canvas
102
+ bind:this={pad}
103
+ id={$fieldStore.id}
104
+ class="signature-canvas"
105
+ aria-describedby="message-{$fieldStore.id}"
106
+ aria-invalid={$fieldStore.validation_result?.type === "error" ? true : undefined}
107
+ />
108
+ <div class="signature-actions">
109
+ <button
110
+ class={classNames("signature-clear", $styles.button)}
111
+ data-variant="destructive"
112
+ on:click={clear}
113
+ type="button">Clear</button
114
+ >
115
+ <span class="signature-status" hidden={!is_uploading} aria-live="polite">
116
+ <UploadStatus status="uploading" />
117
+ <span>Uploading signature…</span>
118
+ </span>
119
+ </div>
96
120
  </div>
121
+ <FieldMessage {fieldStore} />
97
122
  </div>
98
- <FieldMessage {fieldStore} />
123
+
124
+ <style>
125
+ .signature-field,
126
+ .signature-pad,
127
+ .signature-canvas {
128
+ box-sizing: border-box;
129
+ width: 100%;
130
+ }
131
+
132
+ .signature-canvas {
133
+ display: block;
134
+ height: 12rem;
135
+ touch-action: none;
136
+ }
137
+
138
+ .signature-actions {
139
+ display: flex;
140
+ justify-content: space-between;
141
+ align-items: center;
142
+ margin-top: 0.5rem;
143
+ }
144
+
145
+ .signature-status {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 0.5rem;
149
+ }
150
+
151
+ .signature-status[hidden] {
152
+ display: none;
153
+ }
154
+ </style>
@@ -0,0 +1,53 @@
1
+ <script context="module"></script>
2
+
3
+ <script>import { getContext } from "svelte";
4
+ import FieldLabel from "../FieldLabel.svelte";
5
+ import FieldMessage from "../FieldMessage.svelte";
6
+ import { STYLES_CONTEXT, classNames } from "../styles";
7
+ export let fieldStore;
8
+ const styles = getContext(STYLES_CONTEXT);
9
+ $: min = $fieldStore.min ?? 0;
10
+ $: max = $fieldStore.max ?? 100;
11
+ $: step = $fieldStore.step ?? 1;
12
+ </script>
13
+
14
+ <div class="slider-field">
15
+ <FieldLabel {fieldStore} />
16
+ <div class="slider-control">
17
+ <div class="slider-values">
18
+ <span class="slider-boundary">{min}</span>
19
+ <output class="slider-value" for={$fieldStore.id}>
20
+ {$fieldStore.value ?? ""}
21
+ </output>
22
+ <span class="slider-boundary">{max}</span>
23
+ </div>
24
+ <input
25
+ class={classNames("slider-input", $styles.range)}
26
+ id={$fieldStore.id}
27
+ bind:value={$fieldStore.value}
28
+ {min}
29
+ {max}
30
+ {step}
31
+ type="range"
32
+ aria-invalid={$fieldStore.validation_result?.type === "error" ? true : undefined}
33
+ aria-describedby="message-{$fieldStore.id}"
34
+ />
35
+ </div>
36
+ <FieldMessage {fieldStore} />
37
+ </div>
38
+
39
+ <style>
40
+ .slider-field,
41
+ .slider-control,
42
+ .slider-input {
43
+ box-sizing: border-box;
44
+ width: 100%;
45
+ }
46
+
47
+ .slider-values {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ width: 100%;
52
+ }
53
+ </style>
@@ -58,9 +58,10 @@ export const smart_text_field_param_spec = {
58
58
  </script>
59
59
 
60
60
  <script>import { find } from "lodash-es";
61
+ import { getContext } from "svelte";
61
62
  import FieldLabel from "../FieldLabel.svelte";
62
63
  import FieldMessage from "../FieldMessage.svelte";
63
- import { getContext } from "svelte";
64
+ import { STYLES_CONTEXT, classNames } from "../styles";
64
65
  export let fieldStore;
65
66
  let editor_view;
66
67
  let codemirror_loader;
@@ -76,6 +77,7 @@ function loadCodeMirror() {
76
77
  autocompletion: autocomplete.autocompletion,
77
78
  completionKeymap: autocomplete.completionKeymap,
78
79
  history: commands.history,
80
+ Compartment: state.Compartment,
79
81
  EditorState: state.EditorState,
80
82
  StateField: state.StateField,
81
83
  Decoration: view.Decoration,
@@ -93,17 +95,22 @@ $: type = validation_result?.type;
93
95
  $: isValid = type !== "error";
94
96
  $: is_object = typeof $fieldStore.value === "object";
95
97
  const smart_text_fields_ctx_store = getContext("smart_text_fields_ctx");
98
+ const styles = getContext(STYLES_CONTEXT);
96
99
  $: parse_string = $fieldStore.params?.parse_string ?? true;
97
100
  $: code_language = $fieldStore.params?.code_language || "handlebars";
98
- const setup_editor = (element) => {
101
+ const setup_editor = (element, initial_appearance) => {
99
102
  let disposed = false;
100
103
  let local_editor_view;
104
+ let appearance = initial_appearance;
105
+ let appearance_compartment;
106
+ let appearance_extension;
101
107
  void loadCodeMirror().then(
102
108
  ({
103
109
  acceptCompletion,
104
110
  autocompletion,
105
111
  completionKeymap,
106
112
  history,
113
+ Compartment,
107
114
  EditorState,
108
115
  StateField,
109
116
  Decoration,
@@ -114,6 +121,11 @@ const setup_editor = (element) => {
114
121
  ViewPlugin
115
122
  }) => {
116
123
  if (disposed) return;
124
+ appearance_compartment = new Compartment();
125
+ appearance_extension = () => EditorView.editorAttributes.of({
126
+ class: appearance.input,
127
+ "aria-invalid": appearance.invalid ? "true" : "false"
128
+ });
117
129
  const contentField = createContentField(StateField);
118
130
  const tab_accept_completion = keymap.of(
119
131
  completionKeymap.filter((binding) => binding.key != "Enter").concat([
@@ -132,12 +144,19 @@ const setup_editor = (element) => {
132
144
  const extensions = [
133
145
  history({ newGroupDelay: 150 }),
134
146
  EditorView.lineWrapping,
147
+ EditorView.theme({
148
+ "&": { minWidth: "0", width: "100%" },
149
+ ".cm-scroller": { overflow: "auto" }
150
+ }),
151
+ appearance_compartment.of(appearance_extension()),
135
152
  contentField,
136
153
  autocompletion({ override: [myCompletions], defaultKeymap: false }),
137
154
  tab_accept_completion,
138
155
  placeholder($fieldStore.label),
139
156
  EditorView.contentAttributes.of({
140
- spellcheck: "true"
157
+ id: $fieldStore.id,
158
+ spellcheck: "true",
159
+ "aria-describedby": `message-${$fieldStore.id}`
141
160
  })
142
161
  ];
143
162
  if (code_language === "handlebars") {
@@ -155,6 +174,14 @@ const setup_editor = (element) => {
155
174
  }
156
175
  );
157
176
  return {
177
+ update(next_appearance) {
178
+ appearance = next_appearance;
179
+ if (local_editor_view && appearance_compartment && appearance_extension) {
180
+ local_editor_view.dispatch({
181
+ effects: appearance_compartment.reconfigure(appearance_extension())
182
+ });
183
+ }
184
+ },
158
185
  destroy() {
159
186
  disposed = true;
160
187
  local_editor_view?.destroy();
@@ -196,6 +223,17 @@ function update_editor_content(new_value) {
196
223
  }
197
224
  }
198
225
  }
226
+ $: update_editor_attributes($fieldStore.id, !isValid);
227
+ function update_editor_attributes(id, invalid) {
228
+ if (!editor_view) return;
229
+ editor_view.contentDOM.id = id;
230
+ editor_view.contentDOM.setAttribute("aria-describedby", `message-${id}`);
231
+ if (invalid) {
232
+ editor_view.contentDOM.setAttribute("aria-invalid", "true");
233
+ } else {
234
+ editor_view.contentDOM.removeAttribute("aria-invalid");
235
+ }
236
+ }
199
237
  function fake_handlebars_lang(Decoration, MatchDecorator, ViewPlugin) {
200
238
  const curly_decorator = new MatchDecorator({
201
239
  regexp: /(\{\{[^}]*\}\})|(~~[^\s]*)/g,
@@ -248,89 +286,31 @@ function parse_string_value(value) {
248
286
  }
249
287
  </script>
250
288
 
251
- <FieldLabel {fieldStore} />
252
- {#if !is_object}
253
- <div use:setup_editor class="stubber-cm" aria-invalid={!isValid} />
254
- {:else}
255
- <p class="text-surface-400">The value of this field is not a string.</p>
256
- {/if}
257
- <FieldMessage {fieldStore} />
258
-
259
- <!-- below styles are basically this tailwind string:
260
- flex h-9 w-full min-w-0 flex-row items-center gap-2 rounded-md border border-input bg-background px-3 py-1 text-base outline-none ring-offset-background transition-[color,box-shadow] selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-[invalid]:border-destructive aria-[invalid]:ring-destructive/50 aria-[invalid]:focus-visible:ring-destructive/50 md:text-sm dark:bg-input/30 -->
289
+ <div class="smart-text-field">
290
+ <FieldLabel {fieldStore} />
291
+ {#if !is_object}
292
+ <div
293
+ class="smart-text-editor"
294
+ use:setup_editor={{ input: classNames("smart-text-input", $styles.input), invalid: !isValid }}
295
+ />
296
+ {:else}
297
+ <p class={classNames("smart-text-type-error", $styles.message)} data-variant="error">
298
+ The value of this field is not a string.
299
+ </p>
300
+ {/if}
301
+ <FieldMessage {fieldStore} />
302
+ </div>
261
303
 
262
304
  <style>
263
- .stubber-cm :global(.cm-editor) {
264
- width: 100%;
305
+ .smart-text-field {
306
+ display: flex;
307
+ flex-direction: column;
265
308
  min-width: 0;
266
- border-radius: 0.375rem;
267
- border-width: 1px;
268
- --tw-border-opacity: 1;
269
- border-color: hsl(var(--input) / var(--tw-border-opacity, 1));
270
- --tw-bg-opacity: 1;
271
- background-color: hsl(var(--background) / var(--tw-bg-opacity, 1));
272
-
273
- font-size: 0.875rem;
274
- line-height: 1.25rem;
275
- outline: 2px solid transparent;
276
- outline-offset: 2px;
277
- --tw-ring-offset-color: hsl(var(--background) / 1);
278
- transition-property: color, box-shadow;
279
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
280
- transition-duration: 150ms;
281
- }
282
-
283
- @media (prefers-color-scheme: dark) {
284
- .stubber-cm :global(.cm-editor) {
285
- background-color: hsl(var(--input) / 0.3);
286
- }
287
- }
288
-
289
- .stubber-cm[aria-invalid="true"] :global(.cm-editor) {
290
- --tw-border-opacity: 1;
291
- border-color: hsl(var(--destructive) / var(--tw-border-opacity, 1));
292
- --tw-ring-color: hsl(var(--destructive) / 0.5);
293
- }
294
-
295
- .stubber-cm :global(.cm-editor.cm-focused) {
296
- --tw-border-opacity: 1;
297
- border-color: hsl(var(--ring) / var(--tw-border-opacity, 1));
298
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
299
- var(--tw-ring-offset-color);
300
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
301
- var(--tw-ring-color);
302
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
303
- --tw-ring-color: hsl(var(--ring) / 0.5);
304
- }
305
-
306
- .stubber-cm[aria-invalid="true"] :global(.cm-editor.cm-focused) {
307
- --tw-ring-color: hsl(var(--destructive) / 0.5);
308
- --tw-border-opacity: 1;
309
- border-color: hsl(var(--destructive) / var(--tw-border-opacity, 1));
310
- }
311
-
312
- .stubber-cm :global(.cm-editor .cm-scroller) {
313
- font-family: inherit;
314
- line-height: inherit;
315
- }
316
-
317
- .stubber-cm :global(.cm-editor .cm-content) {
318
- padding: 0;
319
- padding-top: 0.5rem;
320
- padding-bottom: 0.5rem;
321
- }
322
-
323
- .stubber-cm :global(.cm-editor .cm-line) {
324
- padding: 0;
325
- padding-left: 0.75rem;
326
- }
327
-
328
- .stubber-cm :global(.cm-editor .cm-placeholder) {
329
- --tw-text-opacity: 1;
330
- color: hsl(var(--muted-foreground) / var(--tw-text-opacity, 1));
309
+ width: 100%;
331
310
  }
332
311
 
333
- .stubber-cm :global(.curly-braces-highlight) {
334
- color: rgb(9 103 210);
312
+ .smart-text-editor {
313
+ min-width: 0;
314
+ width: 100%;
335
315
  }
336
316
  </style>
@@ -0,0 +1,108 @@
1
+ <script context="module">import {} from "../interfaces";
2
+ </script>
3
+
4
+ <script>import { getContext } from "svelte";
5
+ import intlTelInput, {} from "intl-tel-input";
6
+ import "../intl-tel-input.css";
7
+ import FieldLabel from "../FieldLabel.svelte";
8
+ import FieldMessage from "../FieldMessage.svelte";
9
+ import { STYLES_CONTEXT, classNames } from "../styles";
10
+ import { set_value_details } from "../utils";
11
+ export let fieldStore;
12
+ const styles = getContext(STYLES_CONTEXT);
13
+ let iti = null;
14
+ function setupInput(input_element, initial_appearance) {
15
+ const handle_country_change = () => set_number_details();
16
+ input_element.addEventListener("countrychange", handle_country_change);
17
+ let appearance = initial_appearance;
18
+ const instance = intlTelInput(input_element, {
19
+ loadUtils: () => import("intl-tel-input/utils"),
20
+ initialCountryLookup: async () => {
21
+ const res = await fetch("https://ipapi.co/json");
22
+ const data = await res.json();
23
+ return data?.country_code || "us";
24
+ },
25
+ separateDialCode: false,
26
+ classNames: {
27
+ searchWrapper: appearance.search,
28
+ searchInput: appearance.searchInput
29
+ }
30
+ });
31
+ iti = instance;
32
+ const container = input_element.parentElement;
33
+ container?.style.setProperty("width", "100%");
34
+ void instance.promise.then(() => {
35
+ if (iti === instance) set_number_details();
36
+ });
37
+ return {
38
+ update(next_appearance) {
39
+ const container2 = input_element.parentElement;
40
+ const search = container2?.querySelector(".iti__search-input-wrapper");
41
+ const search_input = container2?.querySelector(".iti__search-input");
42
+ replaceClasses(search, appearance.search, next_appearance.search);
43
+ replaceClasses(search_input, appearance.searchInput, next_appearance.searchInput);
44
+ appearance = next_appearance;
45
+ },
46
+ destroy() {
47
+ input_element.removeEventListener("countrychange", handle_country_change);
48
+ instance.destroy();
49
+ if (iti === instance) iti = null;
50
+ }
51
+ };
52
+ }
53
+ function replaceClasses(element, previous, next) {
54
+ if (!element) return;
55
+ element.classList.remove(...previous.split(/\s+/).filter(Boolean));
56
+ element.classList.add(...next.split(/\s+/).filter(Boolean));
57
+ }
58
+ let last_set_value = null;
59
+ function set_number_details(force = false) {
60
+ if (!iti) return;
61
+ if (!force && last_set_value === $fieldStore.value) return;
62
+ last_set_value = $fieldStore.value;
63
+ const formatted_number = iti.getNumber();
64
+ const is_valid = iti.isValidNumber();
65
+ const country_data = iti.getSelectedCountry();
66
+ if (is_valid && formatted_number !== $fieldStore.value) {
67
+ $fieldStore.value = formatted_number;
68
+ }
69
+ if ($fieldStore.value) {
70
+ set_value_details($fieldStore, "details", {
71
+ is_valid_number: is_valid,
72
+ selected_country: country_data
73
+ });
74
+ }
75
+ }
76
+ </script>
77
+
78
+ <div class="telephone-field">
79
+ <FieldLabel {fieldStore} />
80
+ <input
81
+ use:setupInput={{
82
+ search: "telephone-search",
83
+ searchInput: classNames("telephone-search-input", $styles.input),
84
+ }}
85
+ bind:value={$fieldStore.value}
86
+ on:input={() => set_number_details()}
87
+ type="tel"
88
+ id={$fieldStore.id}
89
+ class={classNames("telephone-input", $styles.input)}
90
+ aria-invalid={$fieldStore.validation_result?.type == "error" ? true : undefined}
91
+ aria-describedby="message-{$fieldStore.id}"
92
+ />
93
+ <FieldMessage {fieldStore} />
94
+ </div>
95
+
96
+ <style>
97
+ .telephone-field,
98
+ .telephone-input {
99
+ box-sizing: border-box;
100
+ width: 100%;
101
+ }
102
+
103
+ .telephone-field :global(.telephone-search),
104
+ .telephone-field :global(.telephone-search-input) {
105
+ box-sizing: border-box;
106
+ width: 100%;
107
+ }
108
+ </style>
@@ -6,7 +6,7 @@ export interface ITelephoneFieldParams {
6
6
  export interface ITelephoneField extends IBaseField<ITelephoneFieldParams> {
7
7
  fieldtype: "telephone";
8
8
  }
9
- import "intl-tel-input/styles";
9
+ import "../intl-tel-input.css";
10
10
  declare const __propDef: {
11
11
  props: {
12
12
  fieldStore: Writable<IBuiltField<ITelephoneFieldParams>>;
@@ -11,10 +11,12 @@ export const text_field_param_spec = {
11
11
  };
12
12
  </script>
13
13
 
14
- <script>import { Input } from "@stubber/ui/input";
14
+ <script>import { getContext } from "svelte";
15
15
  import FieldLabel from "../FieldLabel.svelte";
16
16
  import FieldMessage from "../FieldMessage.svelte";
17
+ import { STYLES_CONTEXT } from "../styles";
17
18
  export let fieldStore;
19
+ const styles = getContext(STYLES_CONTEXT);
18
20
  function parse_string_value(value) {
19
21
  if (value === "true") return true;
20
22
  if (value === "false") return false;
@@ -34,13 +36,28 @@ function on_input(e) {
34
36
  }
35
37
  </script>
36
38
 
37
- <FieldLabel {fieldStore} />
38
- <Input
39
- id={$fieldStore.id}
40
- value={$fieldStore.value}
41
- placeholder={$fieldStore.label}
42
- on:input={on_input}
43
- aria-invalid={$fieldStore.validation_result?.type == "error" ? true : undefined}
44
- aria-describedby="message-{$fieldStore.id}"
45
- />
46
- <FieldMessage {fieldStore} />
39
+ <div class="text-field">
40
+ <FieldLabel {fieldStore} />
41
+ <input
42
+ class={$styles.input}
43
+ id={$fieldStore.id}
44
+ value={$fieldStore.value ?? ""}
45
+ placeholder={$fieldStore.label}
46
+ on:input={on_input}
47
+ aria-invalid={$fieldStore.validation_result?.type == "error" ? true : undefined}
48
+ aria-describedby={$fieldStore.validation_result ? `message-${$fieldStore.id}` : undefined}
49
+ />
50
+ <FieldMessage {fieldStore} />
51
+ </div>
52
+
53
+ <style>
54
+ .text-field {
55
+ width: 100%;
56
+ }
57
+
58
+ input {
59
+ box-sizing: border-box;
60
+ display: block;
61
+ width: 100%;
62
+ }
63
+ </style>