@pzerelles/headlessui-svelte 2.0.0-next.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/README.md +58 -0
- package/dist/actions/activePress.svelte.d.ts +8 -0
- package/dist/actions/activePress.svelte.js +84 -0
- package/dist/actions/focusRing.svelte.d.ts +9 -0
- package/dist/actions/focusRing.svelte.js +34 -0
- package/dist/checkbox/Checkbox.svelte +114 -0
- package/dist/checkbox/Checkbox.svelte.d.ts +48 -0
- package/dist/checkbox/index.d.ts +1 -0
- package/dist/checkbox/index.js +1 -0
- package/dist/description/Description.svelte +53 -0
- package/dist/description/Description.svelte.d.ts +39 -0
- package/dist/description/index.d.ts +1 -0
- package/dist/description/index.js +1 -0
- package/dist/field/Field.svelte +33 -0
- package/dist/field/Field.svelte.d.ts +33 -0
- package/dist/field/index.d.ts +1 -0
- package/dist/field/index.js +1 -0
- package/dist/fieldset/Fieldset.svelte +32 -0
- package/dist/fieldset/Fieldset.svelte.d.ts +33 -0
- package/dist/fieldset/index.d.ts +1 -0
- package/dist/fieldset/index.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/internal/FormFields.svelte +44 -0
- package/dist/internal/FormFields.svelte.d.ts +22 -0
- package/dist/internal/FormResolver.svelte +25 -0
- package/dist/internal/FormResolver.svelte.d.ts +19 -0
- package/dist/internal/Hidden.svelte +36 -0
- package/dist/internal/Hidden.svelte.d.ts +26 -0
- package/dist/internal/HoistFormFields.svelte +11 -0
- package/dist/internal/HoistFormFields.svelte.d.ts +18 -0
- package/dist/internal/Portal.svelte +17 -0
- package/dist/internal/Portal.svelte.d.ts +19 -0
- package/dist/label/Label.svelte +99 -0
- package/dist/label/Label.svelte.d.ts +41 -0
- package/dist/label/index.d.ts +1 -0
- package/dist/label/index.js +1 -0
- package/dist/legend/Legend.svelte +7 -0
- package/dist/legend/Legend.svelte.d.ts +18 -0
- package/dist/legend/index.d.ts +1 -0
- package/dist/legend/index.js +1 -0
- package/dist/utils/disabled.d.ts +3 -0
- package/dist/utils/disabled.js +2 -0
- package/dist/utils/disposables.d.ts +24 -0
- package/dist/utils/disposables.js +78 -0
- package/dist/utils/dom.d.ts +3 -0
- package/dist/utils/dom.js +10 -0
- package/dist/utils/focusVisible.svelte.d.ts +50 -0
- package/dist/utils/focusVisible.svelte.js +278 -0
- package/dist/utils/form.d.ts +4 -0
- package/dist/utils/form.js +57 -0
- package/dist/utils/id.d.ts +4 -0
- package/dist/utils/id.js +6 -0
- package/dist/utils/isVirtualEvent.d.ts +2 -0
- package/dist/utils/isVirtualEvent.js +48 -0
- package/dist/utils/microTask.d.ts +1 -0
- package/dist/utils/microTask.js +13 -0
- package/dist/utils/object.d.ts +1 -0
- package/dist/utils/object.js +8 -0
- package/dist/utils/platform.d.ts +4 -0
- package/dist/utils/platform.js +29 -0
- package/dist/utils/state.d.ts +1 -0
- package/dist/utils/state.js +20 -0
- package/package.json +69 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
// Portions of the code in this file are based on code from react.
|
|
13
|
+
// Original licensing for the following can be found in the
|
|
14
|
+
// NOTICE file in the root directory of this source tree.
|
|
15
|
+
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
|
|
16
|
+
import { getOwnerDocument, getOwnerWindow } from "./dom.js";
|
|
17
|
+
import { isVirtualClick } from "./isVirtualEvent.js";
|
|
18
|
+
import { isMac } from "./platform.js";
|
|
19
|
+
let currentModality = null;
|
|
20
|
+
let changeHandlers = new Set();
|
|
21
|
+
export let hasSetupGlobalListeners = new Map(); // We use a map here to support setting event listeners across multiple document objects.
|
|
22
|
+
let hasEventBeforeFocus = false;
|
|
23
|
+
let hasBlurredWindowRecently = false;
|
|
24
|
+
// Only Tab or Esc keys will make focus visible on text input elements
|
|
25
|
+
const FOCUS_VISIBLE_INPUT_KEYS = {
|
|
26
|
+
Tab: true,
|
|
27
|
+
Escape: true,
|
|
28
|
+
};
|
|
29
|
+
function triggerChangeHandlers(modality, e) {
|
|
30
|
+
for (let handler of changeHandlers) {
|
|
31
|
+
handler(modality, e);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Helper function to determine if a KeyboardEvent is unmodified and could make keyboard focus styles visible.
|
|
36
|
+
*/
|
|
37
|
+
function isValidKey(e) {
|
|
38
|
+
// Control and Shift keys trigger when navigating back to the tab with keyboard.
|
|
39
|
+
return !(e.metaKey ||
|
|
40
|
+
(!isMac() && e.altKey) ||
|
|
41
|
+
e.ctrlKey ||
|
|
42
|
+
e.key === "Control" ||
|
|
43
|
+
e.key === "Shift" ||
|
|
44
|
+
e.key === "Meta");
|
|
45
|
+
}
|
|
46
|
+
function handleKeyboardEvent(e) {
|
|
47
|
+
hasEventBeforeFocus = true;
|
|
48
|
+
if (isValidKey(e)) {
|
|
49
|
+
currentModality = "keyboard";
|
|
50
|
+
triggerChangeHandlers("keyboard", e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function handlePointerEvent(e) {
|
|
54
|
+
currentModality = "pointer";
|
|
55
|
+
if (e.type === "mousedown" || e.type === "pointerdown") {
|
|
56
|
+
hasEventBeforeFocus = true;
|
|
57
|
+
triggerChangeHandlers("pointer", e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function handleClickEvent(e) {
|
|
61
|
+
if (isVirtualClick(e)) {
|
|
62
|
+
hasEventBeforeFocus = true;
|
|
63
|
+
currentModality = "virtual";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function handleFocusEvent(e) {
|
|
67
|
+
// Firefox fires two extra focus events when the user first clicks into an iframe:
|
|
68
|
+
// first on the window, then on the document. We ignore these events so they don't
|
|
69
|
+
// cause keyboard focus rings to appear.
|
|
70
|
+
if (e.target === window || e.target === document) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// If a focus event occurs without a preceding keyboard or pointer event, switch to virtual modality.
|
|
74
|
+
// This occurs, for example, when navigating a form with the next/previous buttons on iOS.
|
|
75
|
+
if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {
|
|
76
|
+
currentModality = "virtual";
|
|
77
|
+
triggerChangeHandlers("virtual", e);
|
|
78
|
+
}
|
|
79
|
+
hasEventBeforeFocus = false;
|
|
80
|
+
hasBlurredWindowRecently = false;
|
|
81
|
+
}
|
|
82
|
+
function handleWindowBlur() {
|
|
83
|
+
// When the window is blurred, reset state. This is necessary when tabbing out of the window,
|
|
84
|
+
// for example, since a subsequent focus event won't be fired.
|
|
85
|
+
hasEventBeforeFocus = false;
|
|
86
|
+
hasBlurredWindowRecently = true;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Setup global event listeners to control when keyboard focus style should be visible.
|
|
90
|
+
*/
|
|
91
|
+
function setupGlobalFocusEvents(element) {
|
|
92
|
+
if (typeof window === "undefined" || hasSetupGlobalListeners.get(getOwnerWindow(element))) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const windowObject = getOwnerWindow(element);
|
|
96
|
+
const documentObject = getOwnerDocument(element);
|
|
97
|
+
// Programmatic focus() calls shouldn't affect the current input modality.
|
|
98
|
+
// However, we need to detect other cases when a focus event occurs without
|
|
99
|
+
// a preceding user event (e.g. screen reader focus). Overriding the focus
|
|
100
|
+
// method on HTMLElement.prototype is a bit hacky, but works.
|
|
101
|
+
let focus = windowObject.HTMLElement.prototype.focus;
|
|
102
|
+
windowObject.HTMLElement.prototype.focus = function () {
|
|
103
|
+
hasEventBeforeFocus = true;
|
|
104
|
+
focus.apply(this, arguments);
|
|
105
|
+
};
|
|
106
|
+
documentObject.addEventListener("keydown", handleKeyboardEvent, true);
|
|
107
|
+
documentObject.addEventListener("keyup", handleKeyboardEvent, true);
|
|
108
|
+
documentObject.addEventListener("click", handleClickEvent, true);
|
|
109
|
+
// Register focus events on the window so they are sure to happen
|
|
110
|
+
// before React's event listeners (registered on the document).
|
|
111
|
+
windowObject.addEventListener("focus", handleFocusEvent, true);
|
|
112
|
+
windowObject.addEventListener("blur", handleWindowBlur, false);
|
|
113
|
+
if (typeof PointerEvent !== "undefined") {
|
|
114
|
+
documentObject.addEventListener("pointerdown", handlePointerEvent, true);
|
|
115
|
+
documentObject.addEventListener("pointermove", handlePointerEvent, true);
|
|
116
|
+
documentObject.addEventListener("pointerup", handlePointerEvent, true);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
documentObject.addEventListener("mousedown", handlePointerEvent, true);
|
|
120
|
+
documentObject.addEventListener("mousemove", handlePointerEvent, true);
|
|
121
|
+
documentObject.addEventListener("mouseup", handlePointerEvent, true);
|
|
122
|
+
}
|
|
123
|
+
// Add unmount handler
|
|
124
|
+
windowObject.addEventListener("beforeunload", () => {
|
|
125
|
+
tearDownWindowFocusTracking(element);
|
|
126
|
+
}, { once: true });
|
|
127
|
+
hasSetupGlobalListeners.set(windowObject, { focus });
|
|
128
|
+
}
|
|
129
|
+
const tearDownWindowFocusTracking = (element, loadListener) => {
|
|
130
|
+
const windowObject = getOwnerWindow(element);
|
|
131
|
+
const documentObject = getOwnerDocument(element);
|
|
132
|
+
if (loadListener) {
|
|
133
|
+
documentObject.removeEventListener("DOMContentLoaded", loadListener);
|
|
134
|
+
}
|
|
135
|
+
if (!hasSetupGlobalListeners.has(windowObject)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
windowObject.HTMLElement.prototype.focus = hasSetupGlobalListeners.get(windowObject).focus;
|
|
139
|
+
documentObject.removeEventListener("keydown", handleKeyboardEvent, true);
|
|
140
|
+
documentObject.removeEventListener("keyup", handleKeyboardEvent, true);
|
|
141
|
+
documentObject.removeEventListener("click", handleClickEvent, true);
|
|
142
|
+
windowObject.removeEventListener("focus", handleFocusEvent, true);
|
|
143
|
+
windowObject.removeEventListener("blur", handleWindowBlur, false);
|
|
144
|
+
if (typeof PointerEvent !== "undefined") {
|
|
145
|
+
documentObject.removeEventListener("pointerdown", handlePointerEvent, true);
|
|
146
|
+
documentObject.removeEventListener("pointermove", handlePointerEvent, true);
|
|
147
|
+
documentObject.removeEventListener("pointerup", handlePointerEvent, true);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
documentObject.removeEventListener("mousedown", handlePointerEvent, true);
|
|
151
|
+
documentObject.removeEventListener("mousemove", handlePointerEvent, true);
|
|
152
|
+
documentObject.removeEventListener("mouseup", handlePointerEvent, true);
|
|
153
|
+
}
|
|
154
|
+
hasSetupGlobalListeners.delete(windowObject);
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* EXPERIMENTAL
|
|
158
|
+
* Adds a window (i.e. iframe) to the list of windows that are being tracked for focus visible.
|
|
159
|
+
*
|
|
160
|
+
* Sometimes apps render portions of their tree into an iframe. In this case, we cannot accurately track if the focus
|
|
161
|
+
* is visible because we cannot see interactions inside the iframe. If you have this in your application's architecture,
|
|
162
|
+
* then this function will attach event listeners inside the iframe. You should call `addWindowFocusTracking` with an
|
|
163
|
+
* element from inside the window you wish to add. We'll retrieve the relevant elements based on that.
|
|
164
|
+
* Note, you do not need to call this for the default window, as we call it for you.
|
|
165
|
+
*
|
|
166
|
+
* When you are ready to stop listening, but you do not wish to unmount the iframe, you may call the cleanup function
|
|
167
|
+
* returned by `addWindowFocusTracking`. Otherwise, when you unmount the iframe, all listeners and state will be cleaned
|
|
168
|
+
* up automatically for you.
|
|
169
|
+
*
|
|
170
|
+
* @param element @default document.body - The element provided will be used to get the window to add.
|
|
171
|
+
* @returns A function to remove the event listeners and cleanup the state.
|
|
172
|
+
*/
|
|
173
|
+
export function addWindowFocusTracking(element) {
|
|
174
|
+
const documentObject = getOwnerDocument(element);
|
|
175
|
+
let loadListener;
|
|
176
|
+
if (documentObject.readyState !== "loading") {
|
|
177
|
+
setupGlobalFocusEvents(element);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
loadListener = () => {
|
|
181
|
+
setupGlobalFocusEvents(element);
|
|
182
|
+
};
|
|
183
|
+
documentObject.addEventListener("DOMContentLoaded", loadListener);
|
|
184
|
+
}
|
|
185
|
+
return () => tearDownWindowFocusTracking(element, loadListener);
|
|
186
|
+
}
|
|
187
|
+
// Server-side rendering does not have the document object defined
|
|
188
|
+
// eslint-disable-next-line no-restricted-globals
|
|
189
|
+
if (typeof document !== "undefined") {
|
|
190
|
+
addWindowFocusTracking();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* If true, keyboard focus is visible.
|
|
194
|
+
*/
|
|
195
|
+
export function isFocusVisible() {
|
|
196
|
+
return currentModality !== "pointer";
|
|
197
|
+
}
|
|
198
|
+
export function getInteractionModality() {
|
|
199
|
+
return currentModality;
|
|
200
|
+
}
|
|
201
|
+
export function setInteractionModality(modality) {
|
|
202
|
+
currentModality = modality;
|
|
203
|
+
triggerChangeHandlers(modality, null);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Keeps state of the current modality.
|
|
207
|
+
*/
|
|
208
|
+
/*export function useInteractionModality(): Modality | null {
|
|
209
|
+
setupGlobalFocusEvents()
|
|
210
|
+
|
|
211
|
+
let [modality, setModality] = useState(currentModality)
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
let handler = () => {
|
|
214
|
+
setModality(currentModality)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
changeHandlers.add(handler)
|
|
218
|
+
return () => {
|
|
219
|
+
changeHandlers.delete(handler)
|
|
220
|
+
}
|
|
221
|
+
}, [])
|
|
222
|
+
|
|
223
|
+
return useIsSSR() ? null : modality
|
|
224
|
+
}*/
|
|
225
|
+
const nonTextInputTypes = new Set(["checkbox", "radio", "range", "color", "file", "image", "button", "submit", "reset"]);
|
|
226
|
+
/**
|
|
227
|
+
* If this is attached to text input component, return if the event is a focus event (Tab/Escape keys pressed) so that
|
|
228
|
+
* focus visible style can be properly set.
|
|
229
|
+
*/
|
|
230
|
+
function isKeyboardFocusEvent(isTextInput, modality, e) {
|
|
231
|
+
const IHTMLInputElement = typeof window !== "undefined" ? getOwnerWindow(e?.target).HTMLInputElement : HTMLInputElement;
|
|
232
|
+
const IHTMLTextAreaElement = typeof window !== "undefined" ? getOwnerWindow(e?.target).HTMLTextAreaElement : HTMLTextAreaElement;
|
|
233
|
+
const IHTMLElement = typeof window !== "undefined" ? getOwnerWindow(e?.target).HTMLElement : HTMLElement;
|
|
234
|
+
const IKeyboardEvent = typeof window !== "undefined" ? getOwnerWindow(e?.target).KeyboardEvent : KeyboardEvent;
|
|
235
|
+
isTextInput =
|
|
236
|
+
isTextInput ||
|
|
237
|
+
(e?.target instanceof IHTMLInputElement && !nonTextInputTypes.has(e.target.type)) ||
|
|
238
|
+
e?.target instanceof IHTMLTextAreaElement ||
|
|
239
|
+
(e?.target instanceof IHTMLElement && e.target.isContentEditable);
|
|
240
|
+
return !(isTextInput &&
|
|
241
|
+
modality === "keyboard" &&
|
|
242
|
+
e instanceof IKeyboardEvent &&
|
|
243
|
+
!FOCUS_VISIBLE_INPUT_KEYS[e.key]);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Manages focus visible state for the page, and subscribes individual components for updates.
|
|
247
|
+
*/
|
|
248
|
+
/*export function useFocusVisible(props: FocusVisibleProps = {}): FocusVisibleResult {
|
|
249
|
+
let { isTextInput, autoFocus } = props
|
|
250
|
+
let [isFocusVisibleState, setFocusVisible] = useState(autoFocus || isFocusVisible())
|
|
251
|
+
useFocusVisibleListener(
|
|
252
|
+
(isFocusVisible) => {
|
|
253
|
+
setFocusVisible(isFocusVisible)
|
|
254
|
+
},
|
|
255
|
+
[isTextInput],
|
|
256
|
+
{ isTextInput }
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
return { isFocusVisible: isFocusVisibleState }
|
|
260
|
+
}*/
|
|
261
|
+
/**
|
|
262
|
+
* Listens for trigger change and reports if focus is visible (i.e., modality is not pointer).
|
|
263
|
+
*/
|
|
264
|
+
export function useFocusVisibleListener(fn, opts) {
|
|
265
|
+
setupGlobalFocusEvents();
|
|
266
|
+
$effect(() => {
|
|
267
|
+
const handler = (modality, e) => {
|
|
268
|
+
if (!isKeyboardFocusEvent(!!opts?.isTextInput, modality, e)) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
fn(isFocusVisible());
|
|
272
|
+
};
|
|
273
|
+
changeHandlers.add(handler);
|
|
274
|
+
return () => {
|
|
275
|
+
changeHandlers.delete(handler);
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function objectToFormEntries(source = {}, parentKey = null, entries = []) {
|
|
2
|
+
for (let [key, value] of Object.entries(source)) {
|
|
3
|
+
append(entries, composeKey(parentKey, key), value);
|
|
4
|
+
}
|
|
5
|
+
return entries;
|
|
6
|
+
}
|
|
7
|
+
function composeKey(parent, key) {
|
|
8
|
+
return parent ? parent + "[" + key + "]" : key;
|
|
9
|
+
}
|
|
10
|
+
function append(entries, key, value) {
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
for (let [subkey, subvalue] of value.entries()) {
|
|
13
|
+
append(entries, composeKey(key, subkey.toString()), subvalue);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else if (value instanceof Date) {
|
|
17
|
+
entries.push([key, value.toISOString()]);
|
|
18
|
+
}
|
|
19
|
+
else if (typeof value === "boolean") {
|
|
20
|
+
entries.push([key, value ? "1" : "0"]);
|
|
21
|
+
}
|
|
22
|
+
else if (typeof value === "string") {
|
|
23
|
+
entries.push([key, value]);
|
|
24
|
+
}
|
|
25
|
+
else if (typeof value === "number") {
|
|
26
|
+
entries.push([key, `${value}`]);
|
|
27
|
+
}
|
|
28
|
+
else if (value === null || value === undefined) {
|
|
29
|
+
entries.push([key, ""]);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
objectToFormEntries(value, key, entries);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function attemptSubmit(elementInForm) {
|
|
36
|
+
let form = elementInForm?.form ?? elementInForm.closest("form");
|
|
37
|
+
if (!form)
|
|
38
|
+
return;
|
|
39
|
+
for (let element of form.elements) {
|
|
40
|
+
if (element === elementInForm)
|
|
41
|
+
continue;
|
|
42
|
+
if ((element.tagName === "INPUT" && element.type === "submit") ||
|
|
43
|
+
(element.tagName === "BUTTON" && element.type === "submit") ||
|
|
44
|
+
(element.nodeName === "INPUT" && element.type === "image")) {
|
|
45
|
+
// If you press `enter` in a normal input[type='text'] field, then the form will submit by
|
|
46
|
+
// searching for the a submit element and "click" it. We could also use the
|
|
47
|
+
// `form.requestSubmit()` function, but this has a downside where an `event.preventDefault()`
|
|
48
|
+
// inside a `click` listener on the submit button won't stop the form from submitting.
|
|
49
|
+
element.click();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// If we get here, then there is no submit button in the form. We can use the
|
|
54
|
+
// `form.requestSubmit()` function to submit the form instead. We cannot use `form.submit()`
|
|
55
|
+
// because then the `submit` event won't be fired and `onSubmit` listeners won't be fired.
|
|
56
|
+
form.requestSubmit?.();
|
|
57
|
+
}
|
package/dist/utils/id.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { nanoid, customAlphabet } from "nanoid";
|
|
2
|
+
import { getContext, setContext } from "svelte";
|
|
3
|
+
export const alphaid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
|
4
|
+
export const htmlid = (size = 10) => alphaid(1) + nanoid(size - 1);
|
|
5
|
+
export const getIdContext = () => getContext("Id");
|
|
6
|
+
export const createIdContext = (id) => setContext("Id", id);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import { isAndroid } from "./platform.js";
|
|
13
|
+
// Original licensing for the following method can be found in the
|
|
14
|
+
// NOTICE file in the root directory of this source tree.
|
|
15
|
+
// See https://github.com/facebook/react/blob/3c713d513195a53788b3f8bb4b70279d68b15bcc/packages/react-interactions/events/src/dom/shared/index.js#L74-L87
|
|
16
|
+
// Keyboards, Assistive Technologies, and element.click() all produce a "virtual"
|
|
17
|
+
// click event. This is a method of inferring such clicks. Every browser except
|
|
18
|
+
// IE 11 only sets a zero value of "detail" for click events that are "virtual".
|
|
19
|
+
// However, IE 11 uses a zero value for all click events. For IE 11 we rely on
|
|
20
|
+
// the quirk that it produces click events that are of type PointerEvent, and
|
|
21
|
+
// where only the "virtual" click lacks a pointerType field.
|
|
22
|
+
export function isVirtualClick(event) {
|
|
23
|
+
// JAWS/NVDA with Firefox.
|
|
24
|
+
if (event.mozInputSource === 0 && event.isTrusted) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Android TalkBack's detail value varies depending on the event listener providing the event so we have specific logic here instead
|
|
28
|
+
// If pointerType is defined, event is from a click listener. For events from mousedown listener, detail === 0 is a sufficient check
|
|
29
|
+
// to detect TalkBack virtual clicks.
|
|
30
|
+
if (isAndroid() && event.pointerType) {
|
|
31
|
+
return event.type === "click" && event.buttons === 1;
|
|
32
|
+
}
|
|
33
|
+
return event.detail === 0 && !event.pointerType;
|
|
34
|
+
}
|
|
35
|
+
export function isVirtualPointerEvent(event) {
|
|
36
|
+
// If the pointer size is zero, then we assume it's from a screen reader.
|
|
37
|
+
// Android TalkBack double tap will sometimes return a event with width and height of 1
|
|
38
|
+
// and pointerType === 'mouse' so we need to check for a specific combination of event attributes.
|
|
39
|
+
// Cannot use "event.pressure === 0" as the sole check due to Safari pointer events always returning pressure === 0
|
|
40
|
+
// instead of .5, see https://bugs.webkit.org/show_bug.cgi?id=206216. event.pointerType === 'mouse' is to distingush
|
|
41
|
+
// Talkback double tap from Windows Firefox touch screen press
|
|
42
|
+
return ((!isAndroid() && event.width === 0 && event.height === 0) ||
|
|
43
|
+
(event.width === 1 &&
|
|
44
|
+
event.height === 1 &&
|
|
45
|
+
event.pressure === 0 &&
|
|
46
|
+
event.detail === 0 &&
|
|
47
|
+
event.pointerType === "mouse"));
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function microTask(cb: () => void): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function compact<T extends Record<any, any>>(object: T): {} & T;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function testUserAgent(re) {
|
|
2
|
+
if (typeof window === "undefined" || window.navigator == null) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
return (window.navigator["userAgentData"]?.brands.some((brand) => re.test(brand.brand)) || re.test(window.navigator.userAgent));
|
|
6
|
+
}
|
|
7
|
+
export function testPlatform(re) {
|
|
8
|
+
return typeof window !== "undefined" && window.navigator != null
|
|
9
|
+
? re.test(window.navigator["userAgentData"]?.platform || window.navigator.platform)
|
|
10
|
+
: false;
|
|
11
|
+
}
|
|
12
|
+
function cached(fn) {
|
|
13
|
+
if (process.env.NODE_ENV === "test") {
|
|
14
|
+
return fn;
|
|
15
|
+
}
|
|
16
|
+
let res = null;
|
|
17
|
+
return () => {
|
|
18
|
+
if (res == null) {
|
|
19
|
+
res = fn();
|
|
20
|
+
}
|
|
21
|
+
return res;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export const isMac = cached(function () {
|
|
25
|
+
return testPlatform(/^Mac/i);
|
|
26
|
+
});
|
|
27
|
+
export const isAndroid = cached(function () {
|
|
28
|
+
return testUserAgent(/Android/i);
|
|
29
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const stateFromSlot: <TSlot extends Record<string, any>>(slot?: TSlot) => Record<string, string>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const stateFromSlot = (slot = {}) => {
|
|
2
|
+
let dataAttributes = {};
|
|
3
|
+
let exposeState = false;
|
|
4
|
+
let states = [];
|
|
5
|
+
for (let [k, v] of Object.entries(slot)) {
|
|
6
|
+
if (typeof v === "boolean") {
|
|
7
|
+
exposeState = true;
|
|
8
|
+
}
|
|
9
|
+
if (v === true) {
|
|
10
|
+
states.push(k.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (exposeState) {
|
|
14
|
+
dataAttributes["data-headlessui-state"] = states.join(" ");
|
|
15
|
+
for (let s of states) {
|
|
16
|
+
dataAttributes[`data-${s}`] = "";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return dataAttributes;
|
|
20
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pzerelles/headlessui-svelte",
|
|
3
|
+
"version": "2.0.0-next.1",
|
|
4
|
+
"exports": {
|
|
5
|
+
".": {
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"svelte": "./dist/index.js"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/**/*.test.*",
|
|
13
|
+
"!dist/**/*.spec.*"
|
|
14
|
+
],
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"svelte": "^5.0.0-next.1"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@changesets/cli": "^2.27.5",
|
|
20
|
+
"@changesets/types": "^6.0.0",
|
|
21
|
+
"@playwright/test": "^1.44.1",
|
|
22
|
+
"@sveltejs/adapter-auto": "^3.2.1",
|
|
23
|
+
"@sveltejs/kit": "^2.5.10",
|
|
24
|
+
"@sveltejs/package": "^2.3.1",
|
|
25
|
+
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
|
26
|
+
"@types/eslint": "^8.56.10",
|
|
27
|
+
"@types/node": "^20.14.2",
|
|
28
|
+
"autoprefixer": "^10.4.19",
|
|
29
|
+
"eslint": "^9.4.0",
|
|
30
|
+
"eslint-config-prettier": "^9.1.0",
|
|
31
|
+
"eslint-plugin-svelte": "^2.39.0",
|
|
32
|
+
"globals": "^15.3.0",
|
|
33
|
+
"outdent": "^0.8.0",
|
|
34
|
+
"postcss": "^8.4.38",
|
|
35
|
+
"prettier": "^3.3.1",
|
|
36
|
+
"prettier-plugin-svelte": "^3.2.3",
|
|
37
|
+
"prettier-plugin-tailwindcss": "^0.5.14",
|
|
38
|
+
"publint": "^0.1.16",
|
|
39
|
+
"svelte": "5.0.0-next.150",
|
|
40
|
+
"svelte-check": "^3.8.0",
|
|
41
|
+
"tailwindcss": "^3.4.4",
|
|
42
|
+
"tslib": "^2.6.3",
|
|
43
|
+
"typescript": "^5.4.5",
|
|
44
|
+
"typescript-eslint": "8.0.0-alpha.28",
|
|
45
|
+
"vite": "^5.2.12",
|
|
46
|
+
"vitest": "^1.6.0"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"nanoid": "^5.0.7",
|
|
50
|
+
"svelte-interactions": "^0.2.0"
|
|
51
|
+
},
|
|
52
|
+
"svelte": "./dist/index.js",
|
|
53
|
+
"types": "./dist/index.d.ts",
|
|
54
|
+
"type": "module",
|
|
55
|
+
"scripts": {
|
|
56
|
+
"dev": "vite dev",
|
|
57
|
+
"build": "vite build && npm run package",
|
|
58
|
+
"preview": "vite preview",
|
|
59
|
+
"package": "svelte-kit sync && svelte-package && publint",
|
|
60
|
+
"test": "npm run test:integration && npm run test:unit",
|
|
61
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
62
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
63
|
+
"lint": "prettier --check . && eslint .",
|
|
64
|
+
"format": "prettier --write .",
|
|
65
|
+
"test:integration": "playwright test",
|
|
66
|
+
"test:unit": "vitest",
|
|
67
|
+
"release": "pnpm package && changeset publish"
|
|
68
|
+
}
|
|
69
|
+
}
|