@streamscloud/kit 0.65.1 → 0.66.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.
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">import NodeView from './cmp.tree-node-view.svelte';
2
2
  import { createTreeKeyboard } from './tree-keyboard.svelte';
3
- let { nodes, showSelectedCheckmark, header } = $props();
3
+ let { nodes, checked, header } = $props();
4
4
  const nodesComputed = $derived(nodes ? (Array.isArray(nodes) ? nodes : [nodes]) : []);
5
5
  const hideExpandIcon = $derived(nodesComputed.every((f) => !f.children.length));
6
+ const checkedSet = $derived(checked ? new Set(checked) : null);
6
7
  $effect(() => {
7
8
  for (const root of nodesComputed) {
8
9
  root.setForest(() => nodesComputed);
@@ -14,8 +15,8 @@ const keyboard = createTreeKeyboard(() => nodesComputed);
14
15
  <div class="tree">
15
16
  {@render header?.()}
16
17
  <div class="tree__nodes" role="tree">
17
- {#each nodesComputed as node (node.id)}
18
- <NodeView node={node} keyboard={keyboard} hideExpandIcon={hideExpandIcon} showSelectedCheckmark={showSelectedCheckmark} />
18
+ {#each nodesComputed as node, i (node.id)}
19
+ <NodeView node={node} keyboard={keyboard} checked={checkedSet} posInSet={i + 1} setSize={nodesComputed.length} hideExpandIcon={hideExpandIcon} />
19
20
  {/each}
20
21
  </div>
21
22
  </div>
@@ -23,13 +24,13 @@ const keyboard = createTreeKeyboard(() => nodesComputed);
23
24
  <!--
24
25
  @component
25
26
  Tree — hierarchical, collapsible node list driven by `TreeNode` model instances. Each `TreeNode`
26
- holds its own expanded / selected state, children, optional icon / count / actions, and an optional
27
- drag config (reorder or move-to-folder). Build the model with `new TreeNode({ … })` and pass the
28
- root(s) as `nodes`.
27
+ holds its own expanded / selected state, children, optional icon / count / actions, optional drag /
28
+ drop over the shared `DragAndDrop` store, and optional inline rename. Build the model with
29
+ `new TreeNode({ … })` and pass the root(s) as `nodes`; `checked` marks any set of nodes with a checkmark.
29
30
 
30
31
  Click selects a node — selection is the single source of truth. Keyboard: the current row is the tab
31
32
  stop (native focus + `:focus-visible`), ↑/↓ move between visible rows, →/← expand-or-enter / collapse-or-leave,
32
- Home/End jump to first/last, Enter/Space select.
33
+ Home/End jump to first/last, Enter/Space select, F2 renames. In rename mode Enter / blur commit, Esc cancels.
33
34
 
34
35
  ### CSS Custom Properties
35
36
  | Property | Description | Default |
@@ -3,20 +3,20 @@ import type { Snippet } from 'svelte';
3
3
  type Props = {
4
4
  /** Root node, or array of root nodes. */
5
5
  nodes: TreeNode | readonly TreeNode[];
6
- /** Show a success checkmark on the selected node when it has no actions. */
7
- showSelectedCheckmark?: boolean;
6
+ /** Ids of nodes that show a checkmark. Display only — the tree never changes it. */
7
+ checked?: readonly string[];
8
8
  /** Optional content rendered above the nodes (e.g. a header). */
9
9
  header?: Snippet;
10
10
  };
11
11
  /**
12
12
  * Tree — hierarchical, collapsible node list driven by `TreeNode` model instances. Each `TreeNode`
13
- * holds its own expanded / selected state, children, optional icon / count / actions, and an optional
14
- * drag config (reorder or move-to-folder). Build the model with `new TreeNode({ … })` and pass the
15
- * root(s) as `nodes`.
13
+ * holds its own expanded / selected state, children, optional icon / count / actions, optional drag /
14
+ * drop over the shared `DragAndDrop` store, and optional inline rename. Build the model with
15
+ * `new TreeNode({ … })` and pass the root(s) as `nodes`; `checked` marks any set of nodes with a checkmark.
16
16
  *
17
17
  * Click selects a node — selection is the single source of truth. Keyboard: the current row is the tab
18
18
  * stop (native focus + `:focus-visible`), ↑/↓ move between visible rows, →/← expand-or-enter / collapse-or-leave,
19
- * Home/End jump to first/last, Enter/Space select.
19
+ * Home/End jump to first/last, Enter/Space select, F2 renames. In rename mode Enter / blur commit, Esc cancels.
20
20
  *
21
21
  * ### CSS Custom Properties
22
22
  * | Property | Description | Default |
@@ -1,4 +1,3 @@
1
1
  export { default as Tree } from './cmp.tree.svelte';
2
- export { TreeNode, type TreeNodeInitializer } from './tree-node.svelte';
2
+ export { TreeNode, type TreeNodeIcon, type TreeNodeInitializer } from './tree-node.svelte';
3
3
  export type { TreeNodeAction } from './tree-node-action';
4
- export type { TreeNodeDragConfig, TreeNodeMoveToFolderCallbackParams } from './tree-node-drag-config.svelte';
@@ -0,0 +1,13 @@
1
+ import type { TreeNode } from './tree-node.svelte';
2
+ declare class TreeDropTargetContainer {
3
+ node: TreeNode | null;
4
+ private _element;
5
+ private _listening;
6
+ claim(node: TreeNode, element: HTMLElement): void;
7
+ private _release;
8
+ private _releaseOutside;
9
+ private _listen;
10
+ }
11
+ /** The single row under the pointer that accepts the current drag, shared by every row of every tree. */
12
+ export declare const TreeDropTarget: TreeDropTargetContainer;
13
+ export {};
@@ -0,0 +1,30 @@
1
+ class TreeDropTargetContainer {
2
+ node = $state.raw(null);
3
+ _element = null;
4
+ _listening = false;
5
+ claim(node, element) {
6
+ this.node = node;
7
+ this._element = element;
8
+ this._listen();
9
+ }
10
+ _release = () => {
11
+ this.node = null;
12
+ this._element = null;
13
+ };
14
+ _releaseOutside = (e) => {
15
+ if (!(e.target instanceof Node) || !this._element?.contains(e.target)) {
16
+ this._release();
17
+ }
18
+ };
19
+ _listen() {
20
+ if (this._listening) {
21
+ return;
22
+ }
23
+ this._listening = true;
24
+ window.addEventListener('dragenter', this._releaseOutside, true);
25
+ window.addEventListener('drop', this._release, true);
26
+ window.addEventListener('dragend', this._release, true);
27
+ }
28
+ }
29
+ /** The single row under the pointer that accepts the current drag, shared by every row of every tree. */
30
+ export const TreeDropTarget = new TreeDropTargetContainer();
@@ -4,6 +4,7 @@ export declare const createTreeKeyboard: (getRoots: () => readonly TreeNode[]) =
4
4
  anchorId: (node: TreeNode) => string;
5
5
  readonly currentId: string | null;
6
6
  setFocused: (id: string) => void;
7
+ focusAfterTick: (node: TreeNode) => Promise<void>;
7
8
  handleKeydown: (node: TreeNode, e: KeyboardEvent) => void;
8
9
  };
9
10
  export type TreeKeyboard = ReturnType<typeof createTreeKeyboard>;
@@ -52,6 +52,7 @@ export const createTreeKeyboard = (getRoots) => {
52
52
  setFocused: (id) => {
53
53
  focusedId = id;
54
54
  },
55
+ focusAfterTick,
55
56
  handleKeydown: (node, e) => {
56
57
  switch (e.key) {
57
58
  case 'ArrowDown':
@@ -101,6 +102,12 @@ export const createTreeKeyboard = (getRoots) => {
101
102
  e.preventDefault();
102
103
  node.select({ explicitly: true });
103
104
  break;
105
+ case 'F2':
106
+ if (node.renamable) {
107
+ e.preventDefault();
108
+ node.startRename();
109
+ }
110
+ break;
104
111
  }
105
112
  }
106
113
  };
@@ -0,0 +1,3 @@
1
+ export declare class TreeLocalization {
2
+ get nodeName(): string;
3
+ }
@@ -0,0 +1,12 @@
1
+ import { AppLocale } from '../../core/locale';
2
+ const loc = {
3
+ nodeName: {
4
+ en: 'Name',
5
+ no: 'Navn'
6
+ }
7
+ };
8
+ export class TreeLocalization {
9
+ get nodeName() {
10
+ return loc.nodeName[AppLocale.current];
11
+ }
12
+ }
@@ -1,5 +1,21 @@
1
+ import type { DragEventModel } from '../drag-and-drop';
1
2
  import type { TreeNodeAction } from './tree-node-action';
2
- import type { TreeNodeDragConfig } from './tree-node-drag-config.svelte';
3
+ /** SVG source, a round image, or `null` for no icon. */
4
+ export type TreeNodeIcon = string | {
5
+ image: string;
6
+ } | null;
7
+ type TreeNodeHandlers = {
8
+ select?: (node: TreeNode, opt: {
9
+ selectedExplicitly: boolean;
10
+ }) => void;
11
+ hover?: {
12
+ enter: (node: TreeNode) => void;
13
+ leave: (node: TreeNode) => void;
14
+ };
15
+ drop?: (e: DragEventModel<unknown>) => void;
16
+ /** Enables inline rename (`startRename()`, F2). Resolve `false` to reject the name and keep editing; on `true` the node takes the new name. A throw counts as `false`. */
17
+ rename?: (name: string) => boolean | Promise<boolean>;
18
+ };
3
19
  export type TreeNodeInitializer = {
4
20
  name?: string;
5
21
  id?: string;
@@ -10,45 +26,42 @@ export type TreeNodeInitializer = {
10
26
  meta?: string | null;
11
27
  /** Destination route for this node. The kit does not navigate; read it in your selection handler (e.g. `goto(node.href)`). */
12
28
  href?: string | null;
13
- icon?: string;
14
- imgIcon?: string;
29
+ /** `null` renders no icon. @default folder icon */
30
+ icon?: TreeNodeIcon;
15
31
  count?: number;
16
- hideIcon?: boolean;
17
- onNodeSelected?: (node: TreeNode, opt: {
18
- selectedExplicitly: boolean;
19
- }) => void;
20
- onNodeHover?: {
21
- mouseover: (node: TreeNode) => void;
22
- mouseleave: (node: TreeNode) => void;
23
- };
24
- dragConfig?: TreeNodeDragConfig;
32
+ /** Payload the row puts into the `DragAndDrop` store when dragged. */
33
+ drag?: DragEventModel<unknown>;
34
+ /** Decides per `DragAndDrop` payload whether this node takes the drop (`on.drop`). */
35
+ accepts?: (e: DragEventModel<unknown>) => boolean;
25
36
  children?: TreeNode[];
26
37
  actions?: TreeNodeAction[];
38
+ /** `select` / `hover` go to the nearest handler up the chain from the target node (self included) — only that one fires. */
39
+ on?: TreeNodeHandlers;
27
40
  };
28
41
  export declare class TreeNode {
29
42
  id: string;
30
43
  name: string;
31
- icon: string;
44
+ icon: TreeNodeIcon;
32
45
  count?: number;
33
46
  expandOnDoubleClick: boolean;
34
47
  meta?: string | null;
35
48
  href?: string | null;
36
- private _dragConfig?;
37
- private _onNodeSelected?;
38
- private _onNodeHover?;
39
- private _imgIcon;
40
- private _hideIcon;
49
+ private _drag?;
50
+ private _accepts?;
51
+ private _on;
52
+ private _renaming;
53
+ private _renamePending;
41
54
  private _expanded;
42
55
  private _selected;
43
- private _involvedIntoActivePath;
44
56
  private _actions;
45
57
  private _nonSelectable;
46
58
  private _children;
47
59
  private _parent?;
48
60
  private _forestRoots?;
49
61
  private _dirty;
62
+ private _hoverLeave?;
50
63
  constructor(init: TreeNodeInitializer);
51
- /** Whether this node has unsaved changes; set via `setDirty` and propagated up to ancestors. */
64
+ /** Marked itself via `setDirty`, or has a dirty descendant. */
52
65
  get dirty(): boolean;
53
66
  get expanded(): boolean;
54
67
  get selected(): boolean;
@@ -56,30 +69,37 @@ export declare class TreeNode {
56
69
  get actions(): TreeNodeAction[];
57
70
  get parent(): TreeNode | undefined;
58
71
  get nonSelectable(): boolean;
59
- get involvedIntoActivePath(): boolean;
60
- get imgIcon(): string;
61
- get hideIcon(): boolean;
62
- get dragConfig(): TreeNodeDragConfig | undefined;
72
+ get drag(): DragEventModel<unknown> | undefined;
73
+ get renamable(): boolean;
74
+ get renaming(): boolean;
75
+ /** A committed name is waiting for `on.rename` to answer. */
76
+ get renamePending(): boolean;
77
+ accepts(e: DragEventModel<unknown>): boolean;
78
+ /** Hands the payload to `on.drop`; the caller has already checked `accepts`. */
79
+ receiveDrop(e: DragEventModel<unknown>): void;
80
+ startRename(): void;
81
+ cancelRename(): void;
82
+ /** Resolves `false` when the name was rejected or a commit is already pending — the node stays in rename mode. Empty or unchanged input just leaves rename mode. */
83
+ commitRename(value: string): Promise<boolean>;
63
84
  select(options?: {
64
85
  explicitly?: boolean;
65
86
  }): void;
66
87
  /** Register the full set of forest roots so single-select clears across sibling roots, not just this node's own root. Set by the `Tree` component. */
67
88
  setForest(getRoots: () => readonly TreeNode[]): void;
68
89
  setChildren(value: TreeNode[]): void;
90
+ /** Detaches the child from its current parent first, so a move is a single call. */
69
91
  addChild(child: TreeNode): void;
70
- insertChildAt(index: number, child: TreeNode): void;
71
92
  removeChild(child: TreeNode): void;
72
93
  setDirty(value: boolean): void;
73
- notifyHover(mouseOver: boolean): void;
94
+ notifyHover(entered: boolean): void;
74
95
  collapse(): void;
75
96
  expand(): void;
76
97
  unselect(): void;
77
98
  flatten(): TreeNode[];
78
- canMoveToParent(targetParent: TreeNode): boolean;
79
- moveToFolder(targetFolder: TreeNode): boolean;
80
- private _refreshParentRefs;
81
99
  private _select;
100
+ private _nearestHandler;
82
101
  private _firstSelectable;
83
- private _unselectNodeAndChildren;
84
- private _buildChainToCurrentNode;
102
+ private _clearSelection;
103
+ private _chain;
85
104
  }
105
+ export {};
@@ -3,46 +3,44 @@ import IconDocumentFolder from '@fluentui/svg-icons/icons/document_folder_20_reg
3
3
  export class TreeNode {
4
4
  id = randomNanoid(9);
5
5
  name = $state('');
6
- icon = $state('');
6
+ icon = $state(null);
7
7
  count = $state(undefined);
8
8
  expandOnDoubleClick = $state(false);
9
9
  meta = $state(undefined);
10
10
  href = $state(undefined);
11
- _dragConfig;
12
- _onNodeSelected;
13
- _onNodeHover;
14
- _imgIcon;
15
- _hideIcon;
11
+ _drag;
12
+ _accepts;
13
+ _on;
14
+ _renaming = $state(false);
15
+ _renamePending = $state(false);
16
16
  _expanded = $state(false);
17
17
  _selected = $state(false);
18
- _involvedIntoActivePath = $state(false);
19
18
  _actions = [];
20
19
  _nonSelectable;
21
20
  _children = $state([]);
22
21
  _parent;
23
22
  _forestRoots;
24
23
  _dirty = $state(false);
24
+ _hoverLeave;
25
25
  constructor(init) {
26
26
  this.id = init.id || this.id;
27
27
  this.name = init.name || '';
28
28
  this.href = init.href;
29
29
  this.meta = init.meta;
30
30
  this._nonSelectable = init.nonSelectable || false;
31
- this.icon = init.icon || (init.imgIcon ? '' : IconDocumentFolder);
32
- this._imgIcon = init.imgIcon || '';
31
+ this.icon = init.icon === undefined ? IconDocumentFolder : init.icon;
33
32
  this.count = init.count;
34
- this._hideIcon = init.hideIcon || false;
35
- this._onNodeSelected = init.onNodeSelected;
36
- this._onNodeHover = init.onNodeHover;
37
- this._dragConfig = init.dragConfig;
33
+ this._drag = init.drag;
34
+ this._accepts = init.accepts;
35
+ this._on = init.on ?? {};
38
36
  this.setChildren(init.children || []);
39
37
  this._expanded = init.initiallyExpanded || false;
40
38
  this.expandOnDoubleClick = init.expandOnDoubleClick || false;
41
39
  this._actions = init.actions || [];
42
40
  }
43
- /** Whether this node has unsaved changes; set via `setDirty` and propagated up to ancestors. */
41
+ /** Marked itself via `setDirty`, or has a dirty descendant. */
44
42
  get dirty() {
45
- return this._dirty;
43
+ return this._dirty || this._children.some((c) => c.dirty);
46
44
  }
47
45
  get expanded() {
48
46
  return this._expanded;
@@ -62,17 +60,66 @@ export class TreeNode {
62
60
  get nonSelectable() {
63
61
  return this._nonSelectable;
64
62
  }
65
- get involvedIntoActivePath() {
66
- return this._involvedIntoActivePath;
63
+ get drag() {
64
+ return this._drag;
67
65
  }
68
- get imgIcon() {
69
- return this._imgIcon;
66
+ get renamable() {
67
+ return !!this._on.rename;
70
68
  }
71
- get hideIcon() {
72
- return this._hideIcon;
69
+ get renaming() {
70
+ return this._renaming;
73
71
  }
74
- get dragConfig() {
75
- return this._dragConfig;
72
+ /** A committed name is waiting for `on.rename` to answer. */
73
+ get renamePending() {
74
+ return this._renamePending;
75
+ }
76
+ accepts(e) {
77
+ return !!this._on.drop && !!this._accepts?.(e);
78
+ }
79
+ /** Hands the payload to `on.drop`; the caller has already checked `accepts`. */
80
+ receiveDrop(e) {
81
+ this._on.drop?.(e);
82
+ }
83
+ startRename() {
84
+ if (this._on.rename) {
85
+ this._renaming = true;
86
+ }
87
+ }
88
+ cancelRename() {
89
+ if (!this._renamePending) {
90
+ this._renaming = false;
91
+ }
92
+ }
93
+ /** Resolves `false` when the name was rejected or a commit is already pending — the node stays in rename mode. Empty or unchanged input just leaves rename mode. */
94
+ async commitRename(value) {
95
+ if (!this._renaming) {
96
+ return true;
97
+ }
98
+ if (this._renamePending) {
99
+ return false;
100
+ }
101
+ const name = value.trim();
102
+ const rename = this._on.rename;
103
+ if (!rename || !name || name === this.name) {
104
+ this._renaming = false;
105
+ return true;
106
+ }
107
+ this._renamePending = true;
108
+ let accepted;
109
+ try {
110
+ accepted = await rename(name);
111
+ }
112
+ catch {
113
+ accepted = false;
114
+ }
115
+ finally {
116
+ this._renamePending = false;
117
+ }
118
+ if (accepted) {
119
+ this.name = name;
120
+ this._renaming = false;
121
+ }
122
+ return accepted;
76
123
  }
77
124
  select(options) {
78
125
  this._select(options?.explicitly ?? false);
@@ -83,44 +130,39 @@ export class TreeNode {
83
130
  }
84
131
  setChildren(value) {
85
132
  this._children = value;
86
- this._refreshParentRefs(value);
133
+ for (const child of value) {
134
+ child._parent = this;
135
+ }
87
136
  }
137
+ /** Detaches the child from its current parent first, so a move is a single call. */
88
138
  addChild(child) {
139
+ child._parent?.removeChild(child);
89
140
  child._parent = this;
90
141
  this._children.push(child);
91
142
  }
92
- insertChildAt(index, child) {
93
- child._parent = this;
94
- this._children.splice(index, 0, child);
95
- }
96
143
  removeChild(child) {
97
144
  const i = this._children.indexOf(child);
98
145
  if (i < 0) {
99
146
  return;
100
147
  }
101
148
  this._children.splice(i, 1);
102
- child._parent = undefined;
149
+ if (child._parent === this) {
150
+ child._parent = undefined;
151
+ }
103
152
  }
104
153
  setDirty(value) {
105
154
  this._dirty = value;
106
- if (this._parent) {
107
- const parent = this._parent;
108
- const children = parent.flatten().filter((f) => f.id !== parent.id);
109
- parent.setDirty(children.some((f) => f._dirty));
110
- }
111
- }
112
- notifyHover(mouseOver) {
113
- const selectionPath = this._buildChainToCurrentNode(this);
114
- for (const item of selectionPath) {
115
- if (item._onNodeHover) {
116
- if (mouseOver) {
117
- item._onNodeHover.mouseover(this);
118
- }
119
- else {
120
- item._onNodeHover.mouseleave(this);
121
- }
122
- return;
123
- }
155
+ }
156
+ notifyHover(entered) {
157
+ if (entered) {
158
+ const hover = this._nearestHandler('hover');
159
+ this._hoverLeave = hover?.leave;
160
+ hover?.enter(this);
161
+ }
162
+ else {
163
+ const leave = this._hoverLeave;
164
+ this._hoverLeave = undefined;
165
+ leave?.(this);
124
166
  }
125
167
  }
126
168
  collapse() {
@@ -133,7 +175,7 @@ export class TreeNode {
133
175
  this._expanded = true;
134
176
  }
135
177
  unselect() {
136
- this._unselectNodeAndChildren(this);
178
+ this._clearSelection();
137
179
  }
138
180
  flatten() {
139
181
  const result = [];
@@ -144,74 +186,25 @@ export class TreeNode {
144
186
  processNode(this);
145
187
  return result;
146
188
  }
147
- canMoveToParent(targetParent) {
148
- if (!this._dragConfig) {
149
- return false;
150
- }
151
- if (!targetParent._dragConfig) {
152
- return false;
153
- }
154
- const canMoveTo = (targetParent) => {
155
- let current = targetParent;
156
- while (current) {
157
- if (current.id === this.id) {
158
- return false;
159
- }
160
- current = current._parent;
161
- }
162
- return true;
163
- };
164
- if (!canMoveTo(targetParent)) {
165
- return false;
166
- }
167
- return targetParent._dragConfig.acceptZones.includes(this._dragConfig.dragZone);
168
- }
169
- moveToFolder(targetFolder) {
170
- if (!this._parent || !this._dragConfig || !this.canMoveToParent(targetFolder)) {
171
- return false;
172
- }
173
- this._dragConfig.onNodeMovedToFolder({ movedNode: this, targetFolder });
174
- return true;
175
- }
176
- _refreshParentRefs(children) {
177
- for (const child of children) {
178
- child._parent = this;
179
- if (child._children.length) {
180
- child._refreshParentRefs(child._children);
181
- }
182
- }
183
- }
184
189
  _select(selectedExplicitly) {
185
190
  if (this._nonSelectable) {
186
191
  this._firstSelectable()?._select(selectedExplicitly);
187
192
  return;
188
193
  }
189
- const selectionPath = this._buildChainToCurrentNode(this);
194
+ const selectionPath = this._chain();
190
195
  const root = selectionPath[selectionPath.length - 1];
191
196
  const roots = root._forestRoots?.() ?? [root];
192
197
  for (const r of roots) {
193
- this._unselectNodeAndChildren(r);
198
+ r._clearSelection();
194
199
  }
195
- // process selection path
196
200
  this._selected = true;
197
- if (selectionPath.length > 1) {
198
- for (let i = 1, length = selectionPath.length; i < length; i++) {
199
- const item = selectionPath[i];
200
- item._expanded = true;
201
- }
202
- }
203
- let callbackInvoked = false;
204
- for (let i = 0, length = selectionPath.length; i < length; i++) {
205
- const item = selectionPath[i];
206
- item._involvedIntoActivePath = true;
207
- if (callbackInvoked) {
208
- continue;
209
- }
210
- if (item._onNodeSelected) {
211
- item._onNodeSelected(this, { selectedExplicitly });
212
- callbackInvoked = true;
213
- }
201
+ for (const ancestor of selectionPath.slice(1)) {
202
+ ancestor._expanded = true;
214
203
  }
204
+ this._nearestHandler('select')?.(this, { selectedExplicitly });
205
+ }
206
+ _nearestHandler(key) {
207
+ return this._chain().find((n) => n._on[key])?._on[key];
215
208
  }
216
209
  _firstSelectable() {
217
210
  if (!this._nonSelectable) {
@@ -225,24 +218,11 @@ export class TreeNode {
225
218
  }
226
219
  return null;
227
220
  }
228
- _unselectNodeAndChildren(node) {
229
- node._involvedIntoActivePath = false;
230
- node._selected = false;
231
- const children = node._children;
232
- if (!children.length) {
233
- return;
234
- }
235
- children.forEach((f) => {
236
- this._unselectNodeAndChildren(f);
237
- });
221
+ _clearSelection() {
222
+ this._selected = false;
223
+ this._children.forEach((c) => c._clearSelection());
238
224
  }
239
- _buildChainToCurrentNode(node) {
240
- const chain = [];
241
- let current = node;
242
- while (current) {
243
- chain.push(current);
244
- current = current._parent;
245
- }
246
- return chain;
225
+ _chain() {
226
+ return this._parent ? [this, ...this._parent._chain()] : [this];
247
227
  }
248
228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.65.1",
3
+ "version": "0.66.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,16 +0,0 @@
1
- import type { TreeNode } from './tree-node.svelte';
2
- export type TreeNodeDragConfig = {
3
- /** This node's own drag type (e.g. `'folder'`, `'jpeg'`). */
4
- dragZone: string;
5
- /** Drag types this node accepts dropped into it (e.g. a folder accepts `['folder', 'jpeg', 'mp3']`; a file accepts `[]`). */
6
- acceptZones: string[];
7
- onNodeMovedToFolder: (params: TreeNodeMoveToFolderCallbackParams) => void;
8
- };
9
- export type TreeNodeMoveToFolderCallbackParams = {
10
- movedNode: TreeNode;
11
- targetFolder: TreeNode;
12
- };
13
- export declare const DraggingNodeState: {
14
- draggingNode: TreeNode | null;
15
- dragOverNode: TreeNode | null;
16
- };
@@ -1,4 +0,0 @@
1
- export const DraggingNodeState = $state({
2
- draggingNode: null,
3
- dragOverNode: null
4
- });