@lavalogic/scoria 0.40.2 → 0.40.3

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.
@@ -30,6 +30,36 @@
30
30
  cell.column.columnDef.isEnabled(cell.row.original, cell.row.originalIndex)
31
31
  );
32
32
 
33
+ // Per-row toggle visibility. When `isVisible` resolves false the toggle is
34
+ // omitted entirely (rather than disabled). Sync booleans are used directly
35
+ // so non-visible rows never flash the icon; promises resolve into state.
36
+ const visibleRaw: boolean | Promise<boolean> = $derived(
37
+ cell.column.columnDef.isVisible
38
+ ? cell.column.columnDef.isVisible(cell.row.original, cell.row.originalIndex)
39
+ : true
40
+ );
41
+ let visibleAsyncResolved = $state(true);
42
+ let visibleCounter = 0;
43
+ $effect(() => {
44
+ const raw = visibleRaw;
45
+ if (typeof raw === 'boolean') {
46
+ return;
47
+ }
48
+ const local = (visibleCounter = visibleCounter + 1);
49
+ raw
50
+ .then((v) => {
51
+ if (visibleCounter === local) {
52
+ visibleAsyncResolved = v;
53
+ }
54
+ })
55
+ .catch(() => {
56
+ if (visibleCounter === local) {
57
+ visibleAsyncResolved = false;
58
+ }
59
+ });
60
+ });
61
+ const visible = $derived(typeof visibleRaw === 'boolean' ? visibleRaw : visibleAsyncResolved);
62
+
33
63
  let validity: Validity = $state(Validity.Valid);
