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.
Files changed (152) hide show
  1. package/dist/components/Avatar.svelte +105 -0
  2. package/dist/components/Avatar.svelte.d.ts +14 -0
  3. package/dist/components/Close.svelte +76 -0
  4. package/dist/components/Close.svelte.d.ts +7 -0
  5. package/dist/components/ContextTip.svelte +41 -0
  6. package/dist/components/ContextTip.svelte.d.ts +7 -0
  7. package/dist/components/Icon.svelte +62 -0
  8. package/dist/components/Icon.svelte.d.ts +8 -0
  9. package/dist/components/IconButton.svelte +120 -0
  10. package/dist/components/IconButton.svelte.d.ts +16 -0
  11. package/dist/components/Image.svelte +172 -0
  12. package/dist/components/Image.svelte.d.ts +56 -0
  13. package/dist/components/Indicator.svelte +387 -0
  14. package/dist/components/Indicator.svelte.d.ts +12 -0
  15. package/dist/components/Inset.svelte +23 -0
  16. package/dist/components/Inset.svelte.d.ts +7 -0
  17. package/dist/components/Layout.svelte +2 -1
  18. package/dist/components/MenuDropdown.svelte +195 -0
  19. package/dist/components/MenuDropdown.svelte.d.ts +16 -0
  20. package/dist/components/MenuItem.svelte +155 -0
  21. package/dist/components/MenuItem.svelte.d.ts +11 -0
  22. package/dist/components/MenuItemContent.svelte +25 -0
  23. package/dist/components/MenuItemContent.svelte.d.ts +10 -0
  24. package/dist/components/MenuTypes.d.ts +72 -0
  25. package/dist/components/MenuTypes.js +1 -0
  26. package/dist/components/Modal.svelte +149 -0
  27. package/dist/components/Modal.svelte.d.ts +16 -0
  28. package/dist/components/Notification.svelte +115 -0
  29. package/dist/components/Notification.svelte.d.ts +12 -0
  30. package/dist/components/Overlay.svelte +31 -0
  31. package/dist/components/Overlay.svelte.d.ts +14 -0
  32. package/dist/components/OverlayContainer.svelte +31 -0
  33. package/dist/components/OverlayContainer.svelte.d.ts +18 -0
  34. package/dist/components/OverlayLayer.svelte +168 -0
  35. package/dist/components/OverlayLayer.svelte.d.ts +8 -0
  36. package/dist/components/TabbedContent.svelte +74 -0
  37. package/dist/components/TabbedContent.svelte.d.ts +11 -0
  38. package/dist/components/TabbedContentItem.svelte +33 -0
  39. package/dist/components/TabbedContentItem.svelte.d.ts +10 -0
  40. package/dist/components/Table.svelte +41 -0
  41. package/dist/components/Table.svelte.d.ts +13 -0
  42. package/dist/components/Tabs.svelte +216 -0
  43. package/dist/components/Tabs.svelte.d.ts +20 -0
  44. package/dist/components/Tag.svelte +120 -0
  45. package/dist/components/Tag.svelte.d.ts +21 -0
  46. package/dist/components/Theme.svelte +32 -14
  47. package/dist/components/Tooltip.svelte +8 -8
  48. package/dist/components/UIContent.svelte +19 -0
  49. package/dist/components/UIContent.svelte.d.ts +7 -0
  50. package/dist/components/index.d.ts +28 -0
  51. package/dist/components/index.js +29 -0
  52. package/dist/components/notifications.svelte.d.ts +21 -0
  53. package/dist/components/notifications.svelte.js +30 -0
  54. package/dist/components/overlays.svelte.d.ts +36 -0
  55. package/dist/components/overlays.svelte.js +44 -0
  56. package/dist/css/1-props.css +389 -724
  57. package/dist/css/2-base.css +257 -123
  58. package/dist/css/3-typo.css +75 -34
  59. package/dist/css/4-layout.css +364 -1
  60. package/dist/css/5-media.css +106 -11
  61. package/dist/css/lutra.css +2 -1
  62. package/dist/css/themes/DefaultTheme.css +209 -0
  63. package/dist/form/Button.svelte +58 -0
  64. package/dist/form/Button.svelte.d.ts +15 -0
  65. package/dist/form/Datepicker.svelte +311 -0
  66. package/dist/form/Datepicker.svelte.d.ts +9 -0
  67. package/dist/form/FieldContent.svelte +178 -0
  68. package/dist/form/FieldContent.svelte.d.ts +21 -0
  69. package/dist/form/FieldError.svelte +24 -0
  70. package/dist/form/FieldError.svelte.d.ts +7 -0
  71. package/dist/form/Fieldset.svelte +103 -0
  72. package/dist/form/Fieldset.svelte.d.ts +20 -0
  73. package/dist/form/Form.svelte +220 -0
  74. package/dist/form/Form.svelte.d.ts +38 -0
  75. package/dist/form/FormActions.svelte +80 -0
  76. package/dist/form/FormActions.svelte.d.ts +9 -0
  77. package/dist/form/FormSection.svelte +96 -0
  78. package/dist/form/FormSection.svelte.d.ts +9 -0
  79. package/dist/form/ImageUpload.svelte +299 -0
  80. package/dist/form/ImageUpload.svelte.d.ts +20 -0
  81. package/dist/form/Input.svelte +444 -0
  82. package/dist/form/Input.svelte.d.ts +108 -0
  83. package/dist/form/InputLength.svelte +42 -0
  84. package/dist/form/InputLength.svelte.d.ts +9 -0
  85. package/dist/form/Label.svelte +88 -0
  86. package/dist/form/Label.svelte.d.ts +16 -0
  87. package/dist/form/LogoUpload.svelte +115 -0
  88. package/dist/form/LogoUpload.svelte.d.ts +18 -0
  89. package/dist/form/Select.svelte +186 -0
  90. package/dist/form/Select.svelte.d.ts +59 -0
  91. package/dist/form/Textarea.svelte +265 -0
  92. package/dist/form/Textarea.svelte.d.ts +95 -0
  93. package/dist/form/Toggle.svelte +4 -0
  94. package/dist/form/Toggle.svelte.d.ts +18 -0
  95. package/dist/form/client.svelte.d.ts +45 -0
  96. package/dist/form/client.svelte.js +102 -0
  97. package/dist/form/form.d.ts +55 -0
  98. package/dist/form/form.js +345 -0
  99. package/dist/form/index.d.ts +17 -0
  100. package/dist/form/index.js +17 -0
  101. package/dist/form/types.d.ts +55 -0
  102. package/dist/form/types.js +1 -0
  103. package/dist/icons/IconAlert.svelte +3 -0
  104. package/dist/icons/IconAlert.svelte.d.ts +26 -0
  105. package/dist/icons/IconCopy.svelte +3 -0
  106. package/dist/icons/IconCopy.svelte.d.ts +26 -0
  107. package/dist/icons/IconDone.svelte +3 -0
  108. package/dist/icons/IconDone.svelte.d.ts +26 -0
  109. package/dist/icons/IconError.svelte +3 -0
  110. package/dist/icons/IconError.svelte.d.ts +26 -0
  111. package/dist/icons/IconHelp.svelte +3 -0
  112. package/dist/icons/IconHelp.svelte.d.ts +26 -0
  113. package/dist/icons/IconHide.svelte +3 -0
  114. package/dist/icons/IconHide.svelte.d.ts +26 -0
  115. package/dist/icons/IconInfo.svelte +3 -0
  116. package/dist/icons/IconInfo.svelte.d.ts +26 -0
  117. package/dist/icons/IconLink.svelte +3 -0
  118. package/dist/icons/IconLink.svelte.d.ts +26 -0
  119. package/dist/icons/IconMenuBurger.svelte +3 -0
  120. package/dist/icons/IconMenuBurger.svelte.d.ts +26 -0
  121. package/dist/icons/IconMenuDots.svelte +3 -0
  122. package/dist/icons/IconMenuDots.svelte.d.ts +26 -0
  123. package/dist/icons/IconSearch.svelte +3 -0
  124. package/dist/icons/IconSearch.svelte.d.ts +26 -0
  125. package/dist/icons/IconShow.svelte +3 -0
  126. package/dist/icons/IconShow.svelte.d.ts +26 -0
  127. package/dist/icons/IconSuccess.svelte +3 -0
  128. package/dist/icons/IconSuccess.svelte.d.ts +26 -0
  129. package/dist/icons/IconWarning.svelte +3 -0
  130. package/dist/icons/IconWarning.svelte.d.ts +26 -0
  131. package/dist/icons/index.d.ts +14 -0
  132. package/dist/icons/index.js +14 -0
  133. package/dist/index.d.ts +3 -5
  134. package/dist/index.js +3 -5
  135. package/dist/util/StringOrComponent.svelte +20 -0
  136. package/dist/util/StringOrComponent.svelte.d.ts +8 -0
  137. package/dist/util/StringOrSnippet.svelte +16 -0
  138. package/dist/util/StringOrSnippet.svelte.d.ts +8 -0
  139. package/dist/util/attr.d.ts +5 -0
  140. package/dist/util/attr.js +21 -0
  141. package/dist/util/color.d.ts +51 -0
  142. package/dist/util/color.js +97 -0
  143. package/dist/util/dom.d.ts +15 -0
  144. package/dist/util/dom.js +73 -0
  145. package/dist/util/keyboard.svelte.d.ts +22 -0
  146. package/dist/util/keyboard.svelte.js +161 -0
  147. package/dist/util/locale.d.ts +1 -0
  148. package/dist/util/locale.js +47 -0
  149. package/dist/util/settings.d.ts +4 -0
  150. package/dist/util/settings.js +1 -0
  151. package/package.json +20 -11
  152. 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
+ };
@@ -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,4 @@
1
+ export type LutraSettings = {
2
+ contained: boolean;
3
+ rounded: boolean;
4
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lutra",
3
- "version": "0.1.0",
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.1",
35
- "@sveltejs/kit": "^2.21.1",
36
- "@sveltejs/package": "^2.3.11",
37
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
38
- "@types/node": "^22.15.24",
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.33.9",
41
- "svelte-check": "^4.2.1",
42
- "typescript": "^5.8.3",
43
- "vite": "^6.3.5"
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": "^15.0.12"
54
+ "marked": "16.2.0",
55
+ "zod": "^4.0.17",
56
+ "zodex": "^4.0.0"
48
57
  }
49
58
  }
@@ -1 +0,0 @@
1
- @layer l-base, base-overrides, l-typo, typo-overrides, l-layout, layout-overrides, l-media, media-overrides, misc;