-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
399 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { z } from 'zod'; | ||
|
||
export const spSchema = z.object({ | ||
firstName: z.string().nullish(), | ||
lastName: z.string().nullish(), | ||
phoneNumber: z.string().nullish(), | ||
line1: z.string().nullish(), | ||
line2: z.string().nullish(), | ||
city: z.string().nullish(), | ||
state: z.string().nullish(), | ||
zip: z.string().nullish(), | ||
country: z.string().nullish(), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { getElement, getTextContent, setTextContent } from '#utils'; | ||
|
||
import type { ActionReturn } from 'svelte/action'; | ||
import type { ElementOrSelector } from '#utils'; | ||
|
||
interface CopyAttributes { | ||
'on:!copy'?: (event: CustomEvent<string>) => void; | ||
} | ||
|
||
interface CutAttributes { | ||
'on:!cut'?: (event: CustomEvent<string>) => void; | ||
} | ||
|
||
interface PasteAttributes { | ||
'on:!paste'?: (event: CustomEvent<string>) => void; | ||
} | ||
|
||
/** | ||
* Copies the text content of `target` to the clipboard when `node` is clicked. | ||
* If `target` is not provided, `node`'s textContent is copied. When `target` is a string, it is used as a query selector. | ||
* Also dispatches a `!copy` event on 'node' with the copied text as `detail`. | ||
* When `target` is an input or textarea, its value is copied. Otherwise, textContent is copied. | ||
* | ||
* Example: | ||
* ```svelte | ||
* <input id="input" value="Some value" bind:this={targetElement} /> | ||
* <button use:copy={"#input"} on:!copy={handler} /> | ||
* <button use:copy={targetElement} /> | ||
* <p use:copy>Some text</p> | ||
*``` | ||
*/ | ||
export function copy(node: HTMLElement, target?: ElementOrSelector): ActionReturn<ElementOrSelector, CopyAttributes> { | ||
let targetNode = getElement(target, node); | ||
|
||
async function handleClick() { | ||
const text = getTextContent(targetNode); | ||
await navigator.clipboard.writeText(text); | ||
node.dispatchEvent(new CustomEvent('!copy', { detail: { text } })); | ||
} | ||
|
||
node.addEventListener('click', handleClick); | ||
|
||
return { | ||
update: (newTarget: ElementOrSelector) => { | ||
targetNode = getElement(newTarget, node); | ||
}, | ||
destroy: () => node.removeEventListener('click', handleClick), | ||
}; | ||
} | ||
|
||
/** | ||
* Cuts the text content of `target` to the clipboard when `node` is clicked. | ||
* If `target` is not provided, `node`'s textContent is cut. When `target` is a string, it is used as a query selector. | ||
* Also dispatches a `!cut` event on 'node' with the cut text as `detail`. | ||
* When `target` is an input or textarea, its value is cut. Otherwise, textContent is cut. | ||
* The original value or textArea is replaced with an empty string. | ||
* | ||
* Example: | ||
* ```svelte | ||
* <input id="input" value="Some value" bind:this={targetElement} /> | ||
* <button use:cut={"#input"} on:!cut={handler} /> | ||
* <button use:cut={targetElement} /> | ||
* <p use:cut>Some text</p> | ||
*``` | ||
*/ | ||
export function cut(node: HTMLElement, target?: ElementOrSelector): ActionReturn<ElementOrSelector, CutAttributes> { | ||
let targetNode = getElement(target, node); | ||
|
||
async function handleClick() { | ||
const text = getTextContent(targetNode); | ||
await navigator.clipboard.writeText(text); | ||
node.dispatchEvent(new CustomEvent('!cut', { detail: text })); | ||
setTextContent(targetNode, ''); | ||
} | ||
|
||
node.addEventListener('click', handleClick); | ||
|
||
return { | ||
update: (newTarget: ElementOrSelector) => { | ||
targetNode = getElement(newTarget, node); | ||
}, | ||
destroy: () => node.removeEventListener('click', handleClick), | ||
}; | ||
} | ||
|
||
/** | ||
* Pastes the text content of the clipboard to `target` when `node` is clicked. | ||
* If `target` is not provided, the clipboard contents are pasted to `node`'s textContent. When `target` is a string, it is used as a query selector. | ||
* Also dispatches a `!paste` event on 'node' with the pasted text as `detail`. | ||
* When `target` is an input or textarea, its value is pasted. Otherwise, textContent is pasted. | ||
* | ||
* Example: | ||
* ```svelte | ||
* <input id="input" bind:this={targetElement} /> | ||
* <button use:paste={"#input"} on:!paste={handler} /> | ||
* <button use:paste={targetElement} /> | ||
* <p use:paste /> | ||
*``` | ||
*/ | ||
export function paste(node: HTMLElement, target?: ElementOrSelector): ActionReturn<ElementOrSelector, PasteAttributes> { | ||
let targetNode = getElement(target, node); | ||
|
||
async function handleClick() { | ||
const text = await navigator.clipboard.readText(); | ||
node.dispatchEvent(new CustomEvent('!paste', { detail: text })); | ||
setTextContent(targetNode, text); | ||
} | ||
|
||
node.addEventListener('click', handleClick); | ||
|
||
return { | ||
update: (newTarget: ElementOrSelector) => { | ||
targetNode = getElement(newTarget, node); | ||
}, | ||
destroy: () => node.removeEventListener('click', handleClick), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { browser } from './env.js'; | ||
|
||
export type ElementOrSelector = HTMLElement | string | undefined; | ||
|
||
/** | ||
* Returns the target node from the provided target or the fallback node. | ||
*/ | ||
export function getElement(target: ElementOrSelector, fallback: HTMLElement): HTMLElement; | ||
export function getElement(target: ElementOrSelector): HTMLElement | undefined; | ||
export function getElement(target: ElementOrSelector, fallback?: HTMLElement) { | ||
return (typeof target === 'string' ? document.querySelector(target) : target) || fallback; | ||
} | ||
|
||
/** | ||
* Returns the text content of the target node. If the target is an input or textarea, its value is returned. Otherwise, textContent is returned. | ||
*/ | ||
export function getTextContent(target: Element) { | ||
return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement | ||
? target.value | ||
: target.textContent || ''; | ||
} | ||
|
||
/** | ||
* Sets the text content of the target node. If the target is an input or textarea, its value is set. Otherwise, textContent is set. | ||
*/ | ||
export function setTextContent(target: Element, text: string) { | ||
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) { | ||
target.value = text; | ||
} else { | ||
target.textContent = text; | ||
} | ||
} | ||
|
||
// This list originates from: https://stackoverflow.com/a/30753870 | ||
const focusableElements = ` | ||
a[href]:not([tabindex='-1']), | ||
area[href]:not([tabindex='-1']), | ||
input:not([disabled]):not([tabindex='-1']), | ||
select:not([disabled]):not([tabindex='-1']), | ||
textarea:not([disabled]):not([tabindex='-1']), | ||
button:not([disabled]):not([tabindex='-1']), | ||
iframe:not([tabindex='-1']), | ||
[tabindex]:not([tabindex='-1']), | ||
[contentEditable=true]:not([tabindex='-1']) | ||
`; | ||
|
||
/** | ||
* Returns true if the node is focusable. | ||
*/ | ||
export function isFocusable(node: HTMLElement) { | ||
return node && node.matches(focusableElements); | ||
} | ||
|
||
/** | ||
* Returns an array with all focusable children of the node. | ||
*/ | ||
export function getFocusableChildren(node: HTMLElement): HTMLElement[] { | ||
return node ? Array.from(node.querySelectorAll(focusableElements)) : []; | ||
} |
Oops, something went wrong.