34
64
  $effect(() => {
35
65
  const validityPromise = focusDetails.validity;
@@ -81,13 +111,15 @@
81
111
  class:multifocus={focusDetails.isMultiFocused}
82
112
  class:multifocus-start={focusDetails.isMultiFocusStart}
83
113
  >
84
- {#await enabled}
85
- <!-- eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -->
86
- {@render action(false)}
87
- {:then enabled}
88
- <!-- eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -->
89
- {@render action(enabled)}
90
- {/await}
114
+ {#if visible}
115
+ {#await enabled}
116
+ <!-- eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -->
117
+ {@render action(false)}
118
+ {:then enabled}
119
+ <!-- eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -->
120
+ {@render action(enabled)}
121
+ {/await}
122
+ {/if}
91
123
  </div>
92
124
  </div>
93
125
 
@@ -23,6 +23,31 @@
23
23
  tableContext._focus.getFocusDetailsFor(cell)
24
24
  );
25
25
 
26
+ // Resolve the column's (possibly async) `isEditable` predicate for this
27
+ // row. A non-editable checkbox renders disabled (read-only) rather than
28
+ // toggling — mirrors how the inline-edit cells gate on `isEditable`.
29
+ const editableAsync = $derived(
30
+ cell.column.columnDef.isEditable(cell.row.original, cell.row.originalIndex)
31
+ );
32
+ let editable = $state(false);
33
+ let editableCounter = 0;
34
+ $effect(() => {
35
+ void editableAsync;
36
+ const local = (editableCounter = editableCounter + 1);
37
+ (async () => {
38
+ try {
39
+ const resolved = await editableAsync;
40
+ if (editableCounter === local) {
41
+ editable = resolved;
42
+ }
43
+ } catch {
44
+ if (editableCounter === local) {
45
+ editable = false;
46
+ }
47
+ }
48
+ })().catch(devCatch);
49
+ });
50
+
26
51
  let inputRef: HTMLInputElement | undefined = $state();
27
52
 
28
53
  /**
@@ -32,6 +57,9 @@
32
57
  * already showed the new checked state.
33
58
  */
34
59
  const onchange: ChangeEventHandler<HTMLInputElement> = (e) => {
60
+ if (!editable) {
61
+ return;
62
+ }
35
63
  tableContext
36
64
  .onEditCell(
37
65
  cell.row.original,
@@ -84,7 +112,7 @@
84
112
  }
85
113
  }}
86
114
  onkeyup={(e: KeyboardEvent) => {
87
- if (e.key === 'Enter') {
115
+ if (e.key === 'Enter' && editable) {
88
116
  e.stopPropagation();
89
117
  tableContext.onEditCell(cell.row.original, focusDetails.coordinates, !value).catch(devCatch);
90
118
  }
@@ -119,6 +147,7 @@
119
147
  {onchange}
120
148
  type="checkbox"
121
149
  checked={value}
150
+ disabled={!editable}
122
151
  />
123
152
  </label>
124
153
  </div>
@@ -198,4 +227,9 @@ label.multifocus.multifocus-start {
198
227
  .input-cell.error,
199
228
  .select-cell.error {
200
229
  background-color: #f9e9e9;
230
+ }
231
+
232
+ input:disabled {
233
+ cursor: not-allowed;
234
+ opacity: 0.45;
201
235
  }</style>
@@ -232,6 +232,11 @@ export function createColumnFactory() {
232
232
  const def = new CheckboxDef({
233
233
  id: opts.id,
234
234
  ...commonProps(opts, 'checkbox'),
235
+ // Checkboxes are interactive by default (the base ColumnDef
236
+ // default is read-only); only an explicit `isEditable` gates
237
+ // them. Keeps existing checkboxes editable while letting callers
238
+ // disable per row.
239
+ isEditable: opts.isEditable ?? (() => true),
235
240
  accessorFn: opts.accessor,
236
241
  getDisplayValue: opts.getDisplayValue,
237
242
  filterValueType: FilterValueType.Bool,
@@ -522,6 +527,7 @@ export function createColumnFactory() {
522
527
  allowCopy: opts.inner?.allowCopy,
523
528
  },
524
529
  onActivate: opts.onActivate,
530
+ isVisible: opts.isVisible,
525
531
  });
526
532
  // Expand has no public width option — apply the per-kind default.
527
533
  def.width = KIND_DEFAULT_WIDTHS.expand;
@@ -13,6 +13,8 @@ import { ColumnDef, type ColumnDefProps } from '../ColumnDef.svelte.js';
13
13
  export interface ExpandDefProps<T extends object, R extends object, InnerIdType extends Primitive = Primitive, in FilterValueType = unknown> extends ColumnDefProps<T, FilterValueType> {
14
14
  /** Per-row enable predicate for the expansion affordance. */
15
15
  isEnabled: (item: T, index: number) => boolean | Promise<boolean>;
16
+ /** Per-row predicate for whether the toggle renders at all. */
17
+ isVisible?: (item: T, index: number) => boolean | Promise<boolean>;
16
18
  /** Builds the inner table's column defs. Invoked per expansion. */
17
19
  getInnerColumnDefs: (item: T, index: number) => DefsWrapper<R>;
18
20
  /** Header icon name on the expand-toggle button. */
@@ -49,6 +51,8 @@ export declare class ExpandDef<T extends object, R extends object, InnerIdType e
49
51
  readonly headerIcon: IconProps['name'];
50
52
  /** Per-row enable predicate for the expansion affordance. */
51
53
  readonly isEnabled: (item: T, index: number) => boolean | Promise<boolean>;
54
+ /** Per-row predicate for whether the toggle renders at all. */
55
+ readonly isVisible?: (item: T, index: number) => boolean | Promise<boolean>;
52
56
  /** Init options for the inner table. */
53
57
  readonly innerTableOptions: TableInitOptions<R, InnerIdType>;
54
58
  /** Navigate-instead-of-expand callback; undefined for normal expand. */
@@ -17,6 +17,7 @@ export class ExpandDef extends ColumnDef {
17
17
  this.innerTableName = $derived(props.innerTableName);
18
18
  this.innerTableOptions = $derived(props.innerTableOptions);
19
19
  this.onActivate = $derived(props.onActivate);
20
+ this.isVisible = $derived(props.isVisible);
20
21
  }
21
22
  /** Expand columns are never filtered. */
22
23
  filterFn = undefined;
@@ -28,6 +29,8 @@ export class ExpandDef extends ColumnDef {
28
29
  headerIcon;
29
30
  /** Per-row enable predicate for the expansion affordance. */
30
31
  isEnabled;
32
+ /** Per-row predicate for whether the toggle renders at all. */
33
+ isVisible;
31
34
  /** Init options for the inner table. */
32
35
  innerTableOptions;
33
36
  /** Navigate-instead-of-expand callback; undefined for normal expand. */
@@ -377,6 +377,15 @@ export interface ExpandColumnOptions<TRow extends object, TInner extends object,
377
377
  * inner content. Async supported. Defaults to `() => true`.
378
378
  */
379
379
  isEnabled?: (row: TRow, index: number) => boolean | Promise<boolean>;
380
+ /**
381
+ * Per-row predicate for whether the expand affordance renders at all.
382
+ * When it resolves to `false` the toggle is omitted entirely (not just
383
+ * disabled), so rows that can never expand show no icon. Use to surface
384
+ * only the rows that are actually expandable (vs `isEnabled`, which keeps
385
+ * the toggle visible but greyed). Async supported. Defaults to always
386
+ * visible.
387
+ */
388
+ isVisible?: (row: TRow, index: number) => boolean | Promise<boolean>;
380
389
  /**
381
390
  * Inner-table configuration. Each field maps 1:1 onto the equivalent
382
391
  * `CreateTableOptions` field that configures an outer Table; supply
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lavalogic/scoria",
3
3
  "description": "Svelte components used for the FloWMS Web Frontend",
4
- "version": "0.40.2",
4
+ "version": "0.40.3",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },