@ims360/svelte-ivory 0.0.2

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 (128) hide show
  1. package/LICENCE +23 -0
  2. package/README.md +15 -0
  3. package/dist/components/ai/AiMessage.svelte +115 -0
  4. package/dist/components/ai/AiMessage.svelte.d.ts +18 -0
  5. package/dist/components/ai/AttachedFile.svelte +28 -0
  6. package/dist/components/ai/AttachedFile.svelte.d.ts +9 -0
  7. package/dist/components/ai/Chat.svelte +150 -0
  8. package/dist/components/ai/Chat.svelte.d.ts +40 -0
  9. package/dist/components/ai/Markdown.svelte +59 -0
  10. package/dist/components/ai/Markdown.svelte.d.ts +16 -0
  11. package/dist/components/ai/UserMessage.svelte +53 -0
  12. package/dist/components/ai/UserMessage.svelte.d.ts +16 -0
  13. package/dist/components/ai/index.d.ts +5 -0
  14. package/dist/components/ai/index.js +5 -0
  15. package/dist/components/basic/checkbox/Checkbox.svelte +79 -0
  16. package/dist/components/basic/checkbox/Checkbox.svelte.d.ts +18 -0
  17. package/dist/components/basic/index.d.ts +2 -0
  18. package/dist/components/basic/index.js +2 -0
  19. package/dist/components/basic/toggle/Toggle.svelte +47 -0
  20. package/dist/components/basic/toggle/Toggle.svelte.d.ts +12 -0
  21. package/dist/components/buttons/CopyToClipboardButton.svelte +33 -0
  22. package/dist/components/buttons/CopyToClipboardButton.svelte.d.ts +9 -0
  23. package/dist/components/index.d.ts +0 -0
  24. package/dist/components/index.js +1 -0
  25. package/dist/components/layout/heading/Heading.svelte +33 -0
  26. package/dist/components/layout/heading/Heading.svelte.d.ts +16 -0
  27. package/dist/components/layout/heading/index.d.ts +5 -0
  28. package/dist/components/layout/heading/index.js +5 -0
  29. package/dist/components/layout/hiddenBackground/HiddenBackground.svelte +48 -0
  30. package/dist/components/layout/hiddenBackground/HiddenBackground.svelte.d.ts +13 -0
  31. package/dist/components/layout/hiddenBackground/index.js +6 -0
  32. package/dist/components/layout/index.d.ts +7 -0
  33. package/dist/components/layout/index.js +7 -0
  34. package/dist/components/layout/modal/Modal.svelte +114 -0
  35. package/dist/components/layout/modal/Modal.svelte.d.ts +30 -0
  36. package/dist/components/layout/modal/ModalTest.svelte +16 -0
  37. package/dist/components/layout/modal/ModalTest.svelte.d.ts +9 -0
  38. package/dist/components/layout/popover/Popover.svelte +108 -0
  39. package/dist/components/layout/popover/Popover.svelte.d.ts +36 -0
  40. package/dist/components/layout/portal/Portal.svelte +23 -0
  41. package/dist/components/layout/portal/Portal.svelte.d.ts +15 -0
  42. package/dist/components/layout/tabs/Tab.svelte +80 -0
  43. package/dist/components/layout/tabs/Tab.svelte.d.ts +21 -0
  44. package/dist/components/layout/tabs/TabPanel.svelte +23 -0
  45. package/dist/components/layout/tabs/TabPanel.svelte.d.ts +10 -0
  46. package/dist/components/layout/tabs/Tabs.svelte +86 -0
  47. package/dist/components/layout/tabs/Tabs.svelte.d.ts +21 -0
  48. package/dist/components/layout/tabs/index.d.ts +26 -0
  49. package/dist/components/layout/tabs/index.js +8 -0
  50. package/dist/components/layout/tooltip/Tooltip.svelte +111 -0
  51. package/dist/components/layout/tooltip/Tooltip.svelte.d.ts +32 -0
  52. package/dist/components/toast/Toast.svelte +100 -0
  53. package/dist/components/toast/Toast.svelte.d.ts +16 -0
  54. package/dist/components/toast/index.d.ts +2 -0
  55. package/dist/components/toast/index.js +2 -0
  56. package/dist/components/toast/toasts.svelte.d.ts +26 -0
  57. package/dist/components/toast/toasts.svelte.js +67 -0
  58. package/dist/index.d.ts +0 -0
  59. package/dist/index.js +2 -0
  60. package/dist/utils/actions/clickOutside.d.ts +11 -0
  61. package/dist/utils/actions/clickOutside.js +23 -0
  62. package/dist/utils/actions/focusTrap.d.ts +4 -0
  63. package/dist/utils/actions/focusTrap.js +64 -0
  64. package/dist/utils/actions/index.d.ts +5 -0
  65. package/dist/utils/actions/index.js +5 -0
  66. package/dist/utils/actions/portal.d.ts +9 -0
  67. package/dist/utils/actions/portal.js +39 -0
  68. package/dist/utils/actions/shortcut.d.ts +10 -0
  69. package/dist/utils/actions/shortcut.js +25 -0
  70. package/dist/utils/actions/visible.d.ts +5 -0
  71. package/dist/utils/actions/visible.js +14 -0
  72. package/dist/utils/functions/cookie.d.ts +12 -0
  73. package/dist/utils/functions/cookie.js +36 -0
  74. package/dist/utils/functions/index.d.ts +3 -0
  75. package/dist/utils/functions/index.js +3 -0
  76. package/dist/utils/functions/pseudoRandomId.d.ts +1 -0
  77. package/dist/utils/functions/pseudoRandomId.js +3 -0
  78. package/dist/utils/functions/queryParams.d.ts +1 -0
  79. package/dist/utils/functions/queryParams.js +14 -0
  80. package/package.json +107 -0
  81. package/src/lib/components/ai/AiMessage.svelte +115 -0
  82. package/src/lib/components/ai/AttachedFile.svelte +28 -0
  83. package/src/lib/components/ai/Chat.svelte +150 -0
  84. package/src/lib/components/ai/Markdown.svelte +59 -0
  85. package/src/lib/components/ai/UserMessage.svelte +53 -0
  86. package/src/lib/components/ai/index.ts +5 -0
  87. package/src/lib/components/basic/checkbox/Checkbox.svelte +79 -0
  88. package/src/lib/components/basic/checkbox/checkbox.svelte.spec.ts +39 -0
  89. package/src/lib/components/basic/index.ts +2 -0
  90. package/src/lib/components/basic/toggle/Toggle.svelte +47 -0
  91. package/src/lib/components/basic/toggle/toggle.svelte.spec.ts +19 -0
  92. package/src/lib/components/buttons/CopyToClipboardButton.svelte +33 -0
  93. package/src/lib/components/index.ts +0 -0
  94. package/src/lib/components/layout/heading/Heading.svelte +33 -0
  95. package/src/lib/components/layout/heading/index.ts +7 -0
  96. package/src/lib/components/layout/hiddenBackground/HiddenBackground.svelte +48 -0
  97. package/src/lib/components/layout/hiddenBackground/index.ts +8 -0
  98. package/src/lib/components/layout/index.ts +7 -0
  99. package/src/lib/components/layout/modal/Modal.svelte +114 -0
  100. package/src/lib/components/layout/modal/ModalTest.svelte +16 -0
  101. package/src/lib/components/layout/modal/modal.svelte.spec.ts +39 -0
  102. package/src/lib/components/layout/popover/Popover.svelte +108 -0
  103. package/src/lib/components/layout/portal/Portal.svelte +23 -0
  104. package/src/lib/components/layout/tabs/Tab.svelte +80 -0
  105. package/src/lib/components/layout/tabs/TabPanel.svelte +23 -0
  106. package/src/lib/components/layout/tabs/Tabs.svelte +86 -0
  107. package/src/lib/components/layout/tabs/Tabs.test.svelte +5 -0
  108. package/src/lib/components/layout/tabs/index.ts +10 -0
  109. package/src/lib/components/layout/tooltip/Tooltip.svelte +111 -0
  110. package/src/lib/components/toast/Toast.svelte +100 -0
  111. package/src/lib/components/toast/index.ts +2 -0
  112. package/src/lib/components/toast/toasts.svelte.ts +89 -0
  113. package/src/lib/index.ts +1 -0
  114. package/src/lib/utils/actions/clickOutside.svelte.spec.ts +67 -0
  115. package/src/lib/utils/actions/clickOutside.ts +38 -0
  116. package/src/lib/utils/actions/focusTrap.ts +65 -0
  117. package/src/lib/utils/actions/index.ts +5 -0
  118. package/src/lib/utils/actions/portal.ts +43 -0
  119. package/src/lib/utils/actions/shortcut.svelte.spec.ts +19 -0
  120. package/src/lib/utils/actions/shortcut.ts +35 -0
  121. package/src/lib/utils/actions/visible.ts +28 -0
  122. package/src/lib/utils/functions/cookie.svelte.spec.ts +55 -0
  123. package/src/lib/utils/functions/cookie.ts +46 -0
  124. package/src/lib/utils/functions/index.ts +3 -0
  125. package/src/lib/utils/functions/pseudoRandomId.spec.ts +19 -0
  126. package/src/lib/utils/functions/pseudoRandomId.ts +4 -0
  127. package/src/lib/utils/functions/queryParams.spec.ts +25 -0
  128. package/src/lib/utils/functions/queryParams.ts +15 -0
