@ims360/svelte-ivory 0.0.44 → 0.0.46

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 (73) hide show
  1. package/dist/components/ai/Chat.svelte +11 -11
  2. package/dist/components/ai/UserMessage.svelte +1 -1
  3. package/dist/components/basic/checkbox/Checkbox.svelte +0 -1
  4. package/dist/components/basic/checkbox/Checkbox.svelte.d.ts.map +1 -1
  5. package/dist/components/layout/drawer/Drawer.svelte +0 -2
  6. package/dist/components/layout/drawer/Drawer.svelte.d.ts.map +1 -1
  7. package/dist/components/layout/modal/Modal.svelte +0 -4
  8. package/dist/components/layout/modal/Modal.svelte.d.ts.map +1 -1
  9. package/dist/components/layout/popover/Popover.svelte +1 -0
  10. package/dist/components/layout/popover/Popover.svelte.d.ts.map +1 -1
  11. package/dist/components/layout/tooltip/Tooltip.svelte +5 -5
  12. package/dist/components/layout/tooltip/Tooltip.svelte.d.ts +5 -3
  13. package/dist/components/layout/tooltip/Tooltip.svelte.d.ts.map +1 -1
  14. package/dist/components/table/Column.svelte +2 -2
  15. package/dist/components/table/ColumnHead.svelte +6 -6
  16. package/dist/components/table/ColumnHead.svelte.d.ts +4 -4
  17. package/dist/components/table/ColumnHead.svelte.d.ts.map +1 -1
  18. package/dist/components/table/Row.svelte +0 -1
  19. package/dist/components/table/Row.svelte.d.ts.map +1 -1
  20. package/dist/components/table/Table.svelte +67 -26
  21. package/dist/components/table/Table.svelte.d.ts +12 -9
  22. package/dist/components/table/Table.svelte.d.ts.map +1 -1
  23. package/dist/components/table/VirtualList.svelte +3 -3
  24. package/dist/components/table/columnController.svelte.d.ts +1 -1
  25. package/dist/components/table/columnController.svelte.d.ts.map +1 -1
  26. package/dist/components/table/columnController.svelte.js +1 -1
  27. package/dist/components/table/controller.d.ts +23 -0
  28. package/dist/components/table/controller.d.ts.map +1 -0
  29. package/dist/components/table/controller.js +52 -0
  30. package/dist/components/table/index.d.ts +2 -2
  31. package/dist/components/table/index.d.ts.map +1 -1
  32. package/dist/components/table/index.js +2 -2
  33. package/dist/components/table/plugins/search.svelte.d.ts.map +1 -1
  34. package/dist/components/table/plugins/search.svelte.js +0 -1
  35. package/dist/components/toast/Toast.svelte +0 -2
  36. package/dist/components/toast/Toast.svelte.d.ts.map +1 -1
  37. package/dist/utils/functions/cookie.js +1 -1
  38. package/package.json +1 -1
  39. package/src/lib/components/ai/Chat.svelte +11 -11
  40. package/src/lib/components/ai/UserMessage.svelte +1 -1
  41. package/src/lib/components/basic/checkbox/Checkbox.svelte +0 -1
  42. package/src/lib/components/layout/drawer/Drawer.svelte +0 -2
  43. package/src/lib/components/layout/modal/Modal.svelte +0 -4
  44. package/src/lib/components/layout/popover/Popover.svelte +1 -0
  45. package/src/lib/components/layout/tabs/index.ts +0 -1
  46. package/src/lib/components/layout/tooltip/Tooltip.svelte +5 -5
  47. package/src/lib/components/table/Column.svelte +2 -2
  48. package/src/lib/components/table/ColumnHead.svelte +6 -6
  49. package/src/lib/components/table/Row.svelte +0 -1
  50. package/src/lib/components/table/Table.svelte +67 -26
  51. package/src/lib/components/table/VirtualList.svelte +3 -3
  52. package/src/lib/components/table/columnController.svelte.ts +1 -1
  53. package/src/lib/components/table/controller.ts +75 -0
  54. package/src/lib/components/table/index.ts +2 -9
  55. package/src/lib/components/table/plugins/search.svelte.ts +0 -9
  56. package/src/lib/components/toast/Toast.svelte +0 -2
  57. package/src/lib/utils/functions/cookie.ts +1 -1
  58. package/dist/components/layout/modal/ModalTest.svelte +0 -9
  59. package/dist/components/layout/modal/ModalTest.svelte.d.ts +0 -8
  60. package/dist/components/layout/modal/ModalTest.svelte.d.ts.map +0 -1
  61. package/dist/components/table/controller.svelte.d.ts +0 -37
  62. package/dist/components/table/controller.svelte.d.ts.map +0 -1
  63. package/dist/components/table/controller.svelte.js +0 -160
  64. package/dist/components/table/plugins/expandAll.svelte.d.ts +0 -7
  65. package/dist/components/table/plugins/expandAll.svelte.d.ts.map +0 -1
  66. package/dist/components/table/plugins/expandAll.svelte.js +0 -24
  67. package/dist/utils/functions/transitionProps.d.ts +0 -1
  68. package/dist/utils/functions/transitionProps.d.ts.map +0 -1
  69. package/dist/utils/functions/transitionProps.js +0 -1
  70. package/src/lib/components/layout/modal/ModalTest.svelte +0 -9
  71. package/src/lib/components/table/controller.svelte.ts +0 -203
  72. package/src/lib/components/table/plugins/expandAll.svelte.ts +0 -34
  73. package/src/lib/utils/functions/transitionProps.ts +0 -1
