@x33025/sveltely 0.0.27 → 0.0.29
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.
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
let inputValue = $state('');
|
|
21
21
|
let showInput = $state(false);
|
|
22
22
|
let inputEl = $state<HTMLInputElement | null>(null);
|
|
23
|
+
let editingTag = $state<string | null>(null);
|
|
24
|
+
let editingValue = $state('');
|
|
25
|
+
let editingEl = $state<HTMLInputElement | null>(null);
|
|
23
26
|
const selectionEnabled = $derived(selection !== undefined);
|
|
24
27
|
|
|
25
28
|
const addTag = (rawValue: string) => {
|
|
@@ -64,6 +67,48 @@
|
|
|
64
67
|
inputEl?.focus();
|
|
65
68
|
};
|
|
66
69
|
|
|
70
|
+
const startEditing = async (tag: string) => {
|
|
71
|
+
editingTag = tag;
|
|
72
|
+
editingValue = tag;
|
|
73
|
+
await tick();
|
|
74
|
+
editingEl?.focus();
|
|
75
|
+
editingEl?.select();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const commitEdit = () => {
|
|
79
|
+
if (!editingTag) return;
|
|
80
|
+
|
|
81
|
+
const previous = editingTag;
|
|
82
|
+
const next = editingValue.trim();
|
|
83
|
+
editingTag = null;
|
|
84
|
+
|
|
85
|
+
if (!next || next === previous) return;
|
|
86
|
+
if (tags.includes(next)) return;
|
|
87
|
+
|
|
88
|
+
tags = tags.map((tag) => (tag === previous ? next : tag));
|
|
89
|
+
if (selection?.includes(previous)) {
|
|
90
|
+
selection = selection.map((tag) => (tag === previous ? next : tag));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const cancelEdit = () => {
|
|
95
|
+
editingTag = null;
|
|
96
|
+
editingValue = '';
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const onEditKeydown = (event: KeyboardEvent) => {
|
|
100
|
+
if (event.key === 'Enter') {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
commitEdit();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (event.key === 'Escape') {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
cancelEdit();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
67
112
|
const onBlur = () => {
|
|
68
113
|
if (!inputValue.trim()) {
|
|
69
114
|
showInput = false;
|
|
@@ -82,18 +127,31 @@
|
|
|
82
127
|
<div class="w-full max-w-lg">
|
|
83
128
|
<div class="tag-row flex flex-wrap items-center">
|
|
84
129
|
{#each tags as tag (tag)}
|
|
85
|
-
{#if
|
|
130
|
+
{#if editingTag === tag}
|
|
131
|
+
<input
|
|
132
|
+
bind:this={editingEl}
|
|
133
|
+
bind:value={editingValue}
|
|
134
|
+
class="tag-surface tag-input-field min-w-20 outline-none"
|
|
135
|
+
onblur={commitEdit}
|
|
136
|
+
onkeydown={onEditKeydown}
|
|
137
|
+
/>
|
|
138
|
+
{:else if selectionEnabled}
|
|
86
139
|
<button
|
|
87
140
|
type="button"
|
|
88
141
|
class="tag-surface inline-flex items-center gap-2"
|
|
89
142
|
class:tag-selected={selection?.includes(tag)}
|
|
90
143
|
onclick={() => toggleSelected(tag)}
|
|
144
|
+
ondblclick={() => startEditing(tag)}
|
|
91
145
|
aria-pressed={selection?.includes(tag)}
|
|
92
146
|
>
|
|
93
147
|
{tag}
|
|
94
148
|
</button>
|
|
95
149
|
{:else}
|
|
96
|
-
<
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
class="tag-surface inline-flex items-center gap-2"
|
|
153
|
+
ondblclick={() => startEditing(tag)}>{tag}</button
|
|
154
|
+
>
|
|
97
155
|
{/if}
|
|
98
156
|
{/each}
|
|
99
157
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import { portalContent } from '../../actions/portal';
|
|
4
5
|
|
|
@@ -23,9 +24,11 @@
|
|
|
23
24
|
let isOpen = $state(false);
|
|
24
25
|
let triggerEl = $state<HTMLElement | null>(null);
|
|
25
26
|
let menuEl = $state<HTMLElement | null>(null);
|
|
27
|
+
let contentEl = $state<HTMLElement | null>(null);
|
|
26
28
|
let menuCoords = $state({ top: 0, left: 0 });
|
|
27
29
|
let menuTransform = $state('none');
|
|
28
30
|
let resolvedAnchor = $state<'top' | 'bottom' | 'leading' | 'trailing'>('bottom');
|
|
31
|
+
let computedMenuRadius = $state<string | null>(null);
|
|
29
32
|
|
|
30
33
|
type Point = { x: number; y: number };
|
|
31
34
|
|
|
@@ -87,7 +90,46 @@
|
|
|
87
90
|
];
|
|
88
91
|
};
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
const parsePx = (value: string) => {
|
|
94
|
+
const first = value.split(' ')[0];
|
|
95
|
+
const parsed = Number.parseFloat(first);
|
|
96
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function updateComputedMenuRadius() {
|
|
100
|
+
if (!menuEl || !contentEl) {
|
|
101
|
+
computedMenuRadius = null;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const itemEl = contentEl.querySelector<HTMLElement>('.dropdown-item');
|
|
106
|
+
if (!itemEl) {
|
|
107
|
+
computedMenuRadius = null;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const itemRect = itemEl.getBoundingClientRect();
|
|
112
|
+
const itemStyle = getComputedStyle(itemEl);
|
|
113
|
+
const itemRadius = parsePx(itemStyle.borderTopLeftRadius);
|
|
114
|
+
const effectiveItemRadius = Math.min(itemRadius, itemRect.height / 2, itemRect.width / 2);
|
|
115
|
+
|
|
116
|
+
const contentStyle = getComputedStyle(contentEl);
|
|
117
|
+
const contentPadding = Math.min(
|
|
118
|
+
parsePx(contentStyle.paddingTop),
|
|
119
|
+
parsePx(contentStyle.paddingLeft)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const menuRect = menuEl.getBoundingClientRect();
|
|
123
|
+
const outerRadius = Math.min(
|
|
124
|
+
effectiveItemRadius + contentPadding,
|
|
125
|
+
menuRect.height / 2,
|
|
126
|
+
menuRect.width / 2
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
computedMenuRadius = `${outerRadius}px`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function open() {
|
|
91
133
|
if (triggerEl) {
|
|
92
134
|
const rect = triggerEl.getBoundingClientRect();
|
|
93
135
|
const spaceAbove = rect.top;
|
|
@@ -125,10 +167,13 @@
|
|
|
125
167
|
}
|
|
126
168
|
}
|
|
127
169
|
isOpen = true;
|
|
170
|
+
await tick();
|
|
171
|
+
updateComputedMenuRadius();
|
|
128
172
|
}
|
|
129
173
|
|
|
130
174
|
function close() {
|
|
131
175
|
isOpen = false;
|
|
176
|
+
computedMenuRadius = null;
|
|
132
177
|
}
|
|
133
178
|
|
|
134
179
|
function toggle() {
|
|
@@ -170,6 +215,11 @@
|
|
|
170
215
|
function handleSelect() {
|
|
171
216
|
if (closeOnSelect) close();
|
|
172
217
|
}
|
|
218
|
+
|
|
219
|
+
$effect(() => {
|
|
220
|
+
if (!isOpen) return;
|
|
221
|
+
updateComputedMenuRadius();
|
|
222
|
+
});
|
|
173
223
|
</script>
|
|
174
224
|
|
|
175
225
|
<svelte:window
|
|
@@ -197,7 +247,8 @@
|
|
|
197
247
|
<div
|
|
198
248
|
use:portalContent
|
|
199
249
|
class="dropdown-menu fixed z-50 border border-gray-200 focus:outline-none"
|
|
200
|
-
style="top: {menuCoords.top}px; left: {menuCoords.left}px; transform: {menuTransform}; border-radius:
|
|
250
|
+
style="top: {menuCoords.top}px; left: {menuCoords.left}px; transform: {menuTransform}; border-radius: {computedMenuRadius ??
|
|
251
|
+
'var(--dropdown-border-radius)'}; background: var(--dropdown-background); box-shadow: var(--dropdown-shadow);"
|
|
201
252
|
role="menu"
|
|
202
253
|
aria-orientation="vertical"
|
|
203
254
|
tabindex="-1"
|
|
@@ -208,6 +259,7 @@
|
|
|
208
259
|
style="padding: var(--dropdown-content-padding);"
|
|
209
260
|
role="none"
|
|
210
261
|
onclick={handleSelect}
|
|
262
|
+
bind:this={contentEl}
|
|
211
263
|
>
|
|
212
264
|
{@render children()}
|
|
213
265
|
</div>
|
package/dist/style/index.css
CHANGED
|
@@ -68,10 +68,8 @@
|
|
|
68
68
|
--sheet-shadow: var(--shadow-md);
|
|
69
69
|
|
|
70
70
|
--dropdown-content-padding: calc(var(--spacing));
|
|
71
|
-
--dropdown-item-border-radius: var(--radius-
|
|
72
|
-
--dropdown-border-radius:
|
|
73
|
-
var(--dropdown-item-border-radius) + var(--dropdown-content-padding)
|
|
74
|
-
);
|
|
71
|
+
--dropdown-item-border-radius: var(--radius-md);
|
|
72
|
+
--dropdown-border-radius: var(--radius-md);
|
|
75
73
|
--dropdown-background: var(--color-white);
|
|
76
74
|
--dropdown-shadow: var(--shadow-md);
|
|
77
75
|
--dropdown-item-highlight: var(--color-zinc-100);
|
package/dist/style.css
CHANGED
|
@@ -272,6 +272,9 @@
|
|
|
272
272
|
.max-w-lg {
|
|
273
273
|
max-width: var(--container-lg);
|
|
274
274
|
}
|
|
275
|
+
.min-w-20 {
|
|
276
|
+
min-width: calc(var(--spacing) * 20);
|
|
277
|
+
}
|
|
275
278
|
.min-w-36 {
|
|
276
279
|
min-width: calc(var(--spacing) * 36);
|
|
277
280
|
}
|
|
@@ -604,10 +607,8 @@
|
|
|
604
607
|
--sheet-blur: var(--blur-sm);
|
|
605
608
|
--sheet-shadow: var(--shadow-md);
|
|
606
609
|
--dropdown-content-padding: calc(var(--spacing));
|
|
607
|
-
--dropdown-item-border-radius: var(--radius-
|
|
608
|
-
--dropdown-border-radius:
|
|
609
|
-
var(--dropdown-item-border-radius) + var(--dropdown-content-padding)
|
|
610
|
-
);
|
|
610
|
+
--dropdown-item-border-radius: var(--radius-md);
|
|
611
|
+
--dropdown-border-radius: var(--radius-md);
|
|
611
612
|
--dropdown-background: var(--color-white);
|
|
612
613
|
--dropdown-shadow: var(--shadow-md);
|
|
613
614
|
--dropdown-item-highlight: var(--color-zinc-100);
|