@svar-ui/svelte-toolbar 2.5.3 → 2.6.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svar-ui/svelte-toolbar",
3
- "version": "2.5.3",
3
+ "version": "2.6.1",
4
4
  "description": "Svelte toolbar component. Lightweight, customizable, easy-to-use.",
5
5
  "productTag": "toolbar",
6
6
  "productTrial": false,
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "homepage": "https://svar.dev/svelte/core/",
35
35
  "dependencies": {
36
- "@svar-ui/svelte-core": "2.5.3",
37
- "@svar-ui/lib-dom": "0.12.2"
36
+ "@svar-ui/svelte-core": "2.6.1",
37
+ "@svar-ui/lib-dom": "0.14.0"
38
38
  },
39
39
  "files": [
40
40
  "src",
@@ -0,0 +1,38 @@
1
+ <script>
2
+ import { Button } from "@svar-ui/svelte-core";
3
+
4
+ let { css, disabled, options, onchange, value = $bindable() } = $props();
5
+
6
+ function onClickHandler(id) {
7
+ value = id;
8
+ onchange && onchange({ value: id });
9
+ }
10
+ </script>
11
+
12
+ <div class="wx-button-list {css}">
13
+ {#each options as option}
14
+ <Button
15
+ type={value === option.id ? "primary" : ""}
16
+ {disabled}
17
+ text={option.label}
18
+ onclick={() => onClickHandler(option.id)}
19
+ />
20
+ {/each}
21
+ </div>
22
+
23
+ <style>
24
+ .wx-button-list {
25
+ line-height: normal;
26
+ }
27
+ .wx-button-list :global(button) {
28
+ width: 100%;
29
+ }
30
+ .wx-button-list :global(button:not(:first-child)) {
31
+ border-top-left-radius: 0;
32
+ border-top-right-radius: 0;
33
+ }
34
+ .wx-button-list :global(button:not(:last-child)) {
35
+ border-bottom-left-radius: 0;
36
+ border-bottom-right-radius: 0;
37
+ }
38
+ </style>
@@ -1,26 +1,29 @@
1
1
  <script>
2
2
  import { Button } from "@svar-ui/svelte-core";
3
3
 
4
- let { icon, text, css, type, disabled, menu, onclick } = $props();
4
+ let { icon, title, text, tooltip, css, type, disabled, menu, onclick } =
5
+ $props();
5
6
  </script>
6
7
 
7
8
  {#if menu}
8
9
  <!-- svelte-ignore a11y_click_events_have_key_events -->
9
10
  <!-- svelte-ignore a11y_no_static_element_interactions -->
10
- <div class="wx-item" {onclick}>
11
- {#if icon}<i class="{icon} {css}"></i>{/if}
11
+ <div class="wx-item" class:wx-text-icon={text} {onclick}>
12
+ <i class="{icon || 'wxi-empty'} {css || ''}"></i>
12
13
  {text}
13
14
  </div>
14
15
  {:else}
15
- <Button {icon} {type} {css} title={text} {disabled} {onclick} />
16
+ <Button {icon} {type} {css} {disabled} {title} {tooltip} {onclick} />
16
17
  {/if}
17
18
 
18
19
  <style>
19
20
  i {
20
- margin-right: 8px;
21
21
  font-size: var(--wx-icon-size);
22
22
  color: var(--wx-color-font);
23
23
  }
24
+ .wx-text-icon i {
25
+ margin-right: 8px;
26
+ }
24
27
  .wx-item {
25
28
  cursor: pointer;
26
29
  white-space: nowrap;
@@ -5,8 +5,7 @@
5
5
  import Spacer from "../buttons/Spacer.svelte";
6
6
 
7
7
  let { item = {}, menu = false, values, onclick, onchange } = $props();
8
-
9
- let itemComponent = $derived(getItemHandler(item.comp || "label"));
8
+ let itemComponent = $derived(getItemHandler(item.comp, { menu }));
10
9
 
11
10
  function onClick() {
12
11
  if (item.handler) item.handler(item);
@@ -56,30 +55,16 @@
56
55
  display: flex;
57
56
  }
58
57
  .wx-menu.wx-tb-element {
59
- height: 36px;
60
58
  line-height: 36px;
61
59
  padding: 2px 8px;
62
60
  background-color: var(--wx-background);
63
61
  }
64
-
65
62
  .wx-menu :global(button) {
66
63
  width: 100%;
67
- background: transparent;
68
- text-align: left;
69
- padding-left: 0;
70
- font-weight: var(--wx-font-weight);
71
- font-size: var(--wx-font-size);
72
64
  }
73
65
  .wx-menu :global(.wx-tb-element) {
74
66
  align-self: stretch;
75
67
  }
76
- .wx-menu.wx-tb-element :global(button:hover) {
77
- background: var(--wx-background-alt);
78
- }
79
- .wx-menu :global(button i) {
80
- margin-right: 4px;
81
- font-size: var(--wx-icon-size);
82
- }
83
68
  .wx-menu.wx-tb-element:hover {
84
69
  background: var(--wx-background-alt);
85
70
  }
@@ -3,6 +3,7 @@
3
3
 
4
4
  import BarComponent from "./BarComponent.svelte";
5
5
  import { Dropdown } from "@svar-ui/svelte-core";
6
+ import { collapseSeparators } from "../helpers";
6
7
 
7
8
  let { item, values = null, menu = false, onchange, onclick } = $props();
8
9
 
@@ -49,7 +50,7 @@
49
50
  {/if}
50
51
  {:else}
51
52
  <div class="wx-tb-body">
52
- {#each item.items as sub}
53
+ {#each collapseSeparators(item.items) as sub}
53
54
  {#if sub.items}
54
55
  <Group item={sub} {values} onclick={onClick} {onchange} />
55
56
  {:else}
@@ -3,8 +3,8 @@
3
3
  import Group from "./Group.svelte";
4
4
  import BarComponent from "./BarComponent.svelte";
5
5
 
6
- import { uid } from "@svar-ui/lib-dom";
7
6
  import { onMount, tick } from "svelte";
7
+ import { normalizeToolbarItems } from "../helpers";
8
8
 
9
9
  let {
10
10
  items = $bindable([]),
@@ -28,12 +28,63 @@
28
28
  let div = null;
29
29
  let menuItems = $state([]);
30
30
 
31
+ const barItems = $derived(normalizeToolbarItems(items));
32
+ const vertical = $derived(layout === "column");
33
+
34
+ function getTotalWidth() {
35
+ const nodes = div.children;
36
+ let sum = 0;
37
+ for (let i = 0; i < barItems.length; i++) {
38
+ if (barItems[i].comp != "spacer") {
39
+ sum += nodes[i]?.clientWidth || 0;
40
+ if (barItems[i].comp == "separator") sum += 8;
41
+ }
42
+ }
43
+ return sum;
44
+ }
45
+
46
+ function collapseGroups() {
47
+ for (let i = barItems.length - 1; i >= 0; i--) {
48
+ // close rightmost open group
49
+ if (barItems[i].items && !barItems[i].collapsed) {
50
+ barItems[i].collapsed = true;
51
+ barItems[i].$width = div.children[i].offsetWidth;
52
+ // check after dom update, maybe we need to close more
53
+ tick().then(processOverflow);
54
+
55
+ // items are not deep reactive, so we need to trigger the update
56
+ items = [...items];
57
+ return;
58
+ }
59
+ }
60
+ }
61
+
62
+ function expandGroups(freeSpace) {
63
+ for (let i = 0; i < barItems.length; i++) {
64
+ // open leftmost closed group that was collapsed by overflow
65
+ if (barItems[i].collapsed && barItems[i].$width) {
66
+ // check if group can fit in free space
67
+ if (
68
+ barItems[i].$width - div.children[i].offsetWidth <
69
+ freeSpace + 10
70
+ ) {
71
+ barItems[i].collapsed = false;
72
+ // items are not deep reactive, so we need to trigger the update
73
+ items = [...items];
74
+ // check after dom update, maybe we can open one more
75
+ tick().then(processOverflow);
76
+ }
77
+ return;
78
+ }
79
+ }
80
+ }
81
+
31
82
  function processOverflow() {
32
- if (overflow === "wrap") return;
83
+ if (overflow === "wrap" || vertical) return;
33
84
 
34
85
  const nodes = div.children;
35
86
  // restore all items so widths can be measured
36
- for (let i = 0; i < items.length; i++) {
87
+ for (let i = 0; i < barItems.length; i++) {
37
88
  if (nodes[i]) nodes[i].style.display = "";
38
89
  }
39
90
 
@@ -42,32 +93,32 @@
42
93
  const needMenu = fullWidth > visibleWidth;
43
94
 
44
95
  if (needMenu) {
45
- if (overflow === "collapse") return collapseGroups(visibleWidth);
96
+ if (overflow === "collapse") return collapseGroups();
46
97
 
47
98
  // pinned items always stay visible
48
99
  let pinnedWidth = 0;
49
- for (let i = 0; i < items.length; i++) {
50
- if (items[i].pinned) pinnedWidth += nodes[i].clientWidth;
100
+ for (let i = 0; i < barItems.length; i++) {
101
+ if (barItems[i].pinned) pinnedWidth += nodes[i].clientWidth;
51
102
  }
52
103
 
53
104
  let sum = 0;
54
- for (let i = 0; i < items.length; i++) {
55
- if (items[i].pinned) continue;
105
+ for (let i = 0; i < barItems.length; i++) {
106
+ if (barItems[i].pinned) continue;
56
107
  sum += nodes[i].clientWidth;
57
- if (items[i].comp == "separator") sum += 8;
108
+ if (barItems[i].comp == "separator") sum += 8;
58
109
  if (sum > visibleWidth - 40 - pinnedWidth) {
59
110
  // we need to hide nodes[i] and all next non-pinned nodes
60
111
  menuItems = [];
61
- for (let j = i; j < items.length; j++) {
62
- if (items[j].pinned) continue;
63
- menuItems.push(items[j]);
112
+ for (let j = i; j < barItems.length; j++) {
113
+ if (barItems[j].pinned) continue;
114
+ menuItems.push(barItems[j]);
64
115
  nodes[j].style.display = "none";
65
116
  }
66
117
  // hide the ending separator
67
118
  if (
68
119
  i > 0 &&
69
- items[i - 1].comp == "separator" &&
70
- !items[i - 1].pinned
120
+ barItems[i - 1].comp == "separator" &&
121
+ !barItems[i - 1].pinned
71
122
  ) {
72
123
  nodes[i - 1].style.display = "none";
73
124
  }
@@ -83,82 +134,24 @@
83
134
  }
84
135
  }
85
136
 
86
- function getTotalWidth() {
87
- const nodes = div.children;
88
- let sum = 0;
89
- for (let i = 0; i < items.length; i++) {
90
- if (items[i].comp != "spacer") {
91
- sum += nodes[i]?.clientWidth || 0;
92
- if (items[i].comp == "separator") sum += 8;
93
- }
94
- }
95
- return sum;
96
- }
97
-
98
- function collapseGroups() {
99
- for (let i = items.length - 1; i >= 0; i--) {
100
- // close rightmost open group
101
- if (items[i].items && !items[i].collapsed) {
102
- items[i].collapsed = true;
103
- items[i].$width = div.children[i].offsetWidth;
104
- // check after dom update, maybe we need to close more
105
- tick().then(processOverflow);
106
-
107
- // items are not deep reactive, so we need to trigger the update
108
- items = [...items];
109
- return;
110
- }
111
- }
112
- }
113
-
114
- function expandGroups(freeSpace) {
115
- for (let i = 0; i < items.length; i++) {
116
- // close leftmost closed group, that was closed previously
117
- if (items[i].collapsed && items[i].$width) {
118
- // check if group can fit in free space
119
- if (
120
- items[i].$width - div.children[i].offsetWidth <
121
- freeSpace + 10
122
- ) {
123
- items[i].collapsed = false;
124
- // check after dom update, maybe we can open one more
125
- tick().then(processOverflow);
126
- }
127
-
128
- items = [...items];
129
- return;
130
- }
131
- }
132
- }
133
-
134
- // rebuild toolbar items, inject group nodes
135
- function normalize(items) {
136
- items.forEach(item => {
137
- if (!item.id) item.id = uid();
138
- });
139
- return items;
140
- }
141
-
142
137
  onMount(() => {
143
- const resizeObserver = new ResizeObserver(() => processOverflow(div));
138
+ const resizeObserver = new ResizeObserver(() => processOverflow());
144
139
  resizeObserver.observe(div);
145
140
 
146
141
  return () => {
147
- if (resizeObserver) resizeObserver.unobserve(div);
142
+ resizeObserver.unobserve(div);
148
143
  };
149
144
  });
150
-
151
- const visibleItems = $derived(normalize(items));
152
145
  </script>
153
146
 
154
147
  <div
155
148
  class="wx-toolbar {css}"
156
149
  class:wx-wrap={overflow === "wrap"}
157
- class:wx-column={layout == "column"}
150
+ class:wx-column={vertical}
158
151
  class:wx-has-menu={menuItems.length}
159
152
  bind:this={div}
160
153
  >
161
- {#each visibleItems as item}
154
+ {#each barItems as item}
162
155
  {#if item.items}
163
156
  <Group {item} {values} {onclick} onchange={handleChange} />
164
157
  {:else}
@@ -194,5 +187,9 @@
194
187
  }
195
188
  .wx-column {
196
189
  flex-flow: column;
190
+ width: auto;
191
+ }
192
+ .wx-column :global(.wx-tb-element) {
193
+ width: 100%;
197
194
  }
198
195
  </style>
package/src/helpers.js CHANGED
@@ -1,7 +1,42 @@
1
+ import { uid } from "@svar-ui/lib-dom";
2
+
1
3
  const handlers = {};
2
- export function getItemHandler(type) {
3
- return handlers[type] || type;
4
+ export function getItemHandler(type, config = null) {
5
+ // try to get the handler for the config, fallback to the type
6
+ // so we need not register all button types for alternative modes
7
+ const c =
8
+ typeof type === "string"
9
+ ? handlers[getID(type, config)] || handlers[type]
10
+ : type;
11
+ return c || handlers["label"];
4
12
  }
5
- export function registerToolbarItem(type, handler) {
6
- handlers[type] = handler;
13
+ export function registerToolbarItem(type, handler, config = null) {
14
+ handlers[getID(type, config)] = handler;
15
+ }
16
+
17
+ // Assign stable ids and collapse consecutive separators
18
+ export function normalizeToolbarItems(items) {
19
+ if (!items?.length) return [];
20
+ for (const item of items) {
21
+ if (!item.id) item.id = uid();
22
+ }
23
+ return collapseSeparators(items);
24
+ }
25
+
26
+ // Keep the first of consecutive separator
27
+ export function collapseSeparators(items) {
28
+ if (!items?.length) return items || [];
29
+ const out = [];
30
+ let prevWasSeparator = false;
31
+ for (const item of items) {
32
+ const isSeparator = item.comp === "separator";
33
+ if (isSeparator && prevWasSeparator) continue;
34
+ out.push(item);
35
+ prevWasSeparator = isSeparator;
36
+ }
37
+ return out;
38
+ }
39
+
40
+ function getID(type, config = null) {
41
+ return `${type}${config?.menu ? ":menu" : ""}`;
7
42
  }
package/src/index.js CHANGED
@@ -1,18 +1,20 @@
1
+ import { Button } from "@svar-ui/svelte-core";
1
2
  import Toolbar from "./components/Toolbar.svelte";
2
3
  import { registerToolbarItem } from "./helpers.js";
3
4
 
4
5
  import Separator from "./buttons/Separator.svelte";
5
6
  import Spacer from "./buttons/Spacer.svelte";
6
- import Button from "./buttons/Button.svelte";
7
+ import ButtonList from "./buttons/ButtonList.svelte";
7
8
  import Label from "./buttons/Label.svelte";
8
9
  import Icon from "./buttons/Icon.svelte";
9
10
  import Item from "./buttons/Item.svelte";
10
11
 
11
12
  registerToolbarItem("button", Button);
13
+ registerToolbarItem("button", Icon, { menu: true });
12
14
  registerToolbarItem("separator", Separator);
13
15
  registerToolbarItem("spacer", Spacer);
14
16
  registerToolbarItem("label", Label);
15
17
  registerToolbarItem("item", Item);
16
18
  registerToolbarItem("icon", Icon);
17
19
 
18
- export { Toolbar, registerToolbarItem };
20
+ export { Toolbar, ButtonList, registerToolbarItem };
package/types/index.d.ts CHANGED
@@ -5,9 +5,13 @@ export interface IToolbarItem {
5
5
  comp?: string | Component<any>;
6
6
  icon?: string;
7
7
  css?: string;
8
+ type?: string;
9
+ title?: string;
8
10
  text?: string;
11
+ tooltip?: string;
9
12
  menuText?: string;
10
13
  key?: string;
14
+ disabled?: boolean;
11
15
  spacer?: boolean;
12
16
  collapsed?: boolean;
13
17
  handler?: (item: IToolbarItem, value?: any) => void;
package/whatsnew.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 2.6.1
2
+
3
+ ### Updates
4
+
5
+ - Impossible to render an item differently in the `menu` mode
6
+ - Option based items are not usable in the `menu` mode
7
+
8
+ ## 2.6.0
9
+
10
+ ### New features
11
+
12
+ - Tooltip for toolbar items
13
+
14
+ ### Fixes
15
+
16
+ - Duplicate separators are auto removed
17
+
1
18
  ## 2.5.3
2
19
 
3
20
  ### Fixes
@@ -1,29 +0,0 @@
1
- <script>
2
- import { Button } from "@svar-ui/svelte-core";
3
-
4
- let { icon, text = "", css, type, disabled, menu, onclick } = $props();
5
- </script>
6
-
7
- {#if menu}
8
- <!-- svelte-ignore a11y_click_events_have_key_events -->
9
- <!-- svelte-ignore a11y_no_static_element_interactions -->
10
- <div class="wx-item" {onclick}>
11
- <i class="{icon || 'wxi-empty'} {css || ''}"></i>
12
- {text}
13
- </div>
14
- {:else}
15
- <Button {icon} {type} {css} {text} {disabled} {onclick} />
16
- {/if}
17
-
18
- <style>
19
- i {
20
- margin-right: 8px;
21
- font-size: var(--wx-button-icon-size);
22
- color: var(--wx-icon-color);
23
- }
24
- .wx-item {
25
- cursor: pointer;
26
- white-space: nowrap;
27
- line-height: 26px;
28
- }
29
- </style>