@poirazis/supercomponents-shared 1.2.12 → 1.2.13

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 (31) hide show
  1. package/dist/index.js +11250 -11204
  2. package/dist/index.umd.cjs +15 -15
  3. package/package.json +1 -1
  4. package/src/lib/SuperForm/InnerForm.svelte +68 -19
  5. package/src/lib/SuperForm/SuperForm.svelte +13 -3
  6. package/src/lib/SuperTable/SuperTable.css +9 -6
  7. package/src/lib/SuperTable/SuperTable.svelte +207 -193
  8. package/src/lib/SuperTable/controls/ColumnsSection.svelte +11 -19
  9. package/src/lib/SuperTable/controls/RowButtonsColumn.svelte +4 -4
  10. package/src/lib/SuperTable/controls/SelectionColumn.svelte +9 -10
  11. package/src/lib/SuperTable/overlays/AddNewRowOverlay.svelte +1 -1
  12. package/src/lib/SuperTableCells/CellBoolean.svelte +0 -1
  13. package/src/lib/SuperTableCells/CellCommon.css +59 -88
  14. package/src/lib/SuperTableCells/CellDateRange.svelte +3 -4
  15. package/src/lib/SuperTableCells/CellDatetime.svelte +3 -4
  16. package/src/lib/SuperTableCells/CellJSON.svelte +1 -4
  17. package/src/lib/SuperTableCells/CellLink.svelte +20 -25
  18. package/src/lib/SuperTableCells/CellLinkAdvanced.svelte +3 -7
  19. package/src/lib/SuperTableCells/CellLinkPickerSelect.svelte +7 -4
  20. package/src/lib/SuperTableCells/CellNumber.svelte +18 -23
  21. package/src/lib/SuperTableCells/CellOptions.svelte +34 -31
  22. package/src/lib/SuperTableCells/CellSQLLink.svelte +84 -64
  23. package/src/lib/SuperTableCells/CellSQLLinkPicker.svelte +66 -36
  24. package/src/lib/SuperTableCells/CellString.svelte +9 -25
  25. package/src/lib/SuperTableCells/CellStringMask.svelte +0 -4
  26. package/src/lib/SuperTableCells/CellStringSimple.svelte +6 -8
  27. package/src/lib/SuperTableColumn/SuperTableColumn.svelte +10 -10
  28. package/src/lib/SuperTableColumn/parts/SuperColumnHeader.svelte +4 -1
  29. package/src/lib/SuperTableColumn/parts/SuperColumnRow.svelte +25 -21
  30. package/src/lib/SuperTabs/SuperTabs.svelte +7 -12
  31. package/src/lib/SuperTable/controls/SuperTableWelcome.svelte +0 -58
@@ -76,7 +76,7 @@
76
76
  const unselectRow = (val) => {
77
77
  localValue.splice(
78
78
  localValue.findIndex((e) => e._id === val._id),
79
- 1
79
+ 1,
80
80
  );
81
81
  localValue = localValue;
82
82
  dispatch("change", localValue);
@@ -132,7 +132,9 @@
132
132
  };
133
133
 
134
134
  const handleNavigation = (e) => {
135
- if (e.key == "ArrowDown") {
135
+ if (e.key == "Escape") {
136
+ dispatch("close");
137
+ } else if (e.key == "ArrowDown") {
136
138
  e.preventDefault();
137
139
  focusIdx += 1;
138
140
  if (focusIdx > $fetch.rows.length - 1) focusIdx = 0;
@@ -191,6 +193,7 @@
191
193
  : "Search"}
192
194
  on:input={handleSearch}
193
195
  on:keydown={handleNavigation}
196
+ on:focusout
194
197
  />
195
198
  </div>
196
199
 
@@ -209,7 +212,7 @@
209
212
  on:mouseenter={() => (focusIdx = idx)}
210
213
  on:mouseleave={() => (focusIdx = -1)}
211
214
  on:mousedown|preventDefault|stopPropagation={selectRow(
212
- row
215
+ row,
213
216
  )}
214
217
  >
215
218
  {row[primaryDisplay]}
@@ -243,7 +246,7 @@
243
246
  transition:fly={{ x: -20, duration: 130 }}
244
247
  class="option wide selected"
245
248
  on:mousedown|stopPropagation|preventDefault={unselectRow(
246
- val
249
+ val,
247
250
  )}
248
251
  >
249
252
  {val.primaryDisplay}
@@ -7,18 +7,11 @@
7
7
  } from "svelte";
8
8
  import fsm from "svelte-fsm";
9
9
 
10
- /**
11
- * @typedef {import('./types.js').CellOptions} CellOptions
12
- * @typedef {import('./types.js').CellApi} CellApi
13
- */
14
-
15
10
  const { processStringSync } = getContext("sdk");
16
11
  const context = getContext("context");
17
12
  const dispatch = createEventDispatcher();
18
13
 
19
- /** @type {number | null} */
20
14
  export let value;
21
- /** @type {CellOptions} */
22
15
  export let cellOptions = {};
23
16
  export let autofocus = false;
24
17
 
@@ -48,15 +41,10 @@
48
41
  decimals,
49
42
  thousandsSeparator,
50
43
  role,
44
+ calculated,
51
45
  } = cellOptions ?? {});
