@softwareone/spi-sv5-library 1.11.6 → 1.11.8

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.
@@ -52,7 +52,7 @@
52
52
  let isLoading = $state(false);
53
53
 
54
54
  const { form, errors, isFormValid } = getFormContext<Schema>(contextName);
55
- $form = structuredClone($state.snapshot(initialData));
55
+ $form = $state.snapshot(initialData);
56
56
 
57
57
  const onUpdated = () => {
58
58
  isLoading = false;
@@ -1,59 +1,56 @@
1
1
  <script lang="ts">
2
+ import type { Attachment } from 'svelte/attachments';
3
+ import { fade } from 'svelte/transition';
4
+
2
5
  import type { Action } from './types.js';
3
6
 
4
7
  interface Props {
5
8
  actions: Action[];
6
- isLastChild?: boolean;
7
- isOnlyOneRow?: boolean;
8
9
  }
9
10
 
10
- let { actions, isLastChild = false, isOnlyOneRow = false }: Props = $props();
11
+ let { actions }: Props = $props();
12
+
13
+ let container = $state<HTMLDivElement>()!;
14
+ let isOpen = $state(false);
11
15
 
12
- const icons: Record<string, string> = {
16
+ const ICONS: Record<string, string> = {
13
17
  Edit: 'edit',
14
18
  Delete: 'delete'
15
19
  };
16
20
 
17
- const indexLastSecondaryAction = $derived(
18
- actions.findLastIndex((action) => !action.isMainAction)
19
- );
21
+ const isSingleAction = $derived(actions.length === 1);
22
+ const dividerIndex = $derived(actions.findLastIndex((action) => !action.isMainAction));
23
+
24
+ const showDividerAfter = (index: number) =>
25
+ dividerIndex !== -1 && dividerIndex !== actions.length - 1 && dividerIndex === index;
26
+
27
+ const autoPosition: Attachment<HTMLElement> = (element: HTMLElement) => {
28
+ const updatePosition = () => {
29
+ const rect = container.getBoundingClientRect();
30
+ const showAbove = rect.bottom + element.offsetHeight > window.innerHeight;
31
+
32
+ const left = rect.left + rect.width / 2 - element.offsetWidth / 2;
33
+ const top = showAbove ? rect.top - element.offsetHeight + 14 : rect.bottom - 8;
34
+
35
+ element.style.setProperty('--menu-left', `${Math.max(0, left)}px`);
36
+ element.style.setProperty('--menu-top', `${top}px`);
37
+ };
38
+
39
+ updatePosition();
40
+ window.addEventListener('scroll', updatePosition, true);
41
+
42
+ return () => window.removeEventListener('scroll', updatePosition, true);
43
+ };
44
+
45
+ const toggleActionsMenu = () => {
46
+ isOpen = !isOpen;
47
+ };
20
48
  </script>
21
49
 
22
50
  <div class="actions-column">
23
- {#if actions.length > 1}
24
- <div class="actions-group">
25
- <span class="actions-trigger">...</span>
26
- <div
27
- class={[
28
- 'actions-menu',
29
- isOnlyOneRow && 'actions-menu--only-row',
30
- isLastChild && !isOnlyOneRow && 'actions-menu--last',
31
- !isLastChild && !isOnlyOneRow && 'actions-menu--top'
32
- ]}
33
- >
34
- <ul class="actions-list">
35
- {#each actions as action, index}
36
- <li>
37
- <button
38
- type="button"
39
- onclick={action.onClick}
40
- class="action-button"
41
- class:action-button--delete={action.isDelete && action.isEnabled}
42
- disabled={!action.isEnabled}
43
- >
44
- <span>{action.name}</span>
45
- </button>
46
- </li>
47
- {#if indexLastSecondaryAction !== -1 && indexLastSecondaryAction !== actions.length - 1 && indexLastSecondaryAction === index}
48
- <hr class="action-divider" />
49
- {/if}
50
- {/each}
51
- </ul>
52
- </div>
53
- </div>
54
- {:else}
51
+ {#if isSingleAction}
55
52
  {@const action = actions[0]}
56
- {@const nameIcon = icons[action.name]}
53
+ {@const icon = ICONS[action.name]}
57
54
 
58
55
  <button
59
56
  type="button"
@@ -62,14 +59,45 @@
62
59
  class:action-button--delete={action.isDelete && action.isEnabled}
63
60
  disabled={!action.isEnabled}
64
61
  >
65
- {#if nameIcon}
66
- <span class="material-icons-outlined">
67
- {nameIcon}
68
- </span>
62
+ {#if icon}
63
+ <span class="material-icons-outlined">{icon}</span>
69
64
  {:else}
70
65
  <span class:action-button--primary={action.isEnabled}>{action.name}</span>
71
66
  {/if}
72
67
  </button>
68
+ {:else}
69
+ <div
70
+ role="menu"
71
+ tabindex="-1"
72
+ class="actions-group"
73
+ bind:this={container}
74
+ onmouseenter={toggleActionsMenu}
75
+ onmouseleave={toggleActionsMenu}
76
+ >
77
+ <span class="actions-trigger">...</span>
78
+ {#if isOpen}
79
+ <div class="actions-menu" {@attach autoPosition} transition:fade={{ duration: 100 }}>
80
+ <ul class="actions-list">
81
+ {#each actions as action, index}
82
+ <li>
83
+ <button
84
+ type="button"
85
+ onclick={action.onClick}
86
+ class="action-button"
87
+ class:action-button--delete={action.isDelete && action.isEnabled}
88
+ disabled={!action.isEnabled}
89
+ >
90
+ <span>{action.name}</span>
91
+ </button>
92
+ </li>
93
+ {#if showDividerAfter(index)}
94
+ <hr class="action-divider" />
95
+ {/if}
96
+ {/each}
97
+ </ul>
98
+ </div>
99
+ {/if}
100
+ </div>
73
101
  {/if}
74
102
  </div>
75
103
 
@@ -99,7 +127,7 @@
99
127
  }
100
128
 
101
129
  .actions-group {
102
- position: absolute;
130
+ position: relative;
103
131
  }
104
132
 
105
133
  .actions-trigger {
@@ -111,39 +139,15 @@
111
139
  }
112
140
 
113
141
  .actions-menu {
114
- position: absolute;
142
+ position: fixed;
115
143
  z-index: var(--z-index-menu);
116
144
  width: 108px;
145
+ left: var(--menu-left);
146
+ top: var(--menu-top);
117
147
  padding: var(--spacing-md) var(--spacing-lg);
118
148
  background: var(--color-white);
119
149
  border-radius: var(--radius-lg);
120
150
  box-shadow: var(--shadow-menu);
121
- visibility: hidden;
122
- opacity: 0;
123
- transition: var(--transition-standard);
124
- }
125
-
126
- .actions-group:hover .actions-menu {
127
- visibility: visible;
128
- opacity: 1;
129
- }
130
-
131
- .actions-menu--only-row {
132
- right: 28px;
133
- bottom: 16px;
134
- transform: translateY(50%);
135
- }
136
-
137
- .actions-menu--last {
138
- bottom: 28px;
139
- left: 50%;
140
- transform: translateX(-50%);
141
- }
142
-
143
- .actions-menu--top {
144
- top: 32px;
145
- left: 50%;
146
- transform: translateX(-50%);
147
151
  }
148
152
 
149
153
  .actions-list {
@@ -1,8 +1,6 @@
1
1
  import type { Action } from './types.js';
2
2
  interface Props {
3
3
  actions: Action[];
4
- isLastChild?: boolean;
5
- isOnlyOneRow?: boolean;
6
4
  }
7
5
  declare const ActionsColumn: import("svelte").Component<Props, {}, "">;
8
6
  type ActionsColumn = ReturnType<typeof ActionsColumn>;
@@ -4,16 +4,32 @@
4
4
 
5
5
  interface Props {
6
6
  bodyRows: Row<T>[];
7
+ enableChecked?: boolean;
7
8
  }
8
9
 
9
- let { bodyRows }: Props = $props();
10
+ let { bodyRows, enableChecked = false }: Props = $props();
11
+
12
+ const CLICKABLE_TAGS = ['td', 'span', 'p'];
13
+
14
+ const handleRowClick = (row: Row<T>, target: EventTarget | null) => {
15
+ const isHtmlElement = target instanceof HTMLElement;
16
+ const tagName = isHtmlElement ? target.tagName.toLocaleLowerCase() : '';
17
+ if (isHtmlElement && CLICKABLE_TAGS.includes(tagName)) {
18
+ row.toggleSelected();
19
+ }
20
+ };
10
21
  </script>
11
22
 
12
23
  <tbody class="table-body">
13
24
  {#each bodyRows as row (row.id)}
14
25
  <tr
15
26
  animate:flip={{ duration: 500 }}
16
- class={['table-row', row.getIsSelected() && 'table-row--selected']}
27
+ class={[
28
+ 'table-row',
29
+ row.getIsSelected() && 'table-row--selected',
30
+ enableChecked && 'table-row--clickable'
31
+ ]}
32
+ onclick={enableChecked ? (event) => handleRowClick(row, event.target) : undefined}
17
33
  >
18
34
  {#each row.getVisibleCells() as cell}
19
35
  {@const alignColumn = cell.column.columnDef.meta?.alignColumn}
@@ -66,6 +82,10 @@
66
82
  background: var(--color-row-selected);
67
83
  }
68
84
 
85
+ .table-row--clickable {
86
+ cursor: pointer;
87
+ }
88
+
69
89
  .table-cell {
70
90
  max-width: 50px;
71
91
  padding: var(--spacing-md) var(--spacing-lg);
@@ -2,6 +2,7 @@ import { type Row } from './adapter/index.js';
2
2
  declare function $$render<T>(): {
3
3
  props: {
4
4
  bodyRows: Row<T>[];
5
+ enableChecked?: boolean;
5
6
  };
6
7
  exports: {};
7
8
  bindings: "";
@@ -48,7 +48,7 @@
48
48
  pagination?: Pagination;
49
49
  excelSetting?: ExcelSetting;
50
50
  header?: Snippet;
51
- bulkActions?: Snippet<[selectedRows: T[]]>;
51
+ bulkActions?: Snippet<[selectedRows: T[], clearSelection: VoidFunction]>;
52
52
  onpagechange?: (pageSize: number, pageNumber: number) => void;
53
53
  onpagesizechange?: (pageSize: number) => void;
54
54
  }
@@ -206,6 +206,10 @@
206
206
  }
207
207
  }
208
208
 
209
+ const clearSelection = () => {
210
+ rowSelection = {};
211
+ };
212
+
209
213
  const changeFilters = (filters: Filter[]) => {
210
214
  const searchParams = new URLSearchParams(page.url.searchParams);
211
215
  searchParams.delete('filter');
@@ -271,8 +275,10 @@
271
275
  <Button
272
276
  type="button"
273
277
  variant="secondary"
274
- onclick={() => exportExcel(table, excelSetting)}>Export</Button
278
+ onclick={() => exportExcel(table, excelSetting)}
275
279
  >
280
+ {excelSetting.label ?? 'Export'}
281
+ </Button>
276
282
  {/if}
277
283
  {@render header?.()}
278
284
  </div>
@@ -286,7 +292,10 @@
286
292
  </p>
287
293
  {#if bulkActions}
288
294
  <hr class="action-divider" />
289
- {@render bulkActions?.(selectedRows.map((row) => row.original))}
295
+ {@render bulkActions?.(
296
+ selectedRows.map((row) => row.original),
297
+ clearSelection
298
+ )}
290
299
  {/if}
291
300
  </div>
292
301
  {/if}
@@ -295,7 +304,7 @@
295
304
  <table class="table-main">
296
305
  <Header headerGroups={table.getHeaderGroups()} {enableColumnSearch} />
297
306
  {#if hasData}
298
- <Body bodyRows={table.getRowModel().rows} />
307
+ <Body bodyRows={table.getRowModel().rows} {enableChecked} />
299
308
  {/if}
300
309
  </table>
301
310
  </article>
@@ -18,7 +18,7 @@ declare function $$render<T>(): {
18
18
  pagination?: Pagination;
19
19
  excelSetting?: ExcelSetting;
20
20
  header?: Snippet;
21
- bulkActions?: Snippet<[selectedRows: T[]]>;
21
+ bulkActions?: Snippet<[selectedRows: T[], clearSelection: VoidFunction]>;
22
22
  onpagechange?: (pageSize: number, pageNumber: number) => void;
23
23
  onpagesizechange?: (pageSize: number) => void;
24
24
  };
@@ -1,6 +1,7 @@
1
1
  export interface ExcelSetting {
2
2
  fileName: string;
3
3
  exportAllColumns: boolean;
4
+ label?: string;
4
5
  }
5
6
  export declare const ColumnFormat: {
6
7
  readonly NUMBER: "#,##0.00";
@@ -43,10 +43,8 @@ export const createActionsColumn = (getActions) => {
43
43
  columnWidth: '117px',
44
44
  hideColumnFilter: true
45
45
  },
46
- cell: ({ row, table }) => renderComponent(ActionsColumn, {
47
- actions: getActions(row),
48
- isLastChild: table.getRowModel().rows.at(-1).index === row.index,
49
- isOnlyOneRow: table.getRowModel().rows.length === 1
46
+ cell: ({ row }) => renderComponent(ActionsColumn, {
47
+ actions: getActions(row)
50
48
  })
51
49
  });
52
50
  };
@@ -1,18 +1,15 @@
1
1
  <script lang="ts">
2
2
  interface Props {
3
3
  title: string;
4
- icon: string;
4
+ iconUrl: string;
5
5
  onclickwaffleitems: VoidFunction;
6
6
  }
7
7
 
8
- let { title, icon, onclickwaffleitems }: Props = $props();
8
+ let { title, iconUrl, onclickwaffleitems }: Props = $props();
9
9
  </script>
10
10
 
11
11
  <button type="button" onclick={onclickwaffleitems} class="waffle-item-button">
12
- <figure class="waffle-icon-container" aria-hidden="true">
13
- {@html icon}
14
- </figure>
15
-
12
+ <img src={iconUrl} alt="" aria-hidden="true" class="waffle-item-img" />
16
13
  <h3 class="waffle-title">
17
14
  {title}
18
15
  </h3>
@@ -44,13 +41,12 @@
44
41
  background: #eaecff;
45
42
  }
46
43
 
47
- .waffle-icon-container {
44
+ .waffle-item-img {
48
45
  width: 40px;
49
46
  height: 40px;
50
47
  margin-bottom: 12px;
51
- display: flex;
52
- align-items: center;
53
- justify-content: center;
48
+ filter: invert(27%) sepia(99%) saturate(4729%) hue-rotate(240deg) brightness(101%)
49
+ contrast(106%);
54
50
  }
55
51
 
56
52
  .waffle-title {
@@ -1,6 +1,6 @@
1
1
  interface Props {
2
2
  title: string;
3
- icon: string;
3
+ iconUrl: string;
4
4
  onclickwaffleitems: VoidFunction;
5
5
  }
6
6
  declare const WaffleItems: import("svelte").Component<Props, {}, "">;
@@ -1,5 +1,5 @@
1
1
  export interface WaffleItem {
2
2
  title: string;
3
3
  url: string;
4
- icon: string;
4
+ iconUrl: string;
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwareone/spi-sv5-library",
3
- "version": "1.11.6",
3
+ "version": "1.11.8",
4
4
  "description": "Svelte components",
5
5
  "keywords": [
6
6
  "svelte",