wx-svelte-core 2.4.1 → 2.5.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.
- package/package.json +3 -3
- package/readme.md +3 -1
- package/src/components/Avatar.svelte +153 -0
- package/src/components/ColorBoard.svelte +1 -1
- package/src/components/ColorPicker.svelte +2 -1
- package/src/components/ColorSelect.svelte +2 -2
- package/src/components/Combo.svelte +7 -1
- package/src/components/DatePicker.svelte +3 -3
- package/src/components/DateRangePicker.svelte +3 -3
- package/src/components/Dropdown.svelte +25 -107
- package/src/components/Field.svelte +5 -11
- package/src/components/MultiCombo.svelte +12 -24
- package/src/components/Pager.svelte +1 -0
- package/src/components/Popup.svelte +38 -10
- package/src/components/Portal.svelte +1 -0
- package/src/components/RichSelect.svelte +2 -1
- package/src/components/TimePicker.svelte +3 -1
- package/src/components/helpers/InlineDropdown.svelte +127 -0
- package/src/components/helpers/SuggestDropdown.svelte +225 -32
- package/src/components/helpers/dropdown.js +5 -0
- package/src/components/helpers/getInputId.js +2 -2
- package/src/components/helpers/listnav.js +4 -3
- package/src/index.js +1 -0
- package/src/themes/Material.svelte +3 -3
- package/src/themes/Willow.svelte +2 -2
- package/src/themes/WillowDark.svelte +3 -2
- package/types/index.d.ts +58 -18
- package/whatsnew.md +11 -0
- /package/src/themes/{FonttRoboto.svelte → FontRoboto.svelte} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wx-svelte-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "SVAR Svelte Core - Svelte UI library of 20+ components and form controls",
|
|
5
5
|
"productTag": "core",
|
|
6
6
|
"productTrial": false,
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://svar.dev/svelte/core/",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@svar-ui/core-locales": "2.
|
|
37
|
-
"@svar-ui/lib-dom": "0.12.
|
|
36
|
+
"@svar-ui/core-locales": "2.5.0",
|
|
37
|
+
"@svar-ui/lib-dom": "0.12.1",
|
|
38
38
|
"@svar-ui/lib-svelte": "0.5.2"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
package/readme.md
CHANGED
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
[SVAR Svelte Core library](https://svar.dev/svelte/core/) offers
|
|
21
|
+
[SVAR Svelte Core library](https://svar.dev/svelte/core/) offers 30+ lightweight, fast-performing Svelte UI components with TypeScript support. Beautifully designed light and dark themes are included and fully customizable via CSS variables. A straightforward API and comprehensive documentation make it easy to start building feature-rich Svelte interfaces faster.
|
|
22
|
+
|
|
23
|
+
|
|
22
24
|
|
|
23
25
|
<img src="https://svar.dev/images/github/github-core.png" alt="SVAR Core - Svelte UI Components Library" style="width: 752px;">
|
|
24
26
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let { value, size = 32, limit } = $props();
|
|
3
|
+
|
|
4
|
+
const DEFAULT_BG = "#dfe2e6";
|
|
5
|
+
const DEFAULT_FONT = "#2c2f3c";
|
|
6
|
+
|
|
7
|
+
/** Overlap factor: each avatar after the first adds 75% of size (25% overlap). */
|
|
8
|
+
const OVERLAP_FACTOR = 0.75;
|
|
9
|
+
|
|
10
|
+
let containerEl = $state(null);
|
|
11
|
+
let containerWidth = $state(null);
|
|
12
|
+
|
|
13
|
+
const users = $derived.by(() => {
|
|
14
|
+
if (!value) return [];
|
|
15
|
+
return Array.isArray(value) ? value : [value];
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** Max avatars that fit in container. Formula: width = size + (n-1) * size * 0.75. */
|
|
19
|
+
const maxFitting = $derived.by(() => {
|
|
20
|
+
if (containerWidth == null || containerWidth <= 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const n = 1 + (containerWidth / size - 1) / OVERLAP_FACTOR;
|
|
24
|
+
return Math.max(1, Math.floor(n));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const displayCount = $derived.by(() => {
|
|
28
|
+
const cap =
|
|
29
|
+
limit != null ? Math.min(users.length, limit) : users.length;
|
|
30
|
+
if (maxFitting != null) {
|
|
31
|
+
return Math.min(cap, maxFitting);
|
|
32
|
+
}
|
|
33
|
+
return cap;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const displayUsers = $derived(users.slice(0, displayCount));
|
|
37
|
+
const overflowCount = $derived(Math.max(0, users.length - displayCount));
|
|
38
|
+
|
|
39
|
+
$effect(() => {
|
|
40
|
+
const el = containerEl;
|
|
41
|
+
if (!el) return;
|
|
42
|
+
const ro = new ResizeObserver(entries => {
|
|
43
|
+
const entry = entries[0];
|
|
44
|
+
if (entry) containerWidth = entry.contentRect.width;
|
|
45
|
+
});
|
|
46
|
+
ro.observe(el);
|
|
47
|
+
return () => ro.disconnect();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function getInitials(name) {
|
|
51
|
+
name = name?.trim() || "";
|
|
52
|
+
if (!name) return "";
|
|
53
|
+
const words = name.split(/\s+/);
|
|
54
|
+
return (words[0][0] + (words[1]?.[0] || "")).toUpperCase().slice(0, 2);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getContrastColor(hex) {
|
|
58
|
+
if (!hex) return DEFAULT_FONT;
|
|
59
|
+
let h = hex.replace("#", "");
|
|
60
|
+
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
61
|
+
if (h.length !== 6) return DEFAULT_FONT;
|
|
62
|
+
const r = parseInt(h.slice(0, 2), 16) / 255;
|
|
63
|
+
const g = parseInt(h.slice(2, 4), 16) / 255;
|
|
64
|
+
const b = parseInt(h.slice(4, 6), 16) / 255;
|
|
65
|
+
const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
66
|
+
return luminance > 0.5 ? DEFAULT_FONT : "#ffffff";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const fontSize = $derived(Math.round(size * 0.4));
|
|
70
|
+
const avatarBaseStyle = $derived(
|
|
71
|
+
`width:${size}px;height:${size}px;min-width:${size}px;min-height:${size}px;font-size:${fontSize}px;`
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
function getAvatarItemStyle(user, index) {
|
|
75
|
+
const margin = index === 0 ? "0" : `${size * -0.25}px`;
|
|
76
|
+
const bg = user.avatar ? "transparent" : user.color || DEFAULT_BG;
|
|
77
|
+
const color = user.avatar
|
|
78
|
+
? "transparent"
|
|
79
|
+
: getContrastColor(user.color || DEFAULT_BG);
|
|
80
|
+
return `margin-left:${margin};background-color:${bg};color:${color};`;
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<div class="wx-avatar-root" bind:this={containerEl}>
|
|
85
|
+
{#if displayUsers.length > 0}
|
|
86
|
+
<div class="wx-avatar-stack">
|
|
87
|
+
{#each displayUsers as user, index (user.id)}
|
|
88
|
+
<div
|
|
89
|
+
class="wx-avatar wx-avatar-item"
|
|
90
|
+
class:wx-avatar-overflow={index ===
|
|
91
|
+
displayUsers.length - 1 && overflowCount > 0}
|
|
92
|
+
style={avatarBaseStyle + getAvatarItemStyle(user, index)}
|
|
93
|
+
>
|
|
94
|
+
{#if user.avatar}
|
|
95
|
+
<img src={user.avatar} alt="" loading="lazy" />
|
|
96
|
+
{:else if getInitials(user.name)}
|
|
97
|
+
<span>{getInitials(user.name)}</span>
|
|
98
|
+
{/if}
|
|
99
|
+
{#if index === displayUsers.length - 1 && overflowCount > 0}
|
|
100
|
+
<span class="wx-avatar-overflow-badge"
|
|
101
|
+
>+{overflowCount}</span
|
|
102
|
+
>
|
|
103
|
+
{/if}
|
|
104
|
+
</div>
|
|
105
|
+
{/each}
|
|
106
|
+
</div>
|
|
107
|
+
{/if}
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<style>
|
|
111
|
+
.wx-avatar {
|
|
112
|
+
position: relative;
|
|
113
|
+
border-radius: 50%;
|
|
114
|
+
display: inline-flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
line-height: 1;
|
|
120
|
+
user-select: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.wx-avatar img {
|
|
124
|
+
width: 100%;
|
|
125
|
+
height: 100%;
|
|
126
|
+
object-fit: cover;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.wx-avatar span {
|
|
130
|
+
text-transform: uppercase;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.wx-avatar-overflow .wx-avatar-overflow-badge {
|
|
134
|
+
position: absolute;
|
|
135
|
+
inset: 0;
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
background: rgba(0, 0, 0, 0.5);
|
|
140
|
+
color: #fff;
|
|
141
|
+
text-transform: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.wx-avatar-stack {
|
|
145
|
+
display: inline-flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.wx-avatar-root {
|
|
150
|
+
display: block;
|
|
151
|
+
min-width: 0;
|
|
152
|
+
}
|
|
153
|
+
</style>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
error = false,
|
|
13
13
|
clear = false,
|
|
14
14
|
onchange,
|
|
15
|
+
dropdown = {},
|
|
15
16
|
} = $props();
|
|
16
17
|
|
|
17
18
|
const inputId = $state(getInputId(id));
|
|
@@ -58,7 +59,7 @@
|
|
|
58
59
|
{/if}
|
|
59
60
|
|
|
60
61
|
{#if popup}
|
|
61
|
-
<Dropdown oncancel={() => (popup = false)}>
|
|
62
|
+
<Dropdown oncancel={() => (popup = false)} {...dropdown}>
|
|
62
63
|
<ColorBoard {value} button="true" onchange={selectColor} />
|
|
63
64
|
</Dropdown>
|
|
64
65
|
{/if}
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
disabled = false,
|
|
23
23
|
error = false,
|
|
24
24
|
onchange,
|
|
25
|
+
dropdown = {},
|
|
25
26
|
} = $props();
|
|
26
|
-
|
|
27
27
|
const inputId = $state(getInputId(id));
|
|
28
28
|
|
|
29
29
|
let popup = $state(false);
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
{/if}
|
|
76
76
|
|
|
77
77
|
{#if popup}
|
|
78
|
-
<Dropdown oncancel={() => (popup = false)}>
|
|
78
|
+
<Dropdown oncancel={() => (popup = false)} {...dropdown}>
|
|
79
79
|
<div class="wx-colors">
|
|
80
80
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
81
81
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
clear = false,
|
|
17
17
|
children: kids,
|
|
18
18
|
onchange,
|
|
19
|
+
dropdown = {},
|
|
19
20
|
} = $props();
|
|
20
21
|
|
|
21
22
|
const inputId = $state(getInputId(id));
|
|
@@ -135,7 +136,12 @@
|
|
|
135
136
|
{:else}<i class="wx-icon wxi-angle-down"></i>{/if}
|
|
136
137
|
|
|
137
138
|
{#if !disabled}
|
|
138
|
-
<List
|
|
139
|
+
<List
|
|
140
|
+
items={filteredOptions}
|
|
141
|
+
onready={ready}
|
|
142
|
+
onselect={selectByEvent}
|
|
143
|
+
{...dropdown}
|
|
144
|
+
>
|
|
139
145
|
{#snippet children({ option })}
|
|
140
146
|
{#if kids}{@render kids({ option })}{:else}{option[
|
|
141
147
|
textField
|
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
import Dropdown from "./Dropdown.svelte";
|
|
7
7
|
import Calendar from "./Calendar.svelte";
|
|
8
8
|
import { defaultLocale } from "./helpers/locale";
|
|
9
|
+
import { toDateDropdown } from "./helpers/dropdown";
|
|
9
10
|
|
|
10
11
|
let {
|
|
11
12
|
value = $bindable(),
|
|
12
13
|
id,
|
|
13
14
|
disabled = false,
|
|
14
15
|
error = false,
|
|
15
|
-
width = "unset",
|
|
16
|
-
align = "start",
|
|
17
16
|
placeholder = "",
|
|
18
17
|
format = "",
|
|
19
18
|
buttons = ["clear", "today"],
|
|
@@ -22,6 +21,7 @@
|
|
|
22
21
|
editable = false,
|
|
23
22
|
clear = false,
|
|
24
23
|
onchange,
|
|
24
|
+
dropdown = {},
|
|
25
25
|
} = $props();
|
|
26
26
|
|
|
27
27
|
const { calendar: calendarLocale, formats } = (
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
/>
|
|
99
99
|
|
|
100
100
|
{#if popup && !disabled}
|
|
101
|
-
<Dropdown {oncancel} {
|
|
101
|
+
<Dropdown {oncancel} {...toDateDropdown(dropdown)}>
|
|
102
102
|
<Calendar {buttons} {value} onchange={e => doChange(e.value)} />
|
|
103
103
|
</Dropdown>
|
|
104
104
|
{/if}
|
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
import Dropdown from "./Dropdown.svelte";
|
|
7
7
|
import RangeCalendar from "./RangeCalendar.svelte";
|
|
8
8
|
import { defaultLocale } from "./helpers/locale";
|
|
9
|
+
import { toDateDropdown } from "./helpers/dropdown";
|
|
9
10
|
|
|
10
11
|
let {
|
|
11
12
|
value = $bindable(),
|
|
12
13
|
id,
|
|
13
14
|
disabled = false,
|
|
14
15
|
error = false,
|
|
15
|
-
width = "unset",
|
|
16
|
-
align = "start",
|
|
17
16
|
placeholder = "",
|
|
18
17
|
css = "",
|
|
19
18
|
title = "",
|
|
@@ -23,6 +22,7 @@
|
|
|
23
22
|
editable = false,
|
|
24
23
|
clear = false,
|
|
25
24
|
onchange,
|
|
25
|
+
dropdown,
|
|
26
26
|
} = $props();
|
|
27
27
|
|
|
28
28
|
const { calendar: calendarLocale, formats } = (
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
/>
|
|
114
114
|
|
|
115
115
|
{#if popup && !disabled}
|
|
116
|
-
<Dropdown {oncancel} {
|
|
116
|
+
<Dropdown {oncancel} {...toDateDropdown(dropdown)}>
|
|
117
117
|
<RangeCalendar
|
|
118
118
|
{oncancel}
|
|
119
119
|
{buttons}
|
|
@@ -1,126 +1,44 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import {
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { Portal } from "../index.js";
|
|
4
|
+
import Popup from "./Popup.svelte";
|
|
5
|
+
import InlineDropdown from "./helpers/InlineDropdown.svelte";
|
|
3
6
|
|
|
4
7
|
let {
|
|
5
8
|
position = "bottom",
|
|
6
9
|
align = "start",
|
|
7
10
|
autoFit = true,
|
|
11
|
+
inline = false,
|
|
8
12
|
oncancel,
|
|
9
13
|
width = "100%",
|
|
10
|
-
|
|
14
|
+
...props
|
|
11
15
|
} = $props();
|
|
12
16
|
|
|
13
|
-
let
|
|
14
|
-
$
|
|
15
|
-
if (autoFit) {
|
|
16
|
-
const nodeCoords = node.getBoundingClientRect();
|
|
17
|
-
const bodyCoords = env.getTopNode(node).getBoundingClientRect();
|
|
17
|
+
let target = $state();
|
|
18
|
+
let node = $state();
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
align = "end";
|
|
21
|
-
}
|
|
20
|
+
const at = $derived(`${position}-${align}`);
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
22
|
+
onMount(() => {
|
|
23
|
+
// get the parent element before
|
|
24
|
+
// the popup is moved to the portal
|
|
25
|
+
target = node.parentNode;
|
|
28
26
|
});
|
|
29
|
-
|
|
30
|
-
function down(e) {
|
|
31
|
-
oncancel && oncancel(e);
|
|
32
|
-
}
|
|
33
27
|
</script>
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<style>
|
|
45
|
-
.wx-dropdown {
|
|
46
|
-
position: absolute;
|
|
47
|
-
z-index: 5;
|
|
48
|
-
background: var(--wx-popup-background);
|
|
49
|
-
box-shadow: var(--wx-popup-shadow);
|
|
50
|
-
border: var(--wx-popup-border);
|
|
51
|
-
border-radius: var(--wx-popup-border-radius);
|
|
52
|
-
overflow: hidden;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.wx-top-center {
|
|
56
|
-
top: 0;
|
|
57
|
-
left: 50%;
|
|
58
|
-
transform: translate(-50%, -100%) translateY(-2px);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.wx-top-start {
|
|
62
|
-
top: 0;
|
|
63
|
-
left: 0;
|
|
64
|
-
transform: translateY(-100%) translateY(-2px);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.wx-top-end {
|
|
68
|
-
top: 0;
|
|
69
|
-
right: 0;
|
|
70
|
-
transform: translateY(-100%) translateY(-2px);
|
|
71
|
-
}
|
|
29
|
+
{#if inline}
|
|
30
|
+
<InlineDropdown {oncancel} {position} {align} {autoFit} {width} {...props}
|
|
31
|
+
></InlineDropdown>
|
|
32
|
+
{:else}
|
|
33
|
+
<Portal>
|
|
34
|
+
<Popup parent={target} {at} {oncancel} {width} {...props}></Popup>
|
|
35
|
+
</Portal>
|
|
36
|
+
{/if}
|
|
72
37
|
|
|
73
|
-
|
|
74
|
-
bottom: 0;
|
|
75
|
-
left: 50%;
|
|
76
|
-
transform: translate(-50%, 100%) translateY(2px);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.wx-bottom-start {
|
|
80
|
-
bottom: 0;
|
|
81
|
-
left: 0;
|
|
82
|
-
transform: translateY(100%) translateY(2px);
|
|
83
|
-
}
|
|
38
|
+
<span bind:this={node} class="wx-portal-node"></span>
|
|
84
39
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
transform: translateY(100%) translateY(2px);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.wx-left-center {
|
|
92
|
-
bottom: 50%;
|
|
93
|
-
left: 0;
|
|
94
|
-
transform: translate(-100%, 50%) translateX(-2px);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.wx-left-start {
|
|
98
|
-
top: 0;
|
|
99
|
-
left: 0;
|
|
100
|
-
transform: translateX(-100%) translateX(-2px);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.wx-left-end {
|
|
104
|
-
bottom: 0;
|
|
105
|
-
left: 0;
|
|
106
|
-
transform: translateX(-100%) translateX(-2px);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.wx-right-center {
|
|
110
|
-
bottom: 50%;
|
|
111
|
-
right: 0;
|
|
112
|
-
transform: translate(100%, 50%) translateX(2px);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.wx-right-start {
|
|
116
|
-
top: 0;
|
|
117
|
-
right: 0;
|
|
118
|
-
transform: translateX(100%) translateX(2px);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.wx-right-end {
|
|
122
|
-
bottom: 0;
|
|
123
|
-
right: 0;
|
|
124
|
-
transform: translateX(100%) translateX(2px);
|
|
40
|
+
<style>
|
|
41
|
+
.wx-portal-node {
|
|
42
|
+
display: none;
|
|
125
43
|
}
|
|
126
44
|
</style>
|
|
@@ -8,18 +8,12 @@
|
|
|
8
8
|
error = false,
|
|
9
9
|
type = "",
|
|
10
10
|
required = false,
|
|
11
|
+
id,
|
|
11
12
|
children,
|
|
12
13
|
} = $props();
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const registerInput = () => {
|
|
17
|
-
const id = uid();
|
|
18
|
-
if (!firstInputId) firstInputId = id;
|
|
19
|
-
return id;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
setContext("wx-input-id", registerInput);
|
|
15
|
+
const inputId = id === undefined ? uid() : id;
|
|
16
|
+
setContext("wx-input-id", inputId);
|
|
23
17
|
</script>
|
|
24
18
|
|
|
25
19
|
<div
|
|
@@ -29,8 +23,8 @@
|
|
|
29
23
|
style={width ? `width: ${width}` : ""}
|
|
30
24
|
>
|
|
31
25
|
{#if label}
|
|
32
|
-
{#if
|
|
33
|
-
<label class="wx-label" for={
|
|
26
|
+
{#if inputId}
|
|
27
|
+
<label class="wx-label" for={inputId}>{label}</label>
|
|
34
28
|
{:else}
|
|
35
29
|
<div class="wx-label">{label}</div>
|
|
36
30
|
{/if}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import List from "./helpers/SuggestDropdown.svelte";
|
|
3
|
-
import Checkbox from "./Checkbox.svelte";
|
|
4
3
|
import { getInputId } from "./helpers/getInputId.js";
|
|
5
4
|
|
|
6
5
|
let {
|
|
@@ -16,6 +15,7 @@
|
|
|
16
15
|
checkboxes = false,
|
|
17
16
|
onchange,
|
|
18
17
|
children,
|
|
18
|
+
dropdown = {},
|
|
19
19
|
} = $props();
|
|
20
20
|
|
|
21
21
|
const inputId = $state(getInputId(id));
|
|
@@ -48,22 +48,9 @@
|
|
|
48
48
|
}
|
|
49
49
|
function onselect(ev) {
|
|
50
50
|
const { id } = ev;
|
|
51
|
-
|
|
52
51
|
if (id) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (value.includes(id)) {
|
|
56
|
-
next = value.filter(i => i !== id);
|
|
57
|
-
} else {
|
|
58
|
-
next = [...value, id];
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
next = [id];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
value = next;
|
|
65
|
-
onchange && onchange({ value });
|
|
66
|
-
|
|
52
|
+
value = id;
|
|
53
|
+
onchange && onchange({ value: id });
|
|
67
54
|
inputElement.focus();
|
|
68
55
|
}
|
|
69
56
|
}
|
|
@@ -134,15 +121,16 @@
|
|
|
134
121
|
</div>
|
|
135
122
|
|
|
136
123
|
{#if !disabled}
|
|
137
|
-
<List
|
|
124
|
+
<List
|
|
125
|
+
items={filterOptions}
|
|
126
|
+
multiselect={true}
|
|
127
|
+
{onready}
|
|
128
|
+
{onselect}
|
|
129
|
+
{checkboxes}
|
|
130
|
+
{value}
|
|
131
|
+
{...dropdown}
|
|
132
|
+
>
|
|
138
133
|
{#snippet children({ option })}
|
|
139
|
-
{#if checkboxes}
|
|
140
|
-
<Checkbox
|
|
141
|
-
style="margin-right: 8px; pointer-events: none;"
|
|
142
|
-
name={option.id}
|
|
143
|
-
value={value && value.includes(option.id)}
|
|
144
|
-
/>
|
|
145
|
-
{/if}
|
|
146
134
|
{#if children}{@render children({
|
|
147
135
|
option,
|
|
148
136
|
})}{:else}{option[textField]}{/if}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { clickOutside, calculatePosition } from "@svar-ui/lib-dom";
|
|
2
|
+
import { clickOutside, calculatePosition, getAbsParent } from "@svar-ui/lib-dom";
|
|
3
3
|
import { onMount } from "svelte";
|
|
4
4
|
|
|
5
5
|
let {
|
|
@@ -7,32 +7,60 @@
|
|
|
7
7
|
top = 0,
|
|
8
8
|
at = "bottom",
|
|
9
9
|
parent = null,
|
|
10
|
+
width = "auto",
|
|
11
|
+
css = "",
|
|
10
12
|
oncancel,
|
|
11
13
|
children,
|
|
14
|
+
trackScroll = false,
|
|
12
15
|
} = $props();
|
|
13
16
|
|
|
14
|
-
let self = null;
|
|
17
|
+
let self = $state(null);
|
|
15
18
|
let x = $state(0);
|
|
16
19
|
let y = $state(0);
|
|
17
|
-
let
|
|
20
|
+
let w = $state("auto");
|
|
21
|
+
let portal;
|
|
22
|
+
|
|
23
|
+
function getWidth(calcWidth) {
|
|
24
|
+
if (parent && (width + "").indexOf("%") > -1) {
|
|
25
|
+
return width.replace(/(\d+)%/, (match, value) => {
|
|
26
|
+
value = (value * parent.offsetWidth) / 100 + "px";
|
|
27
|
+
return width.replace(match, value);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return width && width !== "auto" ? width : calcWidth;
|
|
31
|
+
}
|
|
18
32
|
|
|
19
33
|
function updatePosition() {
|
|
20
34
|
if (!self) return;
|
|
21
|
-
|
|
22
35
|
const result = calculatePosition(self, parent, at, left, top);
|
|
23
36
|
if (result) {
|
|
24
37
|
x = result.x;
|
|
25
38
|
y = result.y;
|
|
26
|
-
|
|
39
|
+
w = getWidth(result.width);
|
|
27
40
|
}
|
|
28
41
|
}
|
|
29
42
|
|
|
43
|
+
function onScroll(e) {
|
|
44
|
+
if (oncancel && e.target !== portal && !self.contains(e.target))
|
|
45
|
+
oncancel(e);
|
|
46
|
+
}
|
|
47
|
+
|
|
30
48
|
onMount(() => {
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
requestAnimationFrame(() => {
|
|
50
|
+
updatePosition();
|
|
51
|
+
if (trackScroll) {
|
|
52
|
+
portal = getAbsParent(self);
|
|
53
|
+
if (portal) portal.addEventListener("scroll", onScroll, true);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return () => {
|
|
57
|
+
if (trackScroll && portal)
|
|
58
|
+
portal.removeEventListener("scroll", onScroll, true);
|
|
59
|
+
};
|
|
33
60
|
});
|
|
61
|
+
|
|
34
62
|
$effect(() => {
|
|
35
|
-
updatePosition(
|
|
63
|
+
updatePosition();
|
|
36
64
|
});
|
|
37
65
|
|
|
38
66
|
function down(e) {
|
|
@@ -43,8 +71,8 @@
|
|
|
43
71
|
<div
|
|
44
72
|
use:clickOutside={down}
|
|
45
73
|
bind:this={self}
|
|
46
|
-
class="wx-popup"
|
|
47
|
-
style="position:absolute;top:{y}px;left:{x}px;width:{
|
|
74
|
+
class="wx-popup {css}"
|
|
75
|
+
style="position:absolute;top:{y}px;left:{x}px;width:{w};"
|
|
48
76
|
>
|
|
49
77
|
{@render children?.()}
|
|
50
78
|
</div>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
clear = false,
|
|
14
14
|
children: kids,
|
|
15
15
|
onchange,
|
|
16
|
+
dropdown = {},
|
|
16
17
|
} = $props();
|
|
17
18
|
|
|
18
19
|
let navigate;
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
{:else}<i class="wx-icon wxi-angle-down"></i>{/if}
|
|
74
75
|
|
|
75
76
|
{#if !disabled}
|
|
76
|
-
<List items={options} onready={ready} onselect={select}>
|
|
77
|
+
<List items={options} onready={ready} onselect={select} {...dropdown}>
|
|
77
78
|
{#snippet children({ option })}
|
|
78
79
|
{#if kids}{@render kids(option)}{:else}{option[textField]}{/if}
|
|
79
80
|
{/snippet}
|