52
46
 
53
47
  // Helper function to format number with thousands separator
54
- /**
55
- * @param {number | string | null | undefined} num
56
- * @param {string | undefined} separator
57
- * @param {number | undefined} decimals
58
- * @returns {string}
59
- */
60
48
  function formatNumber(num, separator, decimals) {
61
49
  // Parse string to number if needed
62
50
  const parsedNum = typeof num === "string" ? parseFloat(num) : num;
@@ -90,11 +78,12 @@
90
78
  $: formattedValue = template
91
79
  ? processStringSync(template, {
92
80
  ...$context,
93
- value,
81
+ value: formatNumber(displayValue, thousandsSeparator, decimals),
94
82
  })
95
83
  : formatNumber(displayValue, thousandsSeparator, decimals);
96
84
 
97
85
  $: stepValue = stepSize ?? 1;
86
+ $: clearValue = cellOptions.clearValue && cellOptions.role != "tableCell";
98
87
 
99
88
  // Reset when value changes externally
100
89
  $: cellState.reset(value);
@@ -119,7 +108,7 @@
119
108
  return state;
120
109
  },
121
110
  focus() {
122
- if (!readonly && !disabled) return "Editing";
111
+ if (!readonly && !disabled && !calculated) return "Editing";
123
112
  },
124
113
  },
125
114
  Editing: {
@@ -275,7 +264,6 @@
275
264
  },
276
265
  });
277
266
 