@@ -0,0 +1,23 @@
1
+ /** Dispatch event on click outside of node */
2
+ export function clickOutside(node, params) {
3
+ function handleClick(event) {
4
+ if (!(event.target instanceof Node) ||
5
+ !node ||
6
+ node.contains(event.target) ||
7
+ event.defaultPrevented)
8
+ return;
9
+ if (typeof params === 'function') {
10
+ params(event);
11
+ return;
12
+ }
13
+ if (params.target?.contains(event.target))
14
+ return;
15
+ params.callback(event);
16
+ }
17
+ document.addEventListener('click', handleClick, true);
18
+ return {
19
+ destroy() {
20
+ document.removeEventListener('click', handleClick, true);
21
+ }
22
+ };
23
+ }
@@ -0,0 +1,4 @@
1
+ export declare function focusTrap(node: HTMLElement, enabled: boolean): {
2
+ update(newArgs: boolean): void;
3
+ destroy(): void;
4
+ };
@@ -0,0 +1,64 @@
1
+ export function focusTrap(node, enabled) {
2
+ const elementWhitelist = 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])';
3
+ let firstElement;
4
+ let lastElement;
5
+ function onFirstElemKeydown(e) {
6
+ if (e.shiftKey && e.code === 'Tab') {
7
+ e.preventDefault();
8
+ lastElement.focus();
9
+ }
10
+ }
11
+ function onLastElemKeydown(e) {
12
+ if (!e.shiftKey && e.code === 'Tab') {
13
+ e.preventDefault();
14
+ firstElement.focus();
15
+ }
16
+ }
17
+ function scanElements(fromObserver) {
18
+ if (enabled === false)
19
+ return;
20
+ const focusableElems = Array.from(node.querySelectorAll(elementWhitelist));
21
+ if (focusableElems.length) {
22
+ // Set first/last focusable elements
23
+ firstElement = focusableElems[0];
24
+ lastElement = focusableElems[focusableElems.length - 1];
25
+ // Auto-focus first focusable element only when not called from mutation observer
26
+ if (!fromObserver)
27
+ firstElement.focus();
28
+ // Listen for keydown on first & last element
29
+ firstElement.addEventListener('keydown', onFirstElemKeydown);
30
+ lastElement.addEventListener('keydown', onLastElemKeydown);
31
+ }
32
+ }
33
+ scanElements(false);
34
+ function cleanUp() {
35
+ if (firstElement)
36
+ firstElement.removeEventListener('keydown', onFirstElemKeydown);
37
+ if (lastElement)
38
+ lastElement.removeEventListener('keydown', onLastElemKeydown);
39
+ }
40
+ // Re-determine focusable elements when mutations are observed
41
+ const onObservationChange = (mutationRecords, observer) => {
42
+ if (mutationRecords.length) {
43
+ cleanUp();
44
+ scanElements(true);
45
+ }
46
+ return observer;
47
+ };
48
+ const observer = new MutationObserver(onObservationChange);
49
+ observer.observe(node, { childList: true, subtree: true });
50
+ // Lifecycle
51
+ return {
52
+ update(newArgs) {
53
+ enabled = newArgs;
54
+ if (newArgs)
55
+ scanElements(false);
56
+ else
57
+ cleanUp();
58
+ },
59
+ destroy() {
60
+ cleanUp();
61
+ observer.disconnect();
62
+ }
63
+ };
64
+ }
@@ -0,0 +1,5 @@
1
+ export { clickOutside } from './clickOutside';
2
+ export { focusTrap } from './focusTrap';
3
+ export { portal } from './portal';
4
+ export { shortcut } from './shortcut';
5
+ export { onFirstVisible } from './visible';
@@ -0,0 +1,5 @@
1
+ export { clickOutside } from './clickOutside';
2
+ export { focusTrap } from './focusTrap';
3
+ export { portal } from './portal';
4
+ export { shortcut } from './shortcut';
5
+ export { onFirstVisible } from './visible';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Places the element in a diffrent place in the DOM
3
+ *
4
+ * **Use sparingy as it can make the DOM structure confusing**
5
+ */
6
+ export declare function portal(el: HTMLElement, target?: HTMLElement | string): {
7
+ update: (newTarget: HTMLElement | string) => Promise<void>;
8
+ destroy: () => void;
9
+ };
@@ -0,0 +1,39 @@
1
+ import { tick } from 'svelte';
2
+ /**
3
+ * Places the element in a diffrent place in the DOM
4
+ *
5
+ * **Use sparingy as it can make the DOM structure confusing**
6
+ */
7
+ export function portal(el, target = 'body') {
8
+ let targetEl;
9
+ async function update(newTarget) {
10
+ target = newTarget;
11
+ if (typeof target === 'string') {
12
+ targetEl = document.querySelector(target);
13
+ if (targetEl === null) {
14
+ await tick();
15
+ targetEl = document.querySelector(target);
16
+ }
17
+ if (targetEl === null) {
18
+ throw new Error(`No element found matching css selector: "${target}"`);
19
+ }
20
+ }
21
+ else if (target instanceof HTMLElement) {
22
+ targetEl = target;
23
+ }
24
+ else {
25
+ throw new TypeError(`Unknown portal target type: ${target === null ? 'null' : typeof target}. Allowed types: string (CSS selector) or HTMLElement.`);
26
+ }
27
+ targetEl.appendChild(el);
28
+ el.hidden = false;
29
+ }
30
+ function destroy() {
31
+ if (el.parentNode)
32
+ el.parentNode.removeChild(el);
33
+ }
34
+ update(target);
35
+ return {
36
+ update,
37
+ destroy
38
+ };
39
+ }
@@ -0,0 +1,10 @@
1
+ export declare const shortcut: (node: HTMLElement, params?: {
2
+ alt?: boolean;
3
+ shift?: boolean;
4
+ control?: boolean;
5
+ code: string;
6
+ callback: () => void;
7
+ }) => {
8
+ update: () => void;
9
+ destroy: () => void;
10
+ } | undefined;
@@ -0,0 +1,25 @@
1
+ export const shortcut = (node, params) => {
2
+ if (!params)
3
+ return;
4
+ let handler;
5
+ const removeHandler = () => window.removeEventListener('keydown', handler);
6
+ const setHandler = () => {
7
+ removeHandler();
8
+ handler = (e) => {
9
+ if (!!params.alt != e.altKey ||
10
+ !!params.shift != e.shiftKey ||
11
+ !!params.control != (e.ctrlKey || e.metaKey) ||
12
+ params.code != e.code)
13
+ return;
14
+ e.preventDefault();
15
+ e.stopPropagation();
16
+ params.callback();
17
+ };
18
+ window.addEventListener('keydown', handler);
19
+ };
20
+ setHandler();
21
+ return {
22
+ update: setHandler,
23
+ destroy: removeHandler
24
+ };
25
+ };
@@ -0,0 +1,5 @@
1
+ import type { Action } from 'svelte/action';
2
+ export declare const onFirstVisible: Action<HTMLElement, {
3
+ callback: () => void;
4
+ options?: Partial<IntersectionObserverInit>;
5
+ }>;
@@ -0,0 +1,14 @@
1
+ export const onFirstVisible = (node, input) => {
2
+ const observer = new IntersectionObserver((e) => {
3
+ if (!e[0].isIntersecting) {
4
+ return;
5
+ }
6
+ input.callback();
7
+ }, { root: null, threshold: 1, ...input.options });
8
+ observer.observe(node);
9
+ return {
10
+ destroy: () => {
11
+ observer.disconnect();
12
+ }
13
+ };
14
+ };
@@ -0,0 +1,12 @@
1
+ declare function getCookie(name: string): string;
2
+ interface CookieSetParams {
3
+ name: string;
4
+ value: string;
5
+ days?: number;
6
+ }
7
+ declare function setCookie(params: CookieSetParams): string;
8
+ export declare const cookie: {
9
+ get: typeof getCookie;
10
+ set: typeof setCookie;
11
+ };
12
+ export {};
@@ -0,0 +1,36 @@
1
+ function getCookie(name) {
2
+ const cname = name + '=';
3
+ let decodedCookie;
4
+ try {
5
+ decodedCookie = decodeURIComponent(document.cookie);
6
+ }
7
+ catch (error) {
8
+ return '';
9
+ }
10
+ const ca = decodedCookie.split(';');
11
+ for (let c of ca) {
12
+ while (c.startsWith(' ')) {
13
+ c = c.substring(1);
14
+ }
15
+ if (c.startsWith(cname)) {
16
+ return c.substring(cname.length);
17
+ }
18
+ }
19
+ return '';
20
+ }
21
+ function setCookie(params) {
22
+ const { name, value, days } = params;
23
+ let expires = '';
24
+ if (days) {
25
+ const date = new Date();
26
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
27
+ expires = '; expires=' + date.toUTCString();
28
+ }
29
+ const cookie = name + '=' + (value || '') + expires + '; path=/';
30
+ document.cookie = cookie;
31
+ return cookie;
32
+ }
33
+ export const cookie = {
34
+ get: getCookie,
35
+ set: setCookie
36
+ };
@@ -0,0 +1,3 @@
1
+ export { cookie } from './cookie';
2
+ export { pseudoRandomId } from './pseudoRandomId';
3
+ export { queryParams } from './queryParams';
@@ -0,0 +1,3 @@
1
+ export { cookie } from './cookie';
2
+ export { pseudoRandomId } from './pseudoRandomId';
3
+ export { queryParams } from './queryParams';
@@ -0,0 +1 @@
1
+ export declare const pseudoRandomId: (prefix?: string) => string;
@@ -0,0 +1,3 @@
1
+ export const pseudoRandomId = (prefix = '') => Math.random()
2
+ .toString(36)
3
+ .replace('0.', prefix || '');
@@ -0,0 +1 @@
1
+ export declare const queryParams: <T extends Record<string, string | number | undefined | null>>(params: T) => string;
@@ -0,0 +1,14 @@
1
+ export const queryParams = (params) => {
2
+ // create the string
3
+ let query = '?';
4
+ let isFirst = true;
5
+ for (const [key, value] of Object.entries(params)) {
6
+ if (value === undefined || value === null)
7
+ continue;
8
+ if (!isFirst)
9
+ query += '&';
10
+ query += `${key}=${value}`;
11
+ isFirst = false;
12
+ }
13
+ return isFirst ? '' : query;
14
+ };
package/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "dependencies": {
3
+ "@floating-ui/dom": "^1.6.13",
4
+ "@lucide/svelte": "^0.503.0",
5
+ "@tailwindcss/forms": "^0.5.10",
6
+ "@tailwindcss/typography": "^0.5.16",
7
+ "dompurify": "^3.2.5",
8
+ "marked": "^15.0.11",
9
+ "tailwind-merge": "^3.2.0",
10
+ "tailwindcss": "^4.0.0"
11
+ },
12
+ "devDependencies": {
13
+ "@eslint/compat": "^1.2.5",
14
+ "@eslint/js": "^9.18.0",
15
+ "@sveltejs/adapter-auto": "^4.0.0",
16
+ "@sveltejs/package": "^2.0.0",
17
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
18
+ "@tailwindcss/vite": "^4.1.4",
19
+ "@testing-library/jest-dom": "^6.6.3",
20
+ "@testing-library/svelte": "^5.2.4",
21
+ "@testing-library/user-event": "^14.6.1",
22
+ "@vitest/browser": "^3.1.1",
23
+ "@vitest/coverage-v8": "^3.1.1",
24
+ "@vitest/spy": "^3.1.2",
25
+ "eslint": "^9.18.0",
26
+ "eslint-config-prettier": "^10.0.1",
27
+ "eslint-plugin-svelte": "^3.0.0",
28
+ "globals": "^16.0.0",
29
+ "jsdom": "^26.0.0",
30
+ "playwright": "^1.51.1",
31
+ "prettier": "^3.4.2",
32
+ "prettier-plugin-svelte": "^3.3.3",
33
+ "prettier-plugin-tailwindcss": "^0.6.11",
34
+ "publint": "^0.3.2",
35
+ "svelte-check": "^4.0.0",
36
+ "typescript": "^5.0.0",
37
+ "typescript-eslint": "^8.20.0",
38
+ "vite": "^6.0.0",
39
+ "vitest": "^3.0.0"
40
+ },
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/index.d.ts",
44
+ "svelte": "./dist/index.js"
45
+ },
46
+ "./components/ai": {
47
+ "types": "./dist/components/ai/index.d.ts",
48
+ "svelte": "./dist/components/ai/index.js"
49
+ },
50
+ "./components/basic": {
51
+ "types": "./dist/components/basic/index.d.ts",
52
+ "svelte": "./dist/components/basic/index.js"
53
+ },
54
+ "./components/layout": {
55
+ "types": "./dist/components/layout/index.d.ts",
56
+ "svelte": "./dist/components/layout/index.js"
57
+ },
58
+ "./components/toast": {
59
+ "types": "./dist/components/toast/index.d.ts",
60
+ "svelte": "./dist/components/toast/index.js"
61
+ },
62
+ "./utils/actions": {
63
+ "types": "./dist/utils/actions/index.d.ts",
64
+ "svelte": "./dist/utils/actions/index.js"
65
+ },
66
+ "./utils/functions": {
67
+ "types": "./dist/utils/functions/index.d.ts",
68
+ "svelte": "./dist/utils/functions/index.js"
69
+ }
70
+ },
71
+ "files": [
72
+ "dist",
73
+ "src/lib",
74
+ "!dist/**/*.test.*",
75
+ "!dist/**/*.spec.*"
76
+ ],
77
+ "keywords": [
78
+ "svelte"
79
+ ],
80
+ "license": "MIT",
81
+ "name": "@ims360/svelte-ivory",
82
+ "peerDependencies": {
83
+ "@skeletonlabs/skeleton": "^3.1.1",
84
+ "@sveltejs/kit": "^2.16.0",
85
+ "svelte": "^5.0.0"
86
+ },
87
+ "scripts": {
88
+ "build": "vite build && npm run prepack",
89
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
90
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
91
+ "dev": "vite dev",
92
+ "format": "prettier --write .",
93
+ "lint": "prettier --check . && eslint .",
94
+ "prepack": "svelte-kit sync && svelte-package && publint",
95
+ "prepare": "svelte-kit sync || echo ''",
96
+ "preview": "vite preview",
97
+ "test": "npm run test:unit -- --run",
98
+ "test:unit": "vitest"
99
+ },
100
+ "sideEffects": [
101
+ "**/*.css"
102
+ ],
103
+ "svelte": "./dist/index.js",
104
+ "type": "module",
105
+ "types": "./dist/index.d.ts",
106
+ "version": "0.0.2"
107
+ }
@@ -0,0 +1,115 @@
1
+ <!--
2
+ @component
3
+ The default for AI Messages in the Chat component.
4
+ Can be customized using the `class` and `loading` props.
5
+ -->
6
+
7
+ <script lang="ts">
8
+ import { ThumbsDown, ThumbsUp } from '@lucide/svelte';
9
+ import clsx from 'clsx';
10
+ import type { Snippet } from 'svelte';
11
+ import type { ClassValue } from 'svelte/elements';
12
+ import { twMerge } from 'tailwind-merge';
13
+ import CopyToClipboardButton from '../buttons/CopyToClipboardButton.svelte';
14
+ import type { AiChatMessage } from './Chat.svelte';
15
+ import Markdown from './Markdown.svelte';
16
+
17
+ interface Props {
18
+ b_message: AiChatMessage;
19
+ messageText?: Snippet<[{ message: AiChatMessage }]>;
20
+ class?: ClassValue;
21
+ minHeight?: number;
22
+ }
23
+
24
+ let {
25
+ b_message = $bindable(),
26
+ class: clazz,
27
+ messageText = defaultMessage,
28
+ minHeight
29
+ }: Props = $props();
30
+
31
+ // const uuidRegex =
32
+ // /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g;
33
+ // const icon = `<svg aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" class="h-[1em] origin-center overflow-visible align-[-0.125rem]" viewBox="0 0 512 512"><g transform="translate(256 256)" transform-origin="128 0"><path d="M336 0c-8.8 0-16 7.2-16 16s7.2 16 16 16l121.4 0L212.7 276.7c-6.2 6.2-6.2 16.4 0 22.6s16.4 6.2 22.6 0L480 54.6 480 176c0 8.8 7.2 16 16 16s16-7.2 16-16l0-160c0-8.8-7.2-16-16-16L336 0zM64 32C28.7 32 0 60.7 0 96L0 448c0 35.3 28.7 64 64 64l352 0c35.3 0 64-28.7 64-64l0-144c0-8.8-7.2-16-16-16s-16 7.2-16 16l0 144c0 17.7-14.3 32-32 32L64 480c-17.7 0-32-14.3-32-32L32 96c0-17.7 14.3-32 32-32l144 0c8.8 0 16-7.2 16-16s-7.2-16-16-16L64 32z" fill="currentColor" transform="translate(-256 -256)"></path><!----></g></svg>`;
34
+
35
+ // async function replacerFactory(): Promise<(match: string) => string> {
36
+ // const docs = await documents.value;
37
+ // return (match: string) => {
38
+ // const doc = docs.find((d) => d.id === match);
39
+
40
+ // return `<span class="bg-surface-100-900 py-1 px-2 rounded flex flex-row items-center gap-2 hover:shadow-lg w-fit transition-all">
41
+ // ${doc?.title ?? 'Unbekanntes Dokument'}
42
+ // <a href="/documents/${doc?.id}" target="_blank">
43
+ // ${icon}
44
+ // </a>
45
+ // </span>`;
46
+ // };
47
+ // }
48
+ </script>
49
+
50
+ <div
51
+ class={twMerge(clsx('group flex w-full flex-col items-start', clazz))}
52
+ style={minHeight ? `min-height: ${minHeight}px;` : undefined}
53
+ >
54
+ {@render messageText({
55
+ message: b_message
56
+ })}
57
+ <div
58
+ class={[
59
+ 'text-surface-500 flex -translate-x-3 flex-row items-center transition-all group-hover:opacity-100',
60
+ b_message.liked || b_message.disliked ? 'opacity-100' : 'opacity-0'
61
+ ]}
62
+ >
63
+ <CopyToClipboardButton
64
+ text={b_message.message}
65
+ toastMessage="Nachricht wurde in die Zwischenablage kopiert"
66
+ />
67
+ <button
68
+ type="button"
69
+ class="btn-icon"
70
+ onclick={() => {
71
+ b_message.liked = !b_message.liked;
72
+ b_message.disliked = false;
73
+ }}
74
+ >
75
+ <ThumbsUp class={[b_message.liked && 'fill-surface-500/50']} />
76
+ </button>
77
+ <button
78
+ type="button"
79
+ class="btn-icon"
80
+ onclick={() => {
81
+ b_message.liked = false;
82
+ b_message.disliked = !b_message.disliked;
83
+ }}
84
+ >
85
+ <ThumbsDown class={[b_message.disliked && 'fill-surface-500/50']} />
86
+ </button>
87
+ {#if b_message.time}
88
+ <p class="text-surface-400-600 pl-2">
89
+ {b_message.time.toLocaleString('de')}
90
+ </p>
91
+ {/if}
92
+ </div>
93
+ </div>
94
+
95
+ {#snippet defaultMessage({ message }: { message: AiChatMessage })}
96
+ {#if message}
97
+ <Markdown source={message.message} />
98
+ {:else}
99
+ <p class="flex flex-row">
100
+ <span class="h-4 animate-bounce rounded-full pl-1">.</span>
101
+ <span
102
+ class="h-4 animate-bounce rounded-full"
103
+ style="animation-delay: 125ms !important;"
104
+ >
105
+ .
106
+ </span>
107
+ <span
108
+ class="h-4 animate-bounce rounded-full"
109
+ style="animation-delay: 250ms !important;"
110
+ >
111
+ .
112
+ </span>
113
+ </p>
114
+ {/if}
115
+ {/snippet}
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { X } from '@lucide/svelte';
3
+ import type { ClassValue } from 'svelte/elements';
4
+
5
+ interface Props {
6
+ file: File;
7
+ onremove?: (f: File) => void;
8
+ class?: ClassValue;
9
+ }
10
+
11
+ let { file, onremove, class: clazz }: Props = $props();
12
+ </script>
13
+
14
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
15
+ <svelte:element
16
+ this={onremove ? 'button' : 'div'}
17
+ type={onremove ? 'button' : undefined}
18
+ onclick={onremove ? () => onremove(file) : undefined}
19
+ class={[
20
+ 'bg-primary-500/25 group flex flex-row items-center gap-1 overflow-hidden rounded-full px-4 py-1 whitespace-nowrap',
21
+ clazz
22
+ ]}
23
+ >
24
+ <p>{file.name}</p>
25
+ {#if onremove}
26
+ <X size={16} class="group-hover:text-primary-500" />
27
+ {/if}
28
+ </svelte:element>