@@ -0,0 +1,52 @@
1
+ /** Walks though a tree strucure and turns it into a flat list, needed since the `VirtualList` needs a list, not a tree */
2
+ export function treeWalker(config) {
3
+ const { data, expanded } = config;
4
+ const stack = [];
5
+ // push the root nodes of the trees onto the stack
6
+ for (let i = 0; i < data.length; i++) {
7
+ stack.push({
8
+ node: data[data.length - i - 1],
9
+ nestingLevel: 0
10
+ });
11
+ }
12
+ const entries = []; // the final result
13
+ let someHaveChildren = false; // used to determine whether to show the tree utility buttons
14
+ let maxNestingLevel = 0;
15
+ while (stack.length !== 0) {
16
+ const stackEntry = stack.pop();
17
+ if (!stackEntry)
18
+ break;
19
+ const { node, nestingLevel } = stackEntry;
20
+ const children = node.children;
21
+ if (children && children.length > 0) {
22
+ someHaveChildren = true;
23
+ // only show the children of expanded elements
24
+ if (expanded.has(node.id)) {
25
+ maxNestingLevel = Math.max(maxNestingLevel, nestingLevel + 1);
26
+ stack.push(...children
27
+ .map((c) => ({
28
+ node: c,
29
+ nestingLevel: nestingLevel + 1
30
+ }))
31
+ .reverse());
32
+ }
33
+ }
34
+ entries.push({
35
+ id: node.id,
36
+ node,
37
+ nestingLevel
38
+ });
39
+ }
40
+ return {
41
+ entries,
42
+ someHaveChildren,
43
+ maxNestingLevel
44
+ };
45
+ }
46
+ export function getAllIds(...nodes) {
47
+ const ids = nodes.map((n) => n.id);
48
+ for (const node of nodes) {
49
+ ids.push(...getAllIds(...(node.children ?? [])));
50
+ }
51
+ return ids;
52
+ }
@@ -1,7 +1,7 @@
1
1
  export { default as Column, type ColumnProps } from './Column.svelte';
2
+ export { ColumnController, type ColumnConfig } from './columnController.svelte';
2
3
  export { getColumnHeadContext } from './ColumnHead.svelte';
3
- export { createTableConfig, getAllIds, type TableConfig, type TablePlugin, type TableRow, type TableState } from './controller.svelte';
4
- export { expandAllPlugin } from './plugins/expandAll.svelte';
4
+ export { getAllIds, type TablePlugin, type TableRow, type TableState } from './controller';
5
5
  export { searchPlugin } from './plugins/search.svelte';