278
- /** @type {CellApi} */
279
267
  export const cellApi = {
280
268
  focus: () => cellState.focus(),
281
269
  reset: () => cellState.reset(value),
@@ -320,7 +308,7 @@
320
308
  class:formInput={role == "formInput"}
321
309
  style:color
322
310
  style:background
323
- tabIndex={disabled ? -1 : 0}
311
+ tabIndex={disabled ? -1 : (cellOptions.order ?? 0)}
324
312
  on:focusin={cellState.focus}
325
313
  >
326
314
  {#if icon}
@@ -344,12 +332,15 @@
344
332
  on:wheel={(e) => cellState.handleWheel(e)}
345
333
  use:focus
346
334
  />
335
+
347
336
  <!-- svelte-ignore a11y-no-static-element-interactions -->
348
337
  <i
349
- class="ri-close-line clear-icon visible"
338
+ class="ri-close-line clear-icon"
339
+ class:visible={clearValue}
350
340
  on:mousedown|preventDefault|stopPropagation={cellState.clear}
351
341
  >
352
342
  </i>
343
+
353
344
  {#if showStepper}
354
345
  <div class="controls">
355
346
  <!-- svelte-ignore a11y-no-static-element-interactions -->
@@ -369,7 +360,6 @@
369
360
  {:else}
370
361
  <div
371
362
  class="value"
372
- style:padding-right={"12px"}
373
363
  class:placeholder={!value && value !== 0}
374
364
  style:justify-content={cellOptions.align ?? "flex-end"}
375
365
  >
@@ -380,22 +370,27 @@
380
370
 
381
371
  <style>
382
372
  .controls {
373
+ height: 100%;
383
374
  display: flex;
384
375
  flex-direction: row;
376
+ align-items: stretch;
385
377
  gap: 0.25rem;
386
- padding-right: 0.5rem;
387
- margin-left: -0.25rem;
378
+ padding: 0.25rem 0rem;
388
379
  }
389
380
 
390
381
  .controls i {
382
+ display: flex;
383
+ align-items: center;
384
+ justify-content: center;
391
385
  cursor: pointer;
392
- padding: 0.15rem;
386
+ padding: 0.25rem;
393
387
  background-color: var(--spectrum-global-color-gray-100);
394
388
  border-radius: 0.25rem;
395
389
  transition: all 0.2s ease-in-out;
396
390
  }
397
391
 
398
392
  .controls i:hover {
399
- background-color: var(--spectrum-global-color-gray-200);
393
+ background-color: var(--spectrum-global-color-gray-300);
394
+ color: var(--spectrum-global-color-gray-900);
400
395
  }
401
396
  </style>
@@ -106,6 +106,7 @@
106
106
  View: {
107
107
  _enter() {
108
108
  searchTerm = null;
109
+ editorState.filterOptions();
109
110
  },
110
111
  focus(e) {
111
112
  if (!readonly && !disabled) {
@@ -115,23 +116,22 @@
115
116
  },
116
117
  Editing: {
117
118
  _enter() {
119
+ editorState.open();
118
120
  originalValue = JSON.stringify(
119
121
  Array.isArray(value) ? value : value ? [value] : [],
120
122
  );
121
123
  inputValue = multi ? "" : labels[localValue[0]] || localValue[0] || "";
122
- // Open the popup if the focus in came from a TAB
123
- editorState.open();
124
+
124
125
  dispatch("enteredit");
125
126
  },
126
127
  _exit() {
127
128
  editorState.close();
128
- editorState.filterOptions();
129
129
  dispatch("exitedit");
130
130
  },
131
+ toggle(e) {
132
+ editorState.toggle();
133
+ },
131
134
  focusout(e) {
132
- // If the focus is moving to the search input inside the popup, ignore
133
- if (anchor.contains(e.relatedTarget)) return;
134
-
135
135
  dispatch("focusout");
136
136
 
137
137
  // For debounced inputs, dispatch the current value immediately on focusout
@@ -144,6 +144,12 @@
144
144
 
145
145
  return "View";
146
146
  },
147
+ popupfocusout(e) {
148
+ if (anchor != e?.relatedTarget) {
149
+ this.submit();
150
+ return "View";
151
+ }
152
+ },
147
153
  submit() {
148
154
  if (isDirty && !cellOptions.debounce) {
149
155
  if (multi) dispatch("change", localValue);
@@ -344,11 +350,8 @@
344
350
  searchTerm = null;
345
351
  focusedOptionIdx = -1;
346
352
  },
347
- toggle(e) {
348
- if (inEdit) {
349
- e.preventDefault();
350
- return "Open";
351
- }
353
+ toggle() {
354
+ return "Open";
352
355
  },
353
356
  open() {
354
357
  return "Open";
@@ -505,6 +508,8 @@
505
508
  $: isDirty = inEdit && originalValue !== JSON.stringify(localValue);
506
509
  $: inEdit = $cellState == "Editing";
507
510
  $: pills = optionsViewMode == "pills";
511
+ $: bullets = optionsViewMode == "bullets";
512
+
508
513
  $: multi =
509
514
  fieldSchema && fieldSchema.type ? fieldSchema.type == "array" : multi;
510
515
 
@@ -542,10 +547,12 @@
542
547
  class:inline={role == "inlineInput"}
543
548
  class:tableCell={role == "tableCell"}
544
549
  class:formInput={role == "formInput"}
550
+ class:has-popup={controlType == "select"}
551
+ class:open-popup={open}
545
552
  on:focusin={cellState.focus}
546
553
  on:focusout={cellState.focusout}
547
554
  on:keydown={editorState.handleKeyboard}
548
- on:mousedown={editorState.toggle}
555
+ on:mousedown={cellState.toggle}
549
556
  >
550
557
  {#if icon}
551
558
  <i class={icon + " field-icon"} class:active={searchTerm}></i>
@@ -592,7 +599,7 @@
592
599
  use:focus
593
600
  {placeholder}
594
601
  />
595
- <div class="action-icon" on:click={editorState.toggle}>
602
+ <div class="control-icon" on:click={editorState.toggle}>
596
603
  <i class="ph ph-caret-down"></i>
597
604
  </div>
598
605
  {:else}
@@ -611,7 +618,7 @@
611
618
  <div
612
619
  class="items"
613
620
  class:pills
614
- class:colorText={optionsViewMode == "colorText"}
621
+ class:bullets
615
622
  style:justify-content={cellOptions.align ?? "flex-start"}
616
623
  >
617
624
  {#each localValue as val, idx (val)}
@@ -620,7 +627,7 @@
620
627
  style:--option-color={$colors[val] ||
621
628
  colorsArray[idx % colorsArray.length]}
622
629
  >
623
- <i class={"ph-fill ph-square"}></i>
630
+ <div class="loope"></div>
624
631
  <span> {isObjects ? "JSON" : labels[val] || val} </span>
625
632
  </div>
626
633
  {/each}
@@ -628,7 +635,7 @@
628
635
  {/if}
629
636
  </div>
630
637
  {#if !readonly && (role == "formInput" || inEdit)}
631
- <i class="ph ph-caret-down control-icon"></i>
638
+ <i class="ph ph-caret-down control-icon" on:mousedown></i>
632
639
  {/if}
633
640
  {/if}
634
641
  </div>
@@ -771,21 +778,6 @@
771
778
  font-style: italic;
772
779
  }
773
780
 
774
- .action-icon {
775
- height: 100%;
776
- display: flex;
777
- justify-content: center;
778
- align-items: center;
779
- border-left: 1px solid var(--spectrum-global-color-blue-500);
780
- min-width: 2rem;
781
- font-size: 16px;
782
- }
783
- .action-icon:hover {
784
- cursor: pointer;
785
- background-color: var(--spectrum-global-color-gray-75);
786
- font-weight: 800;
787
- }
788
-
789
781
  .search-icon {
790
782
  font-size: 16px;
791
783
  color: var(--spectrum-global-color-gray-500);
@@ -812,4 +804,15 @@
812
804
  width: 100%;
813
805
  overflow: hidden;
814
806
  }
807
+
808
+ .loope {
809
+ width: 14px;
810
+ height: 14px;
811
+ border-radius: 2px;
812
+ background-color: var(
813
+ --option-color,
814
+ var(--spectrum-global-color-gray-300)
815
+ );
816
+ flex-shrink: 0;
817
+ }
815
818
  </style>
@@ -23,6 +23,8 @@
23
23
  let popup;
24
24
  let search;
25
25
  let pickerApi;
26
+ let isLoading = false;
27
+ let localValue;
26
28
 
27
29
  const editorState = fsm("Closed", {
28
30
  Open: {
@@ -54,7 +56,10 @@
54
56
  },
55
57
  View: {
56
58
  _enter() {},
57
- focus() {
59
+ toggle() {
60
+ return;
61
+ },
62
+ focus(e) {
58
63
  if (!cellOptions.readonly && !cellOptions.disabled) return "Editing";
59
64
  },
60
65
  },
@@ -68,11 +73,18 @@
68
73
  editorState.close();
69
74
  dispatch("exitedit");
70
75
  },
76
+ toggle(e) {
77
+ editorState.toggle();
78
+ },
71
79
  focusout(e) {
72
80
  if (popup?.contains(e?.relatedTarget)) return;
73
81
  this.submit();
74
82
  },
75
-
83
+ popupfocusout(e) {
84
+ if (anchor != e?.relatedTarget) {
85
+ this.submit();
86
+ }
87
+ },
76
88
  clear() {
77
89
  localValue = [];
78
90
  },
@@ -82,6 +94,8 @@
82
94
  return "View";
83
95
  },
84
96
  cancel() {
97
+ anchor.blur();
98
+ localValue = JSON.parse(originalValue);
85
99
  return "View";
86
100
  },
87
101
  },
@@ -94,7 +108,9 @@
94
108
  $: pills = cellOptions.relViewMode == "pills";
95
109
  $: ownId = ownId || cellOptions?.ownId;
96
110
 
97
- $: localValue = enrichValue(value);
111
+ $: if (!isLoading) {
112
+ localValue = enrichValue(value);
113
+ }
98
114
 
99
115
  $: inEdit = $cellState == "Editing";
100
116
  $: isDirty = inEdit && originalValue != JSON.stringify(localValue);
@@ -116,17 +132,7 @@
116
132
  } else if (e.key == "Tab" && $editorState == "Open") {
117
133
  cellState.focusout(e);
118
134
  } else if ($editorState == "Open") {
119
- if (e.key === "ArrowUp") {
120
- e.preventDefault();
121
- pickerApi?.focusPrev();
122
- pickerApi?.scrollIntoView();
123
- } else if (e.key === "ArrowDown") {
124
- e.preventDefault();
125
- pickerApi?.focusNext();
126
- pickerApi?.scrollIntoView();
127
- } else if (e.key.length == 1 && e.key.match(/\S/)) {
128
- pickerApi?.setSearch(e.key);
129
- }
135
+ pickerApi?.focus();
130
136
  }
131
137
  };
132
138
 
@@ -135,6 +141,7 @@
135
141
 
136
142
  if (!multi) {
137
143
  editorState.close();
144
+ anchor.focus();
138
145
  }
139
146
  };
140
147
 
@@ -146,6 +153,7 @@
146
153
  : [];
147
154
  const missingIds = x.filter((id) => !existingIds.includes(id));
148
155
  if (missingIds.length > 0) {
156
+ isLoading = true;
149
157
  API.fetchTableDefinition(relatedTableId).then((def) => {
150
158
  fieldSchema.primaryDisplay = def.primaryDisplay;
151
159
  });
@@ -155,16 +163,20 @@
155
163
  )
156
164
  .then((rows) => {
157
165
  const newEnriched = rows.map((row) => ({
158
- [relatedField]: row[relatedField],
166
+ ...row,
159
167
  primaryDisplay: fieldSchema.primaryDisplay
160
168
  ? row[fieldSchema.primaryDisplay]
161
169
  : row.name || row.id,
162
170
  }));
163
171
  // Append new enriched items to existing localValue
164
172
  localValue = [...(localValue || []), ...newEnriched];
173
+ // Dispatch enrich event with full row context
174
+ dispatch("enrich", { rows: newEnriched });
175
+ isLoading = false;
165
176
  })
166
177
  .catch((e) => {
167
178
  // On error, keep existing localValue
179
+ isLoading = false;
168
180
  });
169
181
  }
170
182
  return localValue || [];
@@ -173,23 +185,27 @@
173
185
  const existing =
174
186
  localValue && localValue.find((v) => v[relatedField] === x);
175
187
  if (!existing) {
188
+ isLoading = true;
176
189
  API.fetchTableDefinition(relatedTableId).then((def) => {
177
190
  fieldSchema.primaryDisplay = def.primaryDisplay;
178
191
  });
179
192
 
180
193
  API.fetchRow(relatedTableId, x, true)
181
194
  .then((row) => {
182
- localValue = [
183
- {
184
- [relatedField]: row[relatedField],
185
- primaryDisplay: fieldSchema.primaryDisplay
186
- ? row[fieldSchema.primaryDisplay]
187
- : row.name || row.id,
188
- },
189
- ];
195
+ const enrichedRow = {
196
+ ...row,
197
+ primaryDisplay: fieldSchema.primaryDisplay
198
+ ? row[fieldSchema.primaryDisplay]
199
+ : row.name || row.id,
200
+ };
201
+ localValue = [enrichedRow];
202
+ // Dispatch enrich event with full row context
203
+ dispatch("enrich", { rows: [enrichedRow] });
204
+ isLoading = false;
190
205
  })
191
206
  .catch((e) => {
192
207
  localValue = [];
208
+ isLoading = false;
193
209
  });
194
210
  }
195
211
  return localValue || [];
@@ -205,7 +221,7 @@
205
221
  <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
206
222
  <!-- svelte-ignore a11y-no-static-element-interactions -->
207
223
  <div
208
- class="superCell"
224
+ class="superCell has-popup"
209
225
  tabindex={cellOptions?.disabled ? -1 : 0}
210
226
  bind:this={anchor}
211
227
  class:isDirty={isDirty && cellOptions.showDirty}
@@ -215,63 +231,66 @@
215
231
  class:formInput={cellOptions.role == "formInput"}
216
232
  class:disabled={cellOptions.disabled}
217
233
  class:readonly
234
+ class:open-popup={$editorState == "Open"}
218
235
  class:error={cellOptions.error}
219
236
  style:color={cellOptions.color}
220
237
  style:background={cellOptions.background}
221
- style:font-weight={cellOptions.fontWeight}
222
238
  on:focusin={cellState.focus}
223
239
  on:keydown|self={handleKeyboard}
240
+ on:mousedown={cellState.toggle}
224
241
  on:focusout={cellState.focusout}
225
242
  >
226
- {#if cellOptions?.icon}
227
- <i class={cellOptions.icon + " field-icon"}></i>
228
- {/if}
243
+ {#if !isLoading}
244
+ {#if cellOptions?.icon}
245
+ <i class={cellOptions.icon + " field-icon"}></i>
246
+ {/if}
229
247
 
230
- <div class="value" class:placeholder={localValue?.length < 1}>
231
- {#if localValue?.length < 1}
232
- <span> {placeholder} </span>
233
- {:else if pills}
234
- <div
235
- class="items"
236
- class:pills
237
- class:withCount={localValue.length > 5}
238
- class:inEdit
239
- >
240
- {#each localValue as val, idx}
241
- {#if idx < 5}
242
- <div class="item">
243
- <span>{val.primaryDisplay}</span>
244
- </div>
248
+ <div class="value" class:placeholder={localValue?.length < 1}>
249
+ {#if localValue?.length < 1}
250
+ <span> {placeholder} </span>
251
+ {:else if pills}
252
+ <div
253
+ class="items"
254
+ class:pills
255
+ class:withCount={localValue.length > 5}
256
+ class:inEdit
257
+ >
258
+ {#each localValue as val, idx}
259
+ {#if idx < 5}
260
+ <div class="item">
261
+ <span>{val.primaryDisplay}</span>
262
+ </div>
263
+ {/if}
264
+ {/each}
265
+ {#if localValue.length > 5}
266
+ <span class="count">
267
+ (+ {localValue.length - 5})
268
+ </span>
245
269
  {/if}
246
- {/each}
247
- {#if localValue.length > 5}
248
- <span class="count">
249
- (+ {localValue.length - 5})
250
- </span>
251
- {/if}
252
- </div>
253
- {:else}
254
- <span>
255
- {#if cellOptions.role == "formInput" && localValue.length > 1}
256
- ({localValue.length})
257
- {/if}
258
- {localValue.map((v) => v.primaryDisplay).join(", ")}
259
- </span>
270
+ </div>
271
+ {:else}
272
+ <span>
273
+ {#if cellOptions.role == "formInput" && localValue.length > 1}
274
+ ({localValue.length})
275
+ {/if}
276
+ {localValue.map((v) => v.primaryDisplay).join(", ")}
277
+ </span>
278
+ {/if}
279
+ </div>
280
+ {#if !readonly && (cellOptions.role == "formInput" || inEdit)}
281
+ <i class="ph ph-caret-down control-icon"></i>
260
282
  {/if}
261
- </div>
262
- {#if !readonly && (cellOptions.role == "formInput" || inEdit)}
263
- <i class="ph ph-caret-down control-icon" on:click={editorState.toggle}></i>
264
283
  {/if}
265
284
  </div>
266
285
 
267
286
  {#if inEdit}
268
287
  <SuperPopover
269
288
  {anchor}
270
- useAnchorWidth
289
+ useAnchorWidth={true}
290
+ minWidth={cellOptions.pickerWidth || undefined}
291
+ align="left"
271
292
  open={$editorState == "Open"}
272
- on:close={(e) => {
273
- editorState.close();
274
- }}
293
+ bind:popup
275
294
  >
276
295
  {#if fieldSchema.recursiveTable}
277
296
  <CellLinkPickerTree
@@ -293,6 +312,7 @@
293
312
  value={localValue}
294
313
  bind:api={pickerApi}
295
314
  on:change={handleChange}
315
+ on:focusout={cellState.popupfocusout}
296
316
  />
297
317
  {/if}
298
318
  </SuperPopover>