lutra 0.1.0 → 0.1.4
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/dist/components/Avatar.svelte +105 -0
- package/dist/components/Avatar.svelte.d.ts +14 -0
- package/dist/components/Close.svelte +76 -0
- package/dist/components/Close.svelte.d.ts +7 -0
- package/dist/components/ContextTip.svelte +41 -0
- package/dist/components/ContextTip.svelte.d.ts +7 -0
- package/dist/components/Icon.svelte +62 -0
- package/dist/components/Icon.svelte.d.ts +8 -0
- package/dist/components/IconButton.svelte +120 -0
- package/dist/components/IconButton.svelte.d.ts +16 -0
- package/dist/components/Image.svelte +172 -0
- package/dist/components/Image.svelte.d.ts +56 -0
- package/dist/components/Indicator.svelte +387 -0
- package/dist/components/Indicator.svelte.d.ts +12 -0
- package/dist/components/Inset.svelte +23 -0
- package/dist/components/Inset.svelte.d.ts +7 -0
- package/dist/components/Layout.svelte +2 -1
- package/dist/components/MenuDropdown.svelte +195 -0
- package/dist/components/MenuDropdown.svelte.d.ts +16 -0
- package/dist/components/MenuItem.svelte +155 -0
- package/dist/components/MenuItem.svelte.d.ts +11 -0
- package/dist/components/MenuItemContent.svelte +25 -0
- package/dist/components/MenuItemContent.svelte.d.ts +10 -0
- package/dist/components/MenuTypes.d.ts +72 -0
- package/dist/components/MenuTypes.js +1 -0
- package/dist/components/Modal.svelte +149 -0
- package/dist/components/Modal.svelte.d.ts +16 -0
- package/dist/components/Notification.svelte +115 -0
- package/dist/components/Notification.svelte.d.ts +12 -0
- package/dist/components/Overlay.svelte +31 -0
- package/dist/components/Overlay.svelte.d.ts +14 -0
- package/dist/components/OverlayContainer.svelte +31 -0
- package/dist/components/OverlayContainer.svelte.d.ts +18 -0
- package/dist/components/OverlayLayer.svelte +168 -0
- package/dist/components/OverlayLayer.svelte.d.ts +8 -0
- package/dist/components/TabbedContent.svelte +74 -0
- package/dist/components/TabbedContent.svelte.d.ts +11 -0
- package/dist/components/TabbedContentItem.svelte +33 -0
- package/dist/components/TabbedContentItem.svelte.d.ts +10 -0
- package/dist/components/Table.svelte +41 -0
- package/dist/components/Table.svelte.d.ts +13 -0
- package/dist/components/Tabs.svelte +216 -0
- package/dist/components/Tabs.svelte.d.ts +20 -0
- package/dist/components/Tag.svelte +120 -0
- package/dist/components/Tag.svelte.d.ts +21 -0
- package/dist/components/Theme.svelte +32 -14
- package/dist/components/Tooltip.svelte +8 -8
- package/dist/components/UIContent.svelte +19 -0
- package/dist/components/UIContent.svelte.d.ts +7 -0
- package/dist/components/index.d.ts +28 -0
- package/dist/components/index.js +29 -0
- package/dist/components/notifications.svelte.d.ts +21 -0
- package/dist/components/notifications.svelte.js +30 -0
- package/dist/components/overlays.svelte.d.ts +36 -0
- package/dist/components/overlays.svelte.js +44 -0
- package/dist/css/1-props.css +389 -724
- package/dist/css/2-base.css +257 -123
- package/dist/css/3-typo.css +75 -34
- package/dist/css/4-layout.css +364 -1
- package/dist/css/5-media.css +106 -11
- package/dist/css/lutra.css +2 -1
- package/dist/css/themes/DefaultTheme.css +209 -0
- package/dist/form/Button.svelte +58 -0
- package/dist/form/Button.svelte.d.ts +15 -0
- package/dist/form/Datepicker.svelte +311 -0
- package/dist/form/Datepicker.svelte.d.ts +9 -0
- package/dist/form/FieldContent.svelte +178 -0
- package/dist/form/FieldContent.svelte.d.ts +21 -0
- package/dist/form/FieldError.svelte +24 -0
- package/dist/form/FieldError.svelte.d.ts +7 -0
- package/dist/form/Fieldset.svelte +103 -0
- package/dist/form/Fieldset.svelte.d.ts +20 -0
- package/dist/form/Form.svelte +220 -0
- package/dist/form/Form.svelte.d.ts +38 -0
- package/dist/form/FormActions.svelte +80 -0
- package/dist/form/FormActions.svelte.d.ts +9 -0
- package/dist/form/FormSection.svelte +96 -0
- package/dist/form/FormSection.svelte.d.ts +9 -0
- package/dist/form/ImageUpload.svelte +299 -0
- package/dist/form/ImageUpload.svelte.d.ts +20 -0
- package/dist/form/Input.svelte +444 -0
- package/dist/form/Input.svelte.d.ts +108 -0
- package/dist/form/InputLength.svelte +42 -0
- package/dist/form/InputLength.svelte.d.ts +9 -0
- package/dist/form/Label.svelte +88 -0
- package/dist/form/Label.svelte.d.ts +16 -0
- package/dist/form/LogoUpload.svelte +115 -0
- package/dist/form/LogoUpload.svelte.d.ts +18 -0
- package/dist/form/Select.svelte +186 -0
- package/dist/form/Select.svelte.d.ts +59 -0
- package/dist/form/Textarea.svelte +265 -0
- package/dist/form/Textarea.svelte.d.ts +95 -0
- package/dist/form/Toggle.svelte +4 -0
- package/dist/form/Toggle.svelte.d.ts +18 -0
- package/dist/form/client.svelte.d.ts +45 -0
- package/dist/form/client.svelte.js +102 -0
- package/dist/form/form.d.ts +55 -0
- package/dist/form/form.js +345 -0
- package/dist/form/index.d.ts +17 -0
- package/dist/form/index.js +17 -0
- package/dist/form/types.d.ts +55 -0
- package/dist/form/types.js +1 -0
- package/dist/icons/IconAlert.svelte +3 -0
- package/dist/icons/IconAlert.svelte.d.ts +26 -0
- package/dist/icons/IconCopy.svelte +3 -0
- package/dist/icons/IconCopy.svelte.d.ts +26 -0
- package/dist/icons/IconDone.svelte +3 -0
- package/dist/icons/IconDone.svelte.d.ts +26 -0
- package/dist/icons/IconError.svelte +3 -0
- package/dist/icons/IconError.svelte.d.ts +26 -0
- package/dist/icons/IconHelp.svelte +3 -0
- package/dist/icons/IconHelp.svelte.d.ts +26 -0
- package/dist/icons/IconHide.svelte +3 -0
- package/dist/icons/IconHide.svelte.d.ts +26 -0
- package/dist/icons/IconInfo.svelte +3 -0
- package/dist/icons/IconInfo.svelte.d.ts +26 -0
- package/dist/icons/IconLink.svelte +3 -0
- package/dist/icons/IconLink.svelte.d.ts +26 -0
- package/dist/icons/IconMenuBurger.svelte +3 -0
- package/dist/icons/IconMenuBurger.svelte.d.ts +26 -0
- package/dist/icons/IconMenuDots.svelte +3 -0
- package/dist/icons/IconMenuDots.svelte.d.ts +26 -0
- package/dist/icons/IconSearch.svelte +3 -0
- package/dist/icons/IconSearch.svelte.d.ts +26 -0
- package/dist/icons/IconShow.svelte +3 -0
- package/dist/icons/IconShow.svelte.d.ts +26 -0
- package/dist/icons/IconSuccess.svelte +3 -0
- package/dist/icons/IconSuccess.svelte.d.ts +26 -0
- package/dist/icons/IconWarning.svelte +3 -0
- package/dist/icons/IconWarning.svelte.d.ts +26 -0
- package/dist/icons/index.d.ts +14 -0
- package/dist/icons/index.js +14 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +3 -5
- package/dist/util/StringOrComponent.svelte +20 -0
- package/dist/util/StringOrComponent.svelte.d.ts +8 -0
- package/dist/util/StringOrSnippet.svelte +16 -0
- package/dist/util/StringOrSnippet.svelte.d.ts +8 -0
- package/dist/util/attr.d.ts +5 -0
- package/dist/util/attr.js +21 -0
- package/dist/util/color.d.ts +51 -0
- package/dist/util/color.js +97 -0
- package/dist/util/dom.d.ts +15 -0
- package/dist/util/dom.js +73 -0
- package/dist/util/keyboard.svelte.d.ts +22 -0
- package/dist/util/keyboard.svelte.js +161 -0
- package/dist/util/locale.d.ts +1 -0
- package/dist/util/locale.js +47 -0
- package/dist/util/settings.d.ts +4 -0
- package/dist/util/settings.js +1 -0
- package/package.json +20 -11
- package/dist/css/0-layers.css +0 -1
@@ -0,0 +1,8 @@
|
|
1
|
+
import type { Component } from "svelte";
|
2
|
+
type $$ComponentProps = {
|
3
|
+
content: string | Component | undefined;
|
4
|
+
props?: Record<string, any>;
|
5
|
+
};
|
6
|
+
declare const StringOrComponent: Component<$$ComponentProps, {}, "">;
|
7
|
+
type StringOrComponent = ReturnType<typeof StringOrComponent>;
|
8
|
+
export default StringOrComponent;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { Snippet } from "svelte";
|
3
|
+
let {
|
4
|
+
props,
|
5
|
+
content,
|
6
|
+
}: {
|
7
|
+
props?: Record<string, any>;
|
8
|
+
content?: string | Snippet<[Record<string, any> | undefined]>;
|
9
|
+
} = $props();
|
10
|
+
</script>
|
11
|
+
|
12
|
+
{#if typeof content === 'string'}
|
13
|
+
{content}
|
14
|
+
{:else if content}
|
15
|
+
{@render content(props)}
|
16
|
+
{/if}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import type { Snippet } from "svelte";
|
2
|
+
type $$ComponentProps = {
|
3
|
+
props?: Record<string, any>;
|
4
|
+
content?: string | Snippet<[Record<string, any> | undefined]>;
|
5
|
+
};
|
6
|
+
declare const StringOrSnippet: import("svelte").Component<$$ComponentProps, {}, "">;
|
7
|
+
type StringOrSnippet = ReturnType<typeof StringOrSnippet>;
|
8
|
+
export default StringOrSnippet;
|
@@ -0,0 +1,5 @@
|
|
1
|
+
/**
|
2
|
+
* Node attribute helper. This is used to create a function that will set e.g. onclick, aria, etc. attributes on an element.
|
3
|
+
* @returns {Function} - The function to set the attributes. Pass this as `attr(opts)` to a child which can use it with `use:attr`.
|
4
|
+
*/
|
5
|
+
export declare function attr(opts: Record<string, any>): (node: Element) => void;
|
@@ -0,0 +1,21 @@
|
|
1
|
+
/**
|
2
|
+
* Node attribute helper. This is used to create a function that will set e.g. onclick, aria, etc. attributes on an element.
|
3
|
+
* @returns {Function} - The function to set the attributes. Pass this as `attr(opts)` to a child which can use it with `use:attr`.
|
4
|
+
*/
|
5
|
+
export function attr(opts) {
|
6
|
+
return function (node) {
|
7
|
+
for (const [key, value] of Object.entries(opts)) {
|
8
|
+
if (key.startsWith('on')) {
|
9
|
+
node.addEventListener(key.slice(2).toLowerCase(), value);
|
10
|
+
}
|
11
|
+
else {
|
12
|
+
if (value === false) {
|
13
|
+
node.removeAttribute(key);
|
14
|
+
}
|
15
|
+
else {
|
16
|
+
node.setAttribute(key, value);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
};
|
21
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
/**
|
2
|
+
* Convert a string to a color
|
3
|
+
* @param {string} str - The string to convert
|
4
|
+
* @returns {string} The color
|
5
|
+
*/
|
6
|
+
export declare function stringToColor(str: string): string;
|
7
|
+
/**
|
8
|
+
* Convert a hex color to rgb
|
9
|
+
* @param {string} hex - The hex color
|
10
|
+
* @returns {number[]} The rgb color
|
11
|
+
*/
|
12
|
+
export declare function hexToRgb(hex: string): number[];
|
13
|
+
/**
|
14
|
+
* Get the relative luminance of a color from a hex color
|
15
|
+
* @param {string} hex - The hex color
|
16
|
+
* @returns {number} The relative luminance
|
17
|
+
*/
|
18
|
+
export declare function hexRelativeLuminance(hex: string): number;
|
19
|
+
/**
|
20
|
+
* Get the relative luminance of a color according to the W3C
|
21
|
+
* @param {number} R8bit - The red value
|
22
|
+
* @param {number} G8bit - The green value
|
23
|
+
* @param {number} B8bit - The blue value
|
24
|
+
* @returns
|
25
|
+
*/
|
26
|
+
export declare function relativeLuminanceW3C(R8bit: number, G8bit: number, B8bit: number): number;
|
27
|
+
/**
|
28
|
+
* Status colors
|
29
|
+
*/
|
30
|
+
export declare const StatusColors: {
|
31
|
+
readonly default: "default";
|
32
|
+
readonly ok: "ok";
|
33
|
+
readonly alert: "alert";
|
34
|
+
readonly warn: "warn";
|
35
|
+
readonly info: "info";
|
36
|
+
readonly task: "task";
|
37
|
+
};
|
38
|
+
export type StatusColor = keyof typeof StatusColors;
|
39
|
+
export type StatusColorOrString = StatusColor | (string & {});
|
40
|
+
/**
|
41
|
+
* Check if the value is a valid StatusColor
|
42
|
+
* @param {string} value - The value to check
|
43
|
+
* @returns {boolean} - If the value is a valid StatusColor
|
44
|
+
*/
|
45
|
+
export declare const isStatusColor: (value?: string) => value is StatusColor;
|
46
|
+
/**
|
47
|
+
* Get the CSS variable for a status color
|
48
|
+
* @param {StatusColorOrString} color - The status color
|
49
|
+
* @returns {string} - The CSS variable for the status color
|
50
|
+
*/
|
51
|
+
export declare function getStatusColorVar(color?: StatusColorOrString, fallback?: string): string;
|
@@ -0,0 +1,97 @@
|
|
1
|
+
/**
|
2
|
+
* Convert a string to a color
|
3
|
+
* @param {string} str - The string to convert
|
4
|
+
* @returns {string} The color
|
5
|
+
*/
|
6
|
+
export function stringToColor(str) {
|
7
|
+
let hash = 0;
|
8
|
+
for (let i = 0; i < str.length; i++) {
|
9
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
10
|
+
}
|
11
|
+
let color = '#';
|
12
|
+
for (let i = 0; i < 3; i++) {
|
13
|
+
const value = (hash >> (i * 8)) & 0xFF;
|
14
|
+
color += ('00' + value.toString(16)).substr(-2);
|
15
|
+
}
|
16
|
+
return color;
|
17
|
+
}
|
18
|
+
/**
|
19
|
+
* Convert a hex color to rgb
|
20
|
+
* @param {string} hex - The hex color
|
21
|
+
* @returns {number[]} The rgb color
|
22
|
+
*/
|
23
|
+
export function hexToRgb(hex) {
|
24
|
+
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
25
|
+
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
26
|
+
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
|
27
|
+
return r + r + g + g + b + b;
|
28
|
+
});
|
29
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
30
|
+
if (!result) {
|
31
|
+
throw new Error('Invalid hex color');
|
32
|
+
}
|
33
|
+
return [
|
34
|
+
parseInt(result[1], 16),
|
35
|
+
parseInt(result[2], 16),
|
36
|
+
parseInt(result[3], 16)
|
37
|
+
];
|
38
|
+
}
|
39
|
+
/**
|
40
|
+
* Get the relative luminance of a color from a hex color
|
41
|
+
* @param {string} hex - The hex color
|
42
|
+
* @returns {number} The relative luminance
|
43
|
+
*/
|
44
|
+
export function hexRelativeLuminance(hex) {
|
45
|
+
const rgb = hexToRgb(hex);
|
46
|
+
return relativeLuminanceW3C(rgb[0], rgb[1], rgb[2]);
|
47
|
+
}
|
48
|
+
/**
|
49
|
+
* Get the relative luminance of a color according to the W3C
|
50
|
+
* @param {number} R8bit - The red value
|
51
|
+
* @param {number} G8bit - The green value
|
52
|
+
* @param {number} B8bit - The blue value
|
53
|
+
* @returns
|
54
|
+
*/
|
55
|
+
export function relativeLuminanceW3C(R8bit, G8bit, B8bit) {
|
56
|
+
var RsRGB = R8bit / 255;
|
57
|
+
var GsRGB = G8bit / 255;
|
58
|
+
var BsRGB = B8bit / 255;
|
59
|
+
var R = (RsRGB <= 0.03928) ? RsRGB / 12.92 : Math.pow((RsRGB + 0.055) / 1.055, 2.4);
|
60
|
+
var G = (GsRGB <= 0.03928) ? GsRGB / 12.92 : Math.pow((GsRGB + 0.055) / 1.055, 2.4);
|
61
|
+
var B = (BsRGB <= 0.03928) ? BsRGB / 12.92 : Math.pow((BsRGB + 0.055) / 1.055, 2.4);
|
62
|
+
// For the sRGB colorspace, the relative luminance of a color is defined as:
|
63
|
+
var L = 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
64
|
+
return L;
|
65
|
+
}
|
66
|
+
/**
|
67
|
+
* Status colors
|
68
|
+
*/
|
69
|
+
export const StatusColors = {
|
70
|
+
default: "default",
|
71
|
+
ok: "ok",
|
72
|
+
alert: "alert",
|
73
|
+
warn: "warn",
|
74
|
+
info: "info",
|
75
|
+
task: "task"
|
76
|
+
};
|
77
|
+
/**
|
78
|
+
* Check if the value is a valid StatusColor
|
79
|
+
* @param {string} value - The value to check
|
80
|
+
* @returns {boolean} - If the value is a valid StatusColor
|
81
|
+
*/
|
82
|
+
export const isStatusColor = (value) => {
|
83
|
+
if (!value)
|
84
|
+
return false;
|
85
|
+
return Object.keys(StatusColors).includes(value);
|
86
|
+
};
|
87
|
+
/**
|
88
|
+
* Get the CSS variable for a status color
|
89
|
+
* @param {StatusColorOrString} color - The status color
|
90
|
+
* @returns {string} - The CSS variable for the status color
|
91
|
+
*/
|
92
|
+
export function getStatusColorVar(color, fallback) {
|
93
|
+
if (isStatusColor(color)) {
|
94
|
+
return `var(--status-${color})`;
|
95
|
+
}
|
96
|
+
return color || fallback || "var(--status-default)";
|
97
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/**
|
2
|
+
* Find the containing block of a given element. The containing block is the nearest ancestor element that is a containing block for positioned elements.
|
3
|
+
* @param {HTMLElement | null} element - The element to find the containing block for.
|
4
|
+
* @returns {HTMLElement | null} - The containing block element, or null if none is found.
|
5
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block
|
6
|
+
*/
|
7
|
+
export declare function findContainingBlock(element: HTMLElement | null, id?: string): HTMLElement | null;
|
8
|
+
export declare function findAllContainingBlocks(element: HTMLElement | null): HTMLElement[];
|
9
|
+
/**
|
10
|
+
* Get the total offset of an element, including all its containing blocks.
|
11
|
+
*/
|
12
|
+
export declare function getPossiblyContainedPosition(element: HTMLElement | null): {
|
13
|
+
left: number;
|
14
|
+
top: number;
|
15
|
+
};
|
package/dist/util/dom.js
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
let initialElements = {};
|
2
|
+
let initialStyles = {};
|
3
|
+
/**
|
4
|
+
* Find the containing block of a given element. The containing block is the nearest ancestor element that is a containing block for positioned elements.
|
5
|
+
* @param {HTMLElement | null} element - The element to find the containing block for.
|
6
|
+
* @returns {HTMLElement | null} - The containing block element, or null if none is found.
|
7
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block
|
8
|
+
*/
|
9
|
+
export function findContainingBlock(element, id = crypto.randomUUID()) {
|
10
|
+
const elementToLookAt = element?.parentElement;
|
11
|
+
// Base case for recursion: if the element is the body or null, return null as no containing block was found
|
12
|
+
if (!elementToLookAt || !element || element.tagName === "BODY" || elementToLookAt.tagName === "BODY") {
|
13
|
+
return null;
|
14
|
+
}
|
15
|
+
if (!initialElements[id]) {
|
16
|
+
initialElements[id] = element;
|
17
|
+
initialStyles[id] = getComputedStyle(element);
|
18
|
+
}
|
19
|
+
// Get computed styles of the current element
|
20
|
+
const style = getComputedStyle(elementToLookAt);
|
21
|
+
// Check for properties that make this element a containing block for positioned elements
|
22
|
+
if ((style.position !== 'static' && (initialStyles[id].position === 'fixed' ||
|
23
|
+
initialStyles[id].position === 'absolute' ||
|
24
|
+
initialStyles[id].position === 'sticky')) ||
|
25
|
+
(style.transform !== 'none' && style.transform !== undefined) ||
|
26
|
+
style.perspective !== 'none' && style.perspective !== undefined ||
|
27
|
+
style.filter !== 'none' && style.filter !== undefined ||
|
28
|
+
style.willChange === 'transform' || style.willChange === 'perspective' ||
|
29
|
+
style.willChange === 'filter' || // Firefox specific for filters
|
30
|
+
(style.contain.includes('layout') || style.contain.includes('paint') || style.contain.includes('strict') || style.contain.includes('content')) ||
|
31
|
+
style.containerType !== 'normal' ||
|
32
|
+
(style.backdropFilter !== 'none' && style.backdropFilter !== undefined)) {
|
33
|
+
delete initialElements[id];
|
34
|
+
delete initialStyles[id];
|
35
|
+
return element;
|
36
|
+
}
|
37
|
+
// Recurse up the DOM tree if no containing block found
|
38
|
+
return findContainingBlock(elementToLookAt, id);
|
39
|
+
}
|
40
|
+
export function findAllContainingBlocks(element) {
|
41
|
+
let currentElement = findContainingBlock(element);
|
42
|
+
if (!currentElement)
|
43
|
+
return [];
|
44
|
+
const blocks = [currentElement];
|
45
|
+
let i = 0;
|
46
|
+
while (currentElement !== null && currentElement.tagName !== 'BODY' && i < 50) {
|
47
|
+
const block = findContainingBlock(currentElement.parentElement);
|
48
|
+
if (block) {
|
49
|
+
blocks.push(block.parentElement);
|
50
|
+
}
|
51
|
+
currentElement = block;
|
52
|
+
i++;
|
53
|
+
}
|
54
|
+
return blocks;
|
55
|
+
}
|
56
|
+
/**
|
57
|
+
* Get the total offset of an element, including all its containing blocks.
|
58
|
+
*/
|
59
|
+
export function getPossiblyContainedPosition(element) {
|
60
|
+
const blocks = findAllContainingBlocks(element);
|
61
|
+
let left = 0;
|
62
|
+
let top = 0;
|
63
|
+
for (const block of blocks) {
|
64
|
+
const rect = block.getBoundingClientRect();
|
65
|
+
console.log('block', block, rect);
|
66
|
+
left += rect.left;
|
67
|
+
top += rect.top;
|
68
|
+
}
|
69
|
+
return {
|
70
|
+
left: left,
|
71
|
+
top: top
|
72
|
+
};
|
73
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
/**
|
2
|
+
* Get the next focusable element in the specified direction
|
3
|
+
* @param {HTMLElement | null} startingElement - The element to start the search from.
|
4
|
+
* @param {HTMLElement | null} triggerElement - The element that triggered the search.
|
5
|
+
* @param {string} direction - The direction to search in, either 'next' or 'previous'.
|
6
|
+
* @returns {HTMLElement | null} - The next focusable element, or null if none are found.
|
7
|
+
*/
|
8
|
+
export declare function getNextFocusableElement(startingElement: HTMLElement | null, triggerElement: HTMLElement | null, direction: "next" | "previous"): HTMLElement | null;
|
9
|
+
/**
|
10
|
+
* Navigate through a list of elements using the arrow keys
|
11
|
+
* @param {HTMLElement | null} el - The element containing the list of elements to navigate.
|
12
|
+
* @param {string} direction - The direction to navigate in, either 'up' or 'down'.
|
13
|
+
* @returns {void}
|
14
|
+
*/
|
15
|
+
export declare function arrowNavigation(el: HTMLElement | null, direction: "up" | "down"): void;
|
16
|
+
/**
|
17
|
+
* Match the key pressed to the first letter of an element in the list
|
18
|
+
* @param {HTMLElement | null} el - The element containing the list of elements to search through.
|
19
|
+
* @param {KeyboardEvent} e - The keyboard event to match the key from.
|
20
|
+
* @returns {void}
|
21
|
+
*/
|
22
|
+
export declare function matchOnType(el: HTMLElement | null, e: KeyboardEvent): void;
|
@@ -0,0 +1,161 @@
|
|
1
|
+
const ignoreKeys = ['backspace', 'tab', 'shift', 'control', 'alt', 'meta', 'arrowup', 'arrowdown', 'arrowleft', 'arrowright', 'home', 'end', 'pageup', 'pagedown', 'escape', 'capslock', 'numlock', 'scrolllock', 'pause', 'contextmenu', 'printscreen', 'help', 'clear', 'os', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f12', 'insert', 'delete', 'enter', 'space', 'escape'];
|
2
|
+
let keyMemory = $state("");
|
3
|
+
let timeout;
|
4
|
+
/**
|
5
|
+
* Get the next focusable element in the specified direction
|
6
|
+
* @param {HTMLElement | null} startingElement - The element to start the search from.
|
7
|
+
* @param {HTMLElement | null} triggerElement - The element that triggered the search.
|
8
|
+
* @param {string} direction - The direction to search in, either 'next' or 'previous'.
|
9
|
+
* @returns {HTMLElement | null} - The next focusable element, or null if none are found.
|
10
|
+
*/
|
11
|
+
export function getNextFocusableElement(startingElement, triggerElement, direction) {
|
12
|
+
if (!startingElement)
|
13
|
+
return null;
|
14
|
+
// Define selectors for focusable elements
|
15
|
+
const focusableSelectors = 'a, button, textarea, input, select';
|
16
|
+
// Get all focusable elements
|
17
|
+
const allFocusableElements = Array.from(document.querySelectorAll(focusableSelectors)).filter(el => !el.disabled && el.tabIndex >= 0);
|
18
|
+
console.log('allFocusableElements', allFocusableElements);
|
19
|
+
// Filter out elements inside the startingElement
|
20
|
+
const focusableElements = allFocusableElements.filter(el => !triggerElement.contains(el) && el !== startingElement);
|
21
|
+
console.log('focusableElements', focusableElements);
|
22
|
+
// Find the index of the starting element among all focusable elements
|
23
|
+
let startingIndex = allFocusableElements.indexOf(triggerElement);
|
24
|
+
console.log('startingIndex', startingIndex);
|
25
|
+
if (startingIndex === -1) {
|
26
|
+
// If the starting element is not focusable or not found, consider the document's active element if it's focusable.
|
27
|
+
const activeElement = document.activeElement;
|
28
|
+
if (activeElement && allFocusableElements.includes(activeElement)) {
|
29
|
+
startingIndex = allFocusableElements.indexOf(activeElement);
|
30
|
+
}
|
31
|
+
else {
|
32
|
+
// No valid starting point found
|
33
|
+
return null;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
// Determine the next index based on the direction, skipping children of startingElement
|
37
|
+
let nextIndex = -1;
|
38
|
+
if (direction === 'next') {
|
39
|
+
for (let i = startingIndex + 1; i < allFocusableElements.length; i++) {
|
40
|
+
if (focusableElements.includes(allFocusableElements[i])) {
|
41
|
+
nextIndex = focusableElements.indexOf(allFocusableElements[i]);
|
42
|
+
break;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
else {
|
47
|
+
for (let i = startingIndex - 1; i >= 0; i--) {
|
48
|
+
if (focusableElements.includes(allFocusableElements[i])) {
|
49
|
+
nextIndex = focusableElements.indexOf(allFocusableElements[i]);
|
50
|
+
break;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
// If no next element is found in the direction, wrap around.
|
55
|
+
if (nextIndex === -1) {
|
56
|
+
if (direction === 'next') {
|
57
|
+
// Wrap to the first element in the list
|
58
|
+
nextIndex = 0;
|
59
|
+
}
|
60
|
+
else {
|
61
|
+
// Wrap to the last element in the list
|
62
|
+
nextIndex = focusableElements.length - 1;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
// Return the next focusable element, or null if none are found
|
66
|
+
return focusableElements[nextIndex] || null;
|
67
|
+
}
|
68
|
+
/**
|
69
|
+
* Navigate through a list of elements using the arrow keys
|
70
|
+
* @param {HTMLElement | null} el - The element containing the list of elements to navigate.
|
71
|
+
* @param {string} direction - The direction to navigate in, either 'up' or 'down'.
|
72
|
+
* @returns {void}
|
73
|
+
*/
|
74
|
+
export function arrowNavigation(el, direction) {
|
75
|
+
const items = el?.querySelectorAll("a, button, input, textarea, select");
|
76
|
+
if (!items)
|
77
|
+
return;
|
78
|
+
const index = Array.from(items).findIndex((el) => el === document.activeElement);
|
79
|
+
if (index === -1) {
|
80
|
+
items[0].focus();
|
81
|
+
}
|
82
|
+
else if (direction === "down" && index < items.length - 1) {
|
83
|
+
items[index + 1].focus();
|
84
|
+
}
|
85
|
+
else if (direction === "up" && index > 0) {
|
86
|
+
items[index - 1].focus();
|
87
|
+
}
|
88
|
+
}
|
89
|
+
/**
|
90
|
+
* Match the key pressed to the first letter of an element in the list
|
91
|
+
* @param {HTMLElement | null} el - The element containing the list of elements to search through.
|
92
|
+
* @param {KeyboardEvent} e - The keyboard event to match the key from.
|
93
|
+
* @returns {void}
|
94
|
+
*/
|
95
|
+
export function matchOnType(el, e) {
|
96
|
+
const items = el?.querySelectorAll("a, button, label");
|
97
|
+
if (!items)
|
98
|
+
return;
|
99
|
+
const index = Array.from(items).findIndex((el) => el === document.activeElement);
|
100
|
+
const search = e.key.toLowerCase();
|
101
|
+
if (timeout)
|
102
|
+
clearTimeout(timeout);
|
103
|
+
function removeUls() {
|
104
|
+
items.forEach((el) => {
|
105
|
+
const contentEl = el.querySelector("span.Content") ?? el.querySelector("label");
|
106
|
+
if (contentEl) {
|
107
|
+
contentEl.innerHTML = contentEl.textContent;
|
108
|
+
}
|
109
|
+
});
|
110
|
+
}
|
111
|
+
const currentlyFocusedElement = document.activeElement;
|
112
|
+
// return if the key is not a letter or number
|
113
|
+
if (ignoreKeys.includes(search) || currentlyFocusedElement.tagName === "INPUT" || currentlyFocusedElement.tagName === "TEXTAREA") {
|
114
|
+
keyMemory = '';
|
115
|
+
removeUls();
|
116
|
+
return;
|
117
|
+
}
|
118
|
+
// clear the memory after 1 second
|
119
|
+
timeout = setTimeout(() => {
|
120
|
+
keyMemory = '';
|
121
|
+
removeUls();
|
122
|
+
}, 1000);
|
123
|
+
// only add if the last key is not the same
|
124
|
+
if (keyMemory[keyMemory.length - 1] !== search) {
|
125
|
+
keyMemory += search;
|
126
|
+
}
|
127
|
+
const matches = Array.from(items).filter((el) => {
|
128
|
+
const text = el.textContent?.trim().toLowerCase();
|
129
|
+
return text?.startsWith(keyMemory);
|
130
|
+
});
|
131
|
+
if (matches.length) {
|
132
|
+
// add underline to the matched text
|
133
|
+
matches.forEach((el) => {
|
134
|
+
const contentEl = el.querySelector("span.Content, label");
|
135
|
+
const text = contentEl?.textContent?.trim();
|
136
|
+
if (text) {
|
137
|
+
const index = text.toLowerCase().indexOf(keyMemory);
|
138
|
+
const first = text.slice(0, index);
|
139
|
+
const last = text.slice(index + keyMemory.length);
|
140
|
+
const middle = text.slice(index, index + keyMemory.length);
|
141
|
+
contentEl.innerHTML = `${first}<u>${middle}</u>${last}`;
|
142
|
+
}
|
143
|
+
});
|
144
|
+
if (keyMemory.length === 1) {
|
145
|
+
// if this element is already focused, move to the next one if memory is at one character
|
146
|
+
const nextMatches = Array.from(items).filter((el) => el.textContent?.trim().toLowerCase().startsWith(keyMemory));
|
147
|
+
if (nextMatches) {
|
148
|
+
const nextIndex = nextMatches.findIndex((el) => el === document.activeElement);
|
149
|
+
if (nextIndex < nextMatches.length - 1) {
|
150
|
+
nextMatches[nextIndex + 1].focus();
|
151
|
+
}
|
152
|
+
else {
|
153
|
+
nextMatches[0].focus();
|
154
|
+
}
|
155
|
+
}
|
156
|
+
}
|
157
|
+
else {
|
158
|
+
matches[0].focus();
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare function getLocaleFirstDayOfWeek(): number;
|
@@ -0,0 +1,47 @@
|
|
1
|
+
export function getLocaleFirstDayOfWeek() {
|
2
|
+
// Returns 0..6 where 0 = Sunday, 1 = Monday
|
3
|
+
try {
|
4
|
+
if (typeof Intl !== "undefined") {
|
5
|
+
const LocaleCtor = Intl.Locale;
|
6
|
+
const resolvedLocale = typeof Intl.DateTimeFormat !== "undefined"
|
7
|
+
? new Intl.DateTimeFormat(undefined).resolvedOptions().locale
|
8
|
+
: undefined;
|
9
|
+
const navigatorPrimary = (typeof navigator !== "undefined" && navigator.languages && navigator.languages.length > 0)
|
10
|
+
? navigator.languages[0]
|
11
|
+
: (typeof navigator !== "undefined" ? navigator.language : undefined);
|
12
|
+
const languageTag = resolvedLocale || navigatorPrimary || "en-US";
|
13
|
+
let region;
|
14
|
+
let first;
|
15
|
+
if (LocaleCtor) {
|
16
|
+
const loc = new LocaleCtor(languageTag);
|
17
|
+
region = loc.region;
|
18
|
+
const weekInfo = loc.weekInfo;
|
19
|
+
if (weekInfo && typeof weekInfo.firstDay === "number") {
|
20
|
+
// Intl Locale weekInfo uses 1..7 where 1 = Monday, 7 = Sunday
|
21
|
+
first = weekInfo.firstDay % 7; // 7 -> 0 (Sunday), 1 -> 1 (Monday)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
else {
|
25
|
+
// Try to infer region from language tag
|
26
|
+
const parts = typeof languageTag === "string" ? languageTag.split("-") : [];
|
27
|
+
region = parts.length >= 2 ? parts[1] : undefined;
|
28
|
+
}
|
29
|
+
const tz = typeof Intl.DateTimeFormat !== "undefined"
|
30
|
+
? new Intl.DateTimeFormat(undefined).resolvedOptions().timeZone
|
31
|
+
: undefined;
|
32
|
+
// If first is explicitly Sunday but timezone/region suggests Europe, prefer Monday
|
33
|
+
if (first === 0 && ((tz && typeof tz === "string" && tz.startsWith("Europe/")) || (region && region !== "US"))) {
|
34
|
+
return 1;
|
35
|
+
}
|
36
|
+
if (typeof first === "number")
|
37
|
+
return first;
|
38
|
+
if (region === "US")
|
39
|
+
return 0; // Sunday for US
|
40
|
+
if (tz && typeof tz === "string" && tz.startsWith("Europe/"))
|
41
|
+
return 1; // Monday for Europe
|
42
|
+
return 1; // default Monday for others
|
43
|
+
}
|
44
|
+
}
|
45
|
+
catch { }
|
46
|
+
return 1; // fallback Monday
|
47
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "lutra",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.4",
|
4
4
|
"scripts": {
|
5
5
|
"dev": "vite dev",
|
6
6
|
"build": "vite build && npm run package",
|
@@ -25,25 +25,34 @@
|
|
25
25
|
".": {
|
26
26
|
"types": "./dist/index.d.ts",
|
27
27
|
"svelte": "./dist/index.js"
|
28
|
+
},
|
29
|
+
"./themes/DefaultTheme.css": {
|
30
|
+
"import": "./dist/css/themes/DefaultTheme.css",
|
31
|
+
"require": "./dist/css/themes/DefaultTheme.css"
|
28
32
|
}
|
29
33
|
},
|
30
34
|
"peerDependencies": {
|
31
35
|
"svelte": "^5.0.0"
|
32
36
|
},
|
33
37
|
"devDependencies": {
|
34
|
-
"@sveltejs/adapter-auto": "^6.0
|
35
|
-
"@sveltejs/kit": "^2.
|
36
|
-
"@sveltejs/package": "^2.
|
37
|
-
"@sveltejs/vite-plugin-svelte": "^
|
38
|
-
"@types/node": "^
|
38
|
+
"@sveltejs/adapter-auto": "^6.1.0",
|
39
|
+
"@sveltejs/kit": "^2.31.1",
|
40
|
+
"@sveltejs/package": "^2.4.1",
|
41
|
+
"@sveltejs/vite-plugin-svelte": "^6.1.2",
|
42
|
+
"@types/node": "^24.3.0",
|
39
43
|
"publint": "^0.3.12",
|
40
|
-
"svelte": "^5.
|
41
|
-
"svelte-check": "^4.
|
42
|
-
"typescript": "^5.
|
43
|
-
"vite": "^
|
44
|
+
"svelte": "^5.38.1",
|
45
|
+
"svelte-check": "^4.3.1",
|
46
|
+
"typescript": "^5.9.2",
|
47
|
+
"vite": "^7.1.2"
|
44
48
|
},
|
45
49
|
"dependencies": {
|
50
|
+
"@auth70/bodyguard": "^1.7.1",
|
51
|
+
"blurhash": "^2.0.5",
|
52
|
+
"browser-image-compression": "^2.0.2",
|
46
53
|
"esm-env": "^1.2.2",
|
47
|
-
"marked": "
|
54
|
+
"marked": "16.2.0",
|
55
|
+
"zod": "^4.0.17",
|
56
|
+
"zodex": "^4.0.0"
|
48
57
|
}
|
49
58
|
}
|
package/dist/css/0-layers.css
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
@layer l-base, base-overrides, l-typo, typo-overrides, l-layout, layout-overrides, l-media, media-overrides, misc;
|