6
6
  export { getTableContext, default as Table, type TableContext, type TableProps } from './Table.svelte';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/table/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACH,iBAAiB,EACjB,SAAS,EACT,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,UAAU,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACH,eAAe,EACf,OAAO,IAAI,KAAK,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,EAClB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/table/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACH,eAAe,EACf,OAAO,IAAI,KAAK,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,EAClB,MAAM,gBAAgB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  export { default as Column } from './Column.svelte';
2
+ export { ColumnController } from './columnController.svelte';
2
3
  export { getColumnHeadContext } from './ColumnHead.svelte';
3
- export { createTableConfig, getAllIds } from './controller.svelte';
4
- export { expandAllPlugin } from './plugins/expandAll.svelte';
4
+ export { getAllIds } from './controller';
5
5
  export { searchPlugin } from './plugins/search.svelte';
6
6
  export { getTableContext, default as Table } from './Table.svelte';
@@ -1 +1 @@
1
- {"version":3,"file":"search.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/table/plugins/search.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAEjD,UAAU,YAAY,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;CAChC;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CA8BzF;AAED,yFAAyF;AACzF,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EACxC,OAAO,CAAC,EAAE,EACV,cAAc,MAAM,EACpB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO;;;CA8B7C,CAAC"}
1
+ {"version":3,"file":"search.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/table/plugins/search.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAEjD,UAAU,YAAY,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;CAChC;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAyBzF;AAED,yFAAyF;AACzF,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EACxC,OAAO,CAAC,EAAE,EACV,cAAc,MAAM,EACpB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO;;;CA0B7C,CAAC"}
@@ -19,7 +19,6 @@ export function searchPlugin(conf) {
19
19
  // figure out which nodes to expand and hide
20
20
  const { expanded, hidden } = search(state.data, conf.search, conf.matches);
21
21
  prevSearch = conf.search;
22
- console.log(hidden);
23
22
  return {
24
23
  data: state.data.filter((d) => !hidden.has(d.id)),
25
24
  expanded: new SvelteSet(expanded)
@@ -51,8 +51,6 @@
51
51
  >
52
52
  {#each Toasts.toasts as toast (toast.id)}
53
53
  {@const VariantIcon = getIcon(toast.variant, toast.icon)}
54
-
55
- <!-- svelte-ignore a11y_no_static_element_interactions -->
56
54
  <div
57
55
  in:fly={{ y: '-100%', duration }}
58
56
  out:scale={{ duration }}
@@ -1 +1 @@
1
- {"version":3,"file":"Toast.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/toast/Toast.svelte.ts"],"names":[],"mappings":"AAGI,MAAM,WAAW,KAAK;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG;QAAE,KAAK,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAC,CAAC;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAKL,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAqF7D;;;GAGG;AACH,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Toast.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/toast/Toast.svelte.ts"],"names":[],"mappings":"AAGI,MAAM,WAAW,KAAK;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG;QAAE,KAAK,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAC,CAAC;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAKL,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAmF7D;;;GAGG;AACH,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -4,7 +4,7 @@ function getCookie(name) {
4
4
  try {
5
5
  decodedCookie = decodeURIComponent(document.cookie);
6
6
  }
7
- catch (error) {
7
+ catch {
8
8
  return '';
9
9
  }
10
10
  const ca = decodedCookie.split(';');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ims360/svelte-ivory",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "keywords": [
5
5
  "svelte"
6
6
  ],
@@ -41,7 +41,7 @@
41
41
 
42
42
  let {
43
43
  class: clazz,
44
- b_chat: chat = $bindable(),
44
+ b_chat = $bindable(),
45
45
  userMessage = defaultUserMessage,
46
46
  systemMessage = defaultSystemMessage,
47
47
  placeholder,
@@ -74,20 +74,20 @@
74
74
  }
75
75
 
76
76
  async function submit(message: AiChatMessage) {
77
- if (chat.loading) {
77
+ if (b_chat.loading) {
78
78
  return;
79
79
  }
80
80
 
81
- chat.messages.push({
81
+ b_chat.messages.push({
82
82
  ...message,
83
83
  from: 'user',
84
84
  time: new Date()
85
85
  });
86
86
  // prevent the user from sending another message while we are loading the ai response
87
- chat.loading = true;
87
+ b_chat.loading = true;
88
88
 
89
89
  // add an empty system message to the chat, this will indicate a loading state
90
- chat.messages.push({
90
+ b_chat.messages.push({
91
91
  from: 'system',
92
92
  message: '',
93
93
  time: new Date()
@@ -97,7 +97,7 @@
97
97
 
98
98
  await externalSubmit(message);
99
99
 
100
- chat.loading = false;
100
+ b_chat.loading = false;
101
101
  }
102
102
  </script>
103
103
 
@@ -106,11 +106,11 @@
106
106
  class="flex grow flex-col gap-4 overflow-auto pr-2 [scrollbar-gutter:stable]"
107
107
  bind:this={chatContainer}
108
108
  >
109
- {#if chat.messages.length === 0 && placeholder}
109
+ {#if b_chat.messages.length === 0 && placeholder}
110
110
  {@render placeholder()}
111
111
  {/if}
112
- {#each chat.messages as _, i}
113
- {@const message = chat.messages[i]}
112
+ {#each b_chat.messages as _, i (i)}
113
+ {@const message = b_chat.messages[i]}
114
114
  {#if message.from === 'user'}
115
115
  {@render userMessage({
116
116
  message,
@@ -120,7 +120,7 @@
120
120
  {@render systemMessage({
121
121
  message,
122
122
  i,
123
- minHeight: i === chat.messages.length - 1 ? lastMessageMinHeight : 0
123
+ minHeight: i === b_chat.messages.length - 1 ? lastMessageMinHeight : 0
124
124
  })}
125
125
  {/if}
126
126
  {/each}
@@ -136,7 +136,7 @@
136
136
  message: AiChatMessage;
137
137
  minHeight?: number;
138
138
  })}
139
- <AiMessage bind:b_message={chat.messages[i]} {minHeight} />
139
+ <AiMessage bind:b_message={b_chat.messages[i]} {minHeight} />
140
140
  {/snippet}
141
141
 
142
142
  {#snippet defaultUserMessage({
@@ -28,7 +28,7 @@
28
28
  {@render messageText({ message })}
29
29
  {#if message.files}
30
30
  <div class="flex flex-row items-center gap-2">
31
- {#each message.files as file}
31
+ {#each message.files as file, i (i)}
32
32
  {@render attachedFile(file)}
33
33
  {/each}
34
34
  </div>
@@ -55,7 +55,6 @@
55
55
  });
56
56
  </script>
57
57
 
58
- <!-- svelte-ignore a11y_no_static_element_interactions-->
59
58
  <svelte:element
60
59
  this={onclick ? 'button' : 'div'}
61
60
  type="button"
@@ -42,8 +42,6 @@
42
42
  {#if b_open}
43
43
  <Portal>
44
44
  <HiddenBackground {onclose}>
45
- <!-- svelte-ignore a11y_click_events_have_key_events -->
46
- <!-- svelte-ignore a11y_no_static_element_interactions -->
47
45
  <div
48
46
  class={twMerge(
49
47
  clsx([
@@ -74,14 +74,10 @@
74
74
  class="flex h-full w-full flex-col items-center justify-start p-8 lg:p-12 xl:p-16"
75
75
  >
76
76
  {#if modal}
77
- <!-- svelte-ignore a11y_no_static_element_interactions -->
78
- <!-- svelte-ignore a11y_click_events_have_key_events -->
79
77
  <div {...rest} {onclick}>
80
78
  {@render modal()}
81
79
  </div>
82
80
  {:else}
83
- <!-- svelte-ignore a11y_click_events_have_key_events -->
84
- <!-- svelte-ignore a11y_no_static_element_interactions -->
85
81
  <div
86
82
  class={twMerge(
87
83
  clsx([
@@ -82,6 +82,7 @@
82
82
 
83
83
  // TODO: this is kinda hacky
84
84
  $effect(() => {
85
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
85
86
  [popover, target];
86
87
  postion(b_open);
87
88
  });
@@ -12,4 +12,3 @@ export default Tabs;
12
12
  export { type TabProps } from './Tab.svelte';
13
13
  export { type TabPanelProps } from './TabPanel.svelte';
14
14
  export { getTabContext, type TabContext, type TabsProps } from './Tabs.svelte';
15
-
@@ -1,13 +1,15 @@
1
1
  <script lang="ts" module>
2
- import type { IvoryComponent } from '$lib/types';
3
2
  import clsx from 'clsx';
4
3
  import type { Snippet } from 'svelte';
5
- import type { ClassValue } from 'svelte/elements';
4
+ import type { ClassValue, MouseEventHandler } from 'svelte/elements';
6
5
  import { twMerge } from 'tailwind-merge';
7
6
  import Popover, { type PopoverPlacement } from '../popover/Popover.svelte';
8
7
  import Portal from '../portal/Portal.svelte';
9
8
 
10
- export interface TooltipProps extends IvoryComponent<HTMLElement> {
9
+ export interface TooltipProps {
10
+ onclick?: MouseEventHandler<HTMLElement>;
11
+ class?: ClassValue;
12
+ style?: string;
11
13
  children?: Snippet;
12
14
  /** The content of the tooltip */
13
15
  tooltip: string | Snippet;
@@ -67,8 +69,6 @@
67
69
  @component
68
70
  Shows additional information when hovering over an element.
69
71
  -->
70
- <!-- Ignoring this error is fine since it's a false positive -->
71
- <!-- svelte-ignore a11y_no_static_element_interactions -->
72
72
  <svelte:element
73
73
  this={href ? 'a' : rest.onclick ? 'button' : 'div'}
74
74
  type={rest.onclick ? 'button' : undefined}
@@ -36,8 +36,8 @@
36
36
  }: ColumnProps = $props();
37
37
 
38
38
  // Register the new column if this is the first table row that was rendered
39
- const { table, nestingInset } = getTableContext();
40
- const column = table.registerColumn({ resizable, ...props });
39
+ const { registerColumn, nestingInset } = getTableContext();
40
+ const column = registerColumn({ resizable, ...props });
41
41
  const allowClicking = $derived(!!onclick);
42
42
 
43
43
  // passes updated props to the column
@@ -1,22 +1,22 @@
1
1
  <script lang="ts" module>
2
2
  import { getContext, setContext, type Snippet } from 'svelte';
3
3
  import { resize } from '../../utils/actions';
4
- import type { Column } from './columnController.svelte';
4
+ import type { ColumnController } from './columnController.svelte';
5
5
 
6
6
  const CONTEXT = {};
7
- function setColumnHeadContext(column: Column) {
7
+ function setColumnHeadContext(column: ColumnController) {
8
8
  setContext(CONTEXT, column);
9
9
  }
10
10
 
11
- export function getColumnHeadContext(): Column {
11
+ export function getColumnHeadContext(): ColumnController {
12
12
  return getContext(CONTEXT);
13
13
  }
14
14
  </script>
15
15
 
16
16
  <script lang="ts">
17
17
  type Props = {
18
- column: Column;
19
- children: Snippet;
18
+ column: ColumnController;
19
+ children?: Snippet;
20
20
  };
21
21
 
22
22
  let { column, children }: Props = $props();
@@ -50,7 +50,7 @@
50
50
  bind:this={target}
51
51
  style="width: {column.width}px;"
52
52
  >
53
- {@render children()}
53
+ {@render children?.()}
54
54
  {#if column.resizable}
55
55
  <button
56
56
  type="button"
@@ -51,7 +51,6 @@
51
51
  });
52
52
  </script>
53
53
 
54
- <!-- svelte-ignore a11y_no_static_element_interactions -->
55
54
  <svelte:element
56
55
  this={elementProps.this}
57
56
  {...elementProps}
@@ -1,15 +1,16 @@
1
1
  <script lang="ts" module>
2
- import { pseudoRandomId } from '$lib/utils/functions';
3
2
  import { ChevronRight } from '@lucide/svelte';
4
3
  import clsx from 'clsx';
5
4
  import { getContext, setContext, type Snippet } from 'svelte';
6
5
  import type { ClassValue } from 'svelte/elements';
6
+ import { SvelteSet } from 'svelte/reactivity';
7
7
  import { twMerge } from 'tailwind-merge';
8
- import { Column, type TableConfig, type TablePlugin, type TableRow } from '.';
8
+ import { Column as ColumnComponent, type TablePlugin, type TableRow } from '.';
9
9
  import ColumnHead from './ColumnHead.svelte';
10
10
  import Row from './Row.svelte';
11
11
  import VirtualList from './VirtualList.svelte';
12
- import { createTableConfig } from './controller.svelte';
12
+ import { ColumnController, type ColumnConfig } from './columnController.svelte';
13
+ import { treeWalker, type TableState } from './controller';
13
14
 
14
15
  export interface TableProps<T extends TableRow<T>> {
15
16
  class?: ClassValue;
@@ -24,7 +25,11 @@
24
25
  rowClass?: ClassValue;
25
26
  headerClass?: ClassValue;
26
27
  plugins?: TablePlugin<T>[];
27
- config?: TableConfig<T>;
28
+ /**
29
+ * **Bindable**
30
+ */
31
+ b_columns?: ColumnController[];
32
+ expanded?: SvelteSet<string>;
28
33
  /**
29
34
  * **Bindable**
30
35
  */
@@ -35,9 +40,11 @@
35
40
 
36
41
  const TABLE_CONTEXT = {};
37
42
  export type TableContext<T extends TableRow<T>> = {
38
- table: TableConfig<T>;
43
+ registerColumn: (config: ColumnConfig) => ColumnController;
44
+ toggleExpansion: (id: string) => void;
39
45
  nestingInset: number;
40
46
  };
47
+
41
48
  function setTableContext<T extends TableRow<T>>(context: TableContext<T>) {
42
49
  setContext(TABLE_CONTEXT, context);
43
50
  }
@@ -45,11 +52,11 @@
45
52
  export function getTableContext<T extends TableRow<T>>(): TableContext<T> {
46
53
  return getContext<TableContext<T>>(TABLE_CONTEXT);
47
54
  }
55
+
56
+ const treeIndicatorId = 'tree-chevron';
48
57
  </script>
49
58
 
50
59
  <script lang="ts" generics="T extends TableRow<T>">
51
- interface Props<TI extends { id: string }> extends TableProps<TI> {}
52
-
53
60
  let {
54
61
  class: clazz,
55
62
  data,
@@ -61,31 +68,62 @@
61
68
  onclick,
62
69
  href,
63
70
  plugins = [],
64
- config: table = createTableConfig<T>(),
65
- nestingInset = 4
66
- }: Props<T> = $props();
71
+ expanded: expanded = new SvelteSet<string>(),
72
+ nestingInset = 4,
73
+ b_columns: externalColumns = $bindable(),
74
+ b_scrollTop = $bindable()
75
+ }: TableProps<T> = $props();
67
76
 
68
- $effect(() => {
69
- table.data = data;
70
- table.plugins = plugins;
71
- });
77
+ let columns = $state<ColumnController[]>(externalColumns ?? []);
78
+ const results = $derived(computeResults(data, expanded, plugins));
79
+ let treeIndicatorColumn = $state<ColumnController>();
80
+
81
+ function toggleExpansion(id: string) {
82
+ if (expanded.has(id)) expanded.delete(id);
83
+ else expanded.add(id);
84
+ }
72
85
 
73
86
  setTableContext({
74
- get table() {
75
- return table;
87
+ toggleExpansion,
88
+ registerColumn(config: ColumnConfig) {
89
+ let existingColumn: ColumnController | undefined = undefined;
90
+
91
+ if (config.id === treeIndicatorId) {
92
+ if (!treeIndicatorColumn) treeIndicatorColumn = new ColumnController(config);
93
+ return treeIndicatorColumn;
94
+ }
95
+
96
+ for (const column of existingColumn || columns) {
97
+ if (column.id !== config.id) continue;
98
+ existingColumn = column;
99
+ break;
100
+ }
101
+ if (existingColumn) return existingColumn;
102
+ const col = new ColumnController(config);
103
+ (externalColumns || columns).push(col);
104
+ return col;
76
105
  },
77
106
  get nestingInset() {
78
107
  return nestingInset;
79
108
  }
80
109
  });
81
110
 
82
- const treeIndicatorId = pseudoRandomId('tree-indicator-');
111
+ function computeResults(data: T[], expanded: Set<string>, plugins: TablePlugin<T>[]) {
112
+ let state: TableState<T> = {
113
+ data,
114
+ expanded
115
+ };
116
+ for (const plugin of plugins) {
117
+ state = plugin(state);
118
+ }
119
+ return treeWalker(state);
120
+ }
83
121
  </script>
84
122
 
85
123
  <VirtualList
86
- data={table.results.entries}
124
+ data={results.entries}
87
125
  class={twMerge(clsx(['flex flex-col overflow-hidden border-transparent', clazz]))}
88
- bind:b_scrollTop={table.scrollTop}
126
+ bind:b_scrollTop
89
127
  {rowHeight}
90
128
  >
91
129
  {#snippet header()}
@@ -97,7 +135,10 @@
97
135
  )
98
136
  )}
99
137
  >
100
- {#each table.columns as column (column.id)}
138
+ {#if treeIndicatorColumn}
139
+ <ColumnHead column={treeIndicatorColumn}></ColumnHead>
140
+ {/if}
141
+ {#each externalColumns || columns as column (column.id)}
101
142
  <ColumnHead {column}>
102
143
  {#if typeof column.header === 'function'}
103
144
  {@render column.header()}
@@ -119,15 +160,15 @@
119
160
  class={rowClass}
120
161
  >
121
162
  {@render firstColumn?.({ row: node })}
122
- <Column
163
+ <ColumnComponent
123
164
  id={treeIndicatorId}
124
165
  resizable={false}
125
166
  header=""
126
167
  onclick={() => {
127
- table.toggleExpansion(node.id);
168
+ toggleExpansion(node.id);
128
169
  }}
129
- ignoreWidth={table.results.someHaveChildren}
130
- width={table.results.someHaveChildren ? 24 : 0}
170
+ ignoreWidth={results.someHaveChildren}
171
+ width={results.someHaveChildren ? 24 : 0}
131
172
  minWidth={0}
132
173
  >
133
174
  <div
@@ -138,12 +179,12 @@
138
179
  <ChevronRight
139
180
  class={[
140
181
  'ml-auto aspect-square shrink-0 transition-transform duration-100',
141
- table.expanded.has(id) && 'rotate-90'
182
+ expanded.has(id) && 'rotate-90'
142
183
  ]}
143
184
  />
144
185
  {/if}
145
186
  </div>
146
- </Column>
187
+ </ColumnComponent>
147
188
  {@render passedChildren?.({ row: node, nestingLevel, index })}
148
189
  </Row>
149
190
  {/snippet}
@@ -19,12 +19,12 @@
19
19
  data,
20
20
  children,
21
21
  header,
22
- b_scrollTop = $bindable(0),
22
+ b_scrollTop = $bindable(),
23
23
  rowHeight,
24
24
  overscan = 2
25
25
  }: Props<T> = $props();
26
26
 
27
- let scroll_top = $state(b_scrollTop);
27
+ let scroll_top = $state(b_scrollTop ?? 0);
28
28
  let scroll_left = $state(0);
29
29
  let header_width = $state(0);
30
30
  let viewport = $state<HTMLElement>();
@@ -53,7 +53,7 @@
53
53
  }
54
54
 
55
55
  onMount(() => {
56
- if (!viewport) return;
56
+ if (!viewport || typeof b_scrollTop === 'undefined') return;
57
57
  viewport.scrollTop = b_scrollTop;
58
58
  });
59
59
  </script>
@@ -11,7 +11,7 @@ export interface ColumnConfig {
11
11
  header: Snippet | string;
12
12
  }
13
13
 
14
- export class Column {
14
+ export class ColumnController {
15
15
  id = $state('');
16
16
  header = $state<Snippet | string>('');
17
17
 
@@ -0,0 +1,75 @@
1
+ export type TableRow<T> = { id: string; children?: T[] };
2
+ export type TablePlugin<T extends TableRow<T>> = (state: TableState<T>) => TableState<T>;
3
+
4
+ export interface TableState<T extends TableRow<T>> {
5
+ data: T[];
6
+ expanded: Set<string>;
7
+ }
8
+
9
+ interface TreeRow<T> {
10
+ node: T;
11
+ nestingLevel: number;
12
+ id: string;
13
+ }
14
+
15
+ /** Walks though a tree strucure and turns it into a flat list, needed since the `VirtualList` needs a list, not a tree */
16
+ export function treeWalker<T extends TableRow<T>>(config: TableState<T>) {
17
+ const { data, expanded } = config;
18
+ const stack: { node: T; nestingLevel: number }[] = [];
19
+
20
+ // push the root nodes of the trees onto the stack
21
+ for (let i = 0; i < data.length; i++) {
22
+ stack.push({
23
+ node: data[data.length - i - 1],
24
+ nestingLevel: 0
25
+ });
26
+ }
27
+
28
+ const entries: TreeRow<T>[] = []; // the final result
29
+
30
+ let someHaveChildren = false; // used to determine whether to show the tree utility buttons
31
+ let maxNestingLevel = 0;
32
+ while (stack.length !== 0) {
33
+ const stackEntry = stack.pop();
34
+ if (!stackEntry) break;
35
+
36
+ const { node, nestingLevel } = stackEntry;
37
+ const children = node.children;
38
+
39
+ if (children && children.length > 0) {
40
+ someHaveChildren = true;
41
+ // only show the children of expanded elements
42
+ if (expanded.has(node.id)) {
43
+ maxNestingLevel = Math.max(maxNestingLevel, nestingLevel + 1);
44
+ stack.push(
45
+ ...children
46
+ .map((c) => ({
47
+ node: c,
48
+ nestingLevel: nestingLevel + 1
49
+ }))
50
+ .reverse()
51
+ );
52
+ }
53
+ }
54
+
55
+ entries.push({
56
+ id: node.id,
57
+ node,
58
+ nestingLevel
59
+ });
60
+ }
61
+
62
+ return {
63
+ entries,
64
+ someHaveChildren,
65
+ maxNestingLevel
66
+ };
67
+ }
68
+
69
+ export function getAllIds<T extends TableRow<T>>(...nodes: T[]): string[] {
70
+ const ids = nodes.map((n) => n.id);
71
+ for (const node of nodes) {
72
+ ids.push(...getAllIds(...(node.children ?? [])));
73
+ }
74
+ return ids;
75
+ }
@@ -1,14 +1,7 @@
1
1
  export { default as Column, type ColumnProps } from './Column.svelte';
2
+ export { ColumnController, type ColumnConfig } from './columnController.svelte';
2
3
  export { getColumnHeadContext } from './ColumnHead.svelte';
3
- export {
4
- createTableConfig,
5
- getAllIds,
6
- type TableConfig,
7
- type TablePlugin,
8
- type TableRow,
9
- type TableState
10
- } from './controller.svelte';
11
- export { expandAllPlugin } from './plugins/expandAll.svelte';
4
+ export { getAllIds, type TablePlugin, type TableRow, type TableState } from './controller';
12
5
  export { searchPlugin } from './plugins/search.svelte';
13
6
  export {
14
7
  getTableContext,