@dvcol/neo-svelte 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/LICENSE +21 -0
  3. package/README.md +89 -0
  4. package/dist/assets/neo-icon-resizer-bottom-right.svg +5 -0
  5. package/dist/buttons/NeoButton.svelte +466 -0
  6. package/dist/buttons/NeoButton.svelte.d.ts +19 -0
  7. package/dist/buttons/NeoButtonGroup.svelte +369 -0
  8. package/dist/buttons/NeoButtonGroup.svelte.d.ts +19 -0
  9. package/dist/buttons/index.d.ts +4 -0
  10. package/dist/buttons/index.js +2 -0
  11. package/dist/buttons/neo-button-group.model.d.ts +71 -0
  12. package/dist/buttons/neo-button-group.model.js +1 -0
  13. package/dist/buttons/neo-button.model.d.ts +110 -0
  14. package/dist/buttons/neo-button.model.js +1 -0
  15. package/dist/cards/NeoCard.svelte +413 -0
  16. package/dist/cards/NeoCard.svelte.d.ts +19 -0
  17. package/dist/cards/index.d.ts +2 -0
  18. package/dist/cards/index.js +1 -0
  19. package/dist/cards/neo-card.model.d.ts +129 -0
  20. package/dist/cards/neo-card.model.js +1 -0
  21. package/dist/containers/NeoTransitionContainer.svelte +34 -0
  22. package/dist/containers/NeoTransitionContainer.svelte.d.ts +19 -0
  23. package/dist/containers/index.d.ts +2 -0
  24. package/dist/containers/index.js +1 -0
  25. package/dist/containers/neo-transition-container.model.d.ts +25 -0
  26. package/dist/containers/neo-transition-container.model.js +1 -0
  27. package/dist/divider/NeoDivider.svelte +47 -0
  28. package/dist/divider/NeoDivider.svelte.d.ts +19 -0
  29. package/dist/divider/index.d.ts +2 -0
  30. package/dist/divider/index.js +1 -0
  31. package/dist/divider/neo-divider.model.d.ts +7 -0
  32. package/dist/divider/neo-divider.model.js +1 -0
  33. package/dist/icons/IconAccount.svelte +10 -0
  34. package/dist/icons/IconAccount.svelte.d.ts +26 -0
  35. package/dist/icons/IconAdd.svelte +10 -0
  36. package/dist/icons/IconAdd.svelte.d.ts +26 -0
  37. package/dist/icons/IconAlert.svelte +14 -0
  38. package/dist/icons/IconAlert.svelte.d.ts +26 -0
  39. package/dist/icons/IconCircleLoading.svelte +16 -0
  40. package/dist/icons/IconCircleLoading.svelte.d.ts +26 -0
  41. package/dist/icons/IconClear.svelte +10 -0
  42. package/dist/icons/IconClear.svelte.d.ts +26 -0
  43. package/dist/icons/IconClose.svelte +14 -0
  44. package/dist/icons/IconClose.svelte.d.ts +26 -0
  45. package/dist/icons/IconConfirm.svelte +10 -0
  46. package/dist/icons/IconConfirm.svelte.d.ts +26 -0
  47. package/dist/icons/IconEmpty.svelte +10 -0
  48. package/dist/icons/IconEmpty.svelte.d.ts +26 -0
  49. package/dist/icons/IconFileUpload.svelte +18 -0
  50. package/dist/icons/IconFileUpload.svelte.d.ts +26 -0
  51. package/dist/icons/IconGithub.svelte +29 -0
  52. package/dist/icons/IconGithub.svelte.d.ts +26 -0
  53. package/dist/icons/IconImage.svelte +18 -0
  54. package/dist/icons/IconImage.svelte.d.ts +26 -0
  55. package/dist/icons/IconMinus.svelte +14 -0
  56. package/dist/icons/IconMinus.svelte.d.ts +26 -0
  57. package/dist/icons/IconMoon.svelte +64 -0
  58. package/dist/icons/IconMoon.svelte.d.ts +26 -0
  59. package/dist/icons/IconSave.svelte +15 -0
  60. package/dist/icons/IconSave.svelte.d.ts +26 -0
  61. package/dist/icons/IconSaveOff.svelte +19 -0
  62. package/dist/icons/IconSaveOff.svelte.d.ts +26 -0
  63. package/dist/icons/IconSearch.svelte +14 -0
  64. package/dist/icons/IconSearch.svelte.d.ts +26 -0
  65. package/dist/icons/IconSun.svelte +54 -0
  66. package/dist/icons/IconSun.svelte.d.ts +26 -0
  67. package/dist/icons/IconSunrise.svelte +24 -0
  68. package/dist/icons/IconSunrise.svelte.d.ts +26 -0
  69. package/dist/icons/IconVideo.svelte +15 -0
  70. package/dist/icons/IconVideo.svelte.d.ts +26 -0
  71. package/dist/icons/IconWatch.svelte +21 -0
  72. package/dist/icons/IconWatch.svelte.d.ts +26 -0
  73. package/dist/icons/IconWatchOff.svelte +26 -0
  74. package/dist/icons/IconWatchOff.svelte.d.ts +26 -0
  75. package/dist/index.d.ts +8 -0
  76. package/dist/index.js +8 -0
  77. package/dist/inputs/NeoInput.svelte +750 -0
  78. package/dist/inputs/NeoInput.svelte.d.ts +27 -0
  79. package/dist/inputs/NeoPassword.svelte +31 -0
  80. package/dist/inputs/NeoPassword.svelte.d.ts +19 -0
  81. package/dist/inputs/NeoTextarea.svelte +768 -0
  82. package/dist/inputs/NeoTextarea.svelte.d.ts +27 -0
  83. package/dist/inputs/NeoValidation.svelte +106 -0
  84. package/dist/inputs/NeoValidation.svelte.d.ts +22 -0
  85. package/dist/inputs/index.d.ts +4 -0
  86. package/dist/inputs/index.js +3 -0
  87. package/dist/inputs/neo-input.model.d.ts +234 -0
  88. package/dist/inputs/neo-input.model.js +10 -0
  89. package/dist/inputs/neo-validation.model.d.ts +40 -0
  90. package/dist/inputs/neo-validation.model.js +1 -0
  91. package/dist/nav/NeoTab.svelte +170 -0
  92. package/dist/nav/NeoTab.svelte.d.ts +19 -0
  93. package/dist/nav/NeoTabPanel.svelte +75 -0
  94. package/dist/nav/NeoTabPanel.svelte.d.ts +19 -0
  95. package/dist/nav/NeoTabs.svelte +288 -0
  96. package/dist/nav/NeoTabs.svelte.d.ts +19 -0
  97. package/dist/nav/NeoTabsCard.svelte +47 -0
  98. package/dist/nav/NeoTabsCard.svelte.d.ts +19 -0
  99. package/dist/nav/index.d.ts +8 -0
  100. package/dist/nav/index.js +4 -0
  101. package/dist/nav/neo-tab-panel.model.d.ts +30 -0
  102. package/dist/nav/neo-tab-panel.model.js +1 -0
  103. package/dist/nav/neo-tab.model.d.ts +43 -0
  104. package/dist/nav/neo-tab.model.js +1 -0
  105. package/dist/nav/neo-tabs-card.model.d.ts +25 -0
  106. package/dist/nav/neo-tabs-card.model.js +5 -0
  107. package/dist/nav/neo-tabs-context.svelte.d.ts +82 -0
  108. package/dist/nav/neo-tabs-context.svelte.js +163 -0
  109. package/dist/nav/neo-tabs.model.d.ts +60 -0
  110. package/dist/nav/neo-tabs.model.js +1 -0
  111. package/dist/providers/NeoThemeProvider.svelte +28 -0
  112. package/dist/providers/NeoThemeProvider.svelte.d.ts +21 -0
  113. package/dist/providers/NeoThemeSelector.svelte +79 -0
  114. package/dist/providers/NeoThemeSelector.svelte.d.ts +19 -0
  115. package/dist/providers/index.d.ts +5 -0
  116. package/dist/providers/index.js +3 -0
  117. package/dist/providers/neo-theme-provider-context.svelte.d.ts +26 -0
  118. package/dist/providers/neo-theme-provider-context.svelte.js +78 -0
  119. package/dist/providers/neo-theme-provider.model.d.ts +35 -0
  120. package/dist/providers/neo-theme-provider.model.js +20 -0
  121. package/dist/providers/neo-theme-selector.model.d.ts +6 -0
  122. package/dist/providers/neo-theme-selector.model.js +1 -0
  123. package/dist/skeletons/NeoSkeletonContainer.svelte +48 -0
  124. package/dist/skeletons/NeoSkeletonContainer.svelte.d.ts +19 -0
  125. package/dist/skeletons/NeoSkeletonMedia.svelte +146 -0
  126. package/dist/skeletons/NeoSkeletonMedia.svelte.d.ts +19 -0
  127. package/dist/skeletons/NeoSkeletonText.svelte +170 -0
  128. package/dist/skeletons/NeoSkeletonText.svelte.d.ts +19 -0
  129. package/dist/skeletons/index.d.ts +4 -0
  130. package/dist/skeletons/index.js +2 -0
  131. package/dist/skeletons/neo-skeleton-container.model.d.ts +29 -0
  132. package/dist/skeletons/neo-skeleton-container.model.js +1 -0
  133. package/dist/skeletons/neo-skeleton-media.model.d.ts +40 -0
  134. package/dist/skeletons/neo-skeleton-media.model.js +1 -0
  135. package/dist/skeletons/neo-skeleton-text.model.d.ts +45 -0
  136. package/dist/skeletons/neo-skeleton-text.model.js +1 -0
  137. package/dist/styles/animation.scss +75 -0
  138. package/dist/styles/common/colors.scss +217 -0
  139. package/dist/styles/common/flex.scss +26 -0
  140. package/dist/styles/common/properties.css +23 -0
  141. package/dist/styles/common/shadows.scss +305 -0
  142. package/dist/styles/common/spacing.scss +27 -0
  143. package/dist/styles/common/typography.scss +13 -0
  144. package/dist/styles/common/utils.scss +8 -0
  145. package/dist/styles/common/z-index.scss +12 -0
  146. package/dist/styles/mixin.scss +225 -0
  147. package/dist/styles/reset.scss +81 -0
  148. package/dist/styles/theme.scss +22 -0
  149. package/dist/utils/action.utils.d.ts +52 -0
  150. package/dist/utils/action.utils.js +30 -0
  151. package/dist/utils/error.utils.d.ts +25 -0
  152. package/dist/utils/error.utils.js +35 -0
  153. package/dist/utils/html-element.utils.d.ts +22 -0
  154. package/dist/utils/html-element.utils.js +1 -0
  155. package/dist/utils/logger.utils.d.ts +14 -0
  156. package/dist/utils/logger.utils.js +31 -0
  157. package/dist/utils/shadow.utils.d.ts +8 -0
  158. package/dist/utils/shadow.utils.js +26 -0
  159. package/dist/utils/timeout.util.d.ts +5 -0
  160. package/dist/utils/timeout.util.js +15 -0
  161. package/dist/utils/transition.utils.d.ts +5 -0
  162. package/dist/utils/transition.utils.js +8 -0
  163. package/package.json +180 -0
@@ -0,0 +1,768 @@
1
+ <script lang="ts">
2
+ import { wait } from '@dvcol/common-utils/common/promise';
3
+ import { fade } from 'svelte/transition';
4
+
5
+ import type { EventHandler, FocusEventHandler, FormEventHandler, MouseEventHandler } from 'svelte/elements';
6
+
7
+ import IconAlert from '../icons/IconAlert.svelte';
8
+ import IconCircleLoading from '../icons/IconCircleLoading.svelte';
9
+ import IconClear from '../icons/IconClear.svelte';
10
+ import IconConfirm from '../icons/IconConfirm.svelte';
11
+ import NeoValidation from './NeoValidation.svelte';
12
+ import {
13
+ type NeoInputContext,
14
+ NeoInputLabelPosition,
15
+ type NeoInputMethods,
16
+ type NeoInputState,
17
+ type NeoTextareaProps,
18
+ } from './neo-input.model.js';
19
+ import { toAction, toActionProps, toTransition, toTransitionProps } from '../utils/action.utils.js';
20
+ import { computeGlassFilter, computeHoverShadowElevation, computeShadowElevation } from '../utils/shadow.utils.js';
21
+ import { enterDefaultTransition, leaveDefaultTransition } from '../utils/transition.utils.js';
22
+
23
+ /* eslint-disable prefer-const -- necessary for binding checked */
24
+ let {
25
+ // Snippets
26
+ label,
27
+ suffix,
28
+ message,
29
+ error,
30
+
31
+ // States
32
+ id = label ? `neo-textarea-${crypto.randomUUID()}` : undefined,
33
+ ref = $bindable(),
34
+ value = $bindable(''),
35
+ valid = $bindable(undefined),
36
+ dirty = $bindable(false),
37
+ touched = $bindable(false),
38
+ readonly,
39
+ disabled,
40
+ loading,
41
+ clearable,
42
+ dirtyOnInput,
43
+ validateOnInput,
44
+ position = NeoInputLabelPosition.Inside,
45
+ autoResize = true,
46
+
47
+ // Styles
48
+ elevation = 3,
49
+ hover = -1,
50
+ borderless,
51
+ rounded,
52
+ glass,
53
+ start,
54
+ floating = true,
55
+ skeleton,
56
+ validation,
57
+
58
+ // Transition
59
+ in: inAction,
60
+ out: outAction,
61
+ transition: transitionAction,
62
+
63
+ // Actions
64
+ use,
65
+
66
+ // Events
67
+ oninput,
68
+ onfocus,
69
+ onblur,
70
+ onmark,
71
+ onclear,
72
+ onchange,
73
+ oninvalid,
74
+
75
+ // Other props
76
+ labelRef = $bindable(),
77
+ labelProps,
78
+ suffixProps,
79
+ suffixTag = suffixProps?.onclick ? 'button' : 'span',
80
+ containerProps,
81
+ containerTag = 'div',
82
+ wrapperProps,
83
+ wrapperTag = 'div',
84
+ messageProps,
85
+ messageTag = 'div',
86
+ ...rest
87
+ }: NeoTextareaProps = $props();
88
+ /* eslint-enable prefer-const */
89
+
90
+ let initial = $state(value);
91
+ let validationMessage: string | undefined = $state(ref?.validationMessage);
92
+
93
+ const filter = $derived.by(() => computeGlassFilter(elevation, glass));
94
+ const boxShadow = $derived.by(() => computeShadowElevation(elevation, glass));
95
+ const hoverShadow = $derived.by(() => computeHoverShadowElevation(elevation, hover, glass) ?? boxShadow);
96
+
97
+ const hoverFlat = $derived(boxShadow.endsWith('flat') && !hoverShadow.endsWith('flat'));
98
+ const flatHover = $derived(hoverShadow.endsWith('flat') && !boxShadow.endsWith('flat'));
99
+
100
+ let hovered = $state(false);
101
+ const onMouseEnter: MouseEventHandler<HTMLDivElement> = e => {
102
+ hovered = true;
103
+ containerProps?.onmouseenter?.(e);
104
+ };
105
+
106
+ const onMouseLeave: MouseEventHandler<HTMLDivElement> = e => {
107
+ hovered = false;
108
+ containerProps?.onmouseleave?.(e);
109
+ };
110
+
111
+ let focused = $state(false);
112
+ const onFocus: FocusEventHandler<HTMLTextAreaElement> = e => {
113
+ focused = true;
114
+ touched = true;
115
+ onfocus?.(e);
116
+ };
117
+
118
+ const onBlur: FocusEventHandler<HTMLTextAreaElement> = e => {
119
+ focused = false;
120
+ onblur?.(e);
121
+ };
122
+
123
+ const checkValidity = (update: { dirty?: boolean; valid?: boolean } = { dirty: true, valid: true }) => {
124
+ if (update.dirty) dirty = value !== initial;
125
+ if (!update.valid) return;
126
+ valid = ref?.checkValidity();
127
+ validationMessage = ref?.validationMessage;
128
+ };
129
+
130
+ const onInput: FormEventHandler<HTMLTextAreaElement> = e => {
131
+ checkValidity({ dirty: dirtyOnInput, valid: validateOnInput });
132
+ oninput?.(e);
133
+ };
134
+
135
+ const onChange: FormEventHandler<HTMLTextAreaElement> = e => {
136
+ checkValidity();
137
+ onchange?.(e);
138
+ };
139
+
140
+ const onInvalid: EventHandler<Event, HTMLTextAreaElement> = e => {
141
+ valid = false;
142
+ validationMessage = ref?.validationMessage;
143
+ e.preventDefault();
144
+ oninvalid?.(e);
145
+ };
146
+
147
+ /**
148
+ * Change the state of the input
149
+ * @param state
150
+ */
151
+ export const mark: NeoInputMethods<HTMLTextAreaElement>['mark'] = (state: NeoInputState) => {
152
+ if (state.touched !== undefined) touched = state.touched;
153
+ if (state.valid !== undefined) valid = state.valid;
154
+ if (state.dirty === undefined) return onmark?.({ touched, dirty, valid });
155
+ dirty = state.dirty;
156
+ if (!dirty) initial = value;
157
+ return onmark?.({ touched, dirty, valid, value });
158
+ };
159
+
160
+ const focus = () => {
161
+ if (focused) return;
162
+ ref?.focus();
163
+ };
164
+
165
+ /**
166
+ * Clear the input state
167
+ */
168
+ export const clear: NeoInputMethods<HTMLTextAreaElement>['clear'] = (state?: NeoInputState) => {
169
+ value = '';
170
+ focus();
171
+ if (!state) {
172
+ setTimeout(() => checkValidity());
173
+ return onclear?.({ touched, dirty, valid, value });
174
+ }
175
+ initial = value;
176
+ setTimeout(() => mark({ touched: false, dirty: false, ...state }));
177
+ return onclear?.({ touched, dirty, valid, value });
178
+ };
179
+
180
+ const affix = $derived(clearable || loading !== undefined || validation);
181
+ const close = $derived(clearable && (focused || hovered) && value?.length && !disabled && !readonly);
182
+ const isFloating = $derived(floating && !focused && !value?.length && !disabled && !readonly);
183
+
184
+ let labelHeight = $state<string>();
185
+ let labelWidth = $state<string>();
186
+
187
+ let first = $state(true);
188
+ // Skip enter transition on first render for floating label
189
+ const waitForTick = async () => {
190
+ if (!first) return;
191
+ await wait();
192
+ first = false;
193
+ };
194
+
195
+ $effect(() => {
196
+ if (first) waitForTick();
197
+ if (!labelRef) return;
198
+ if (position === NeoInputLabelPosition.Inside && !floating) return;
199
+ labelHeight = `${labelRef?.clientHeight ?? 0}px`;
200
+ if (position === NeoInputLabelPosition.Left || position === NeoInputLabelPosition.Right) return;
201
+ labelWidth = `${labelRef?.clientWidth ?? 0}px`;
202
+ });
203
+
204
+ const rows = $derived.by(() => {
205
+ if (typeof autoResize === 'boolean' || !autoResize) return suffix ? 3 : undefined;
206
+ if (suffix) return Math.max(3, autoResize.min ?? 0);
207
+ return autoResize.min;
208
+ });
209
+
210
+ const max = $derived.by(() => {
211
+ if (!ref) return;
212
+ if (typeof autoResize === 'boolean' || !autoResize) return;
213
+ if (!autoResize.max) return;
214
+
215
+ const lineHeight = Number.parseInt(getComputedStyle(ref).lineHeight, 10);
216
+ if (!lineHeight || Number.isNaN(lineHeight)) return;
217
+ return autoResize.max * lineHeight;
218
+ });
219
+
220
+ const resize = () => {
221
+ if (!autoResize || !ref) return;
222
+
223
+ const isScrolled = ref.scrollHeight && ref.scrollHeight > ref.clientHeight;
224
+ const hasMin = typeof autoResize !== 'boolean' && autoResize.min;
225
+ if (!isScrolled && !hasMin) return;
226
+
227
+ ref.style.height = '1px';
228
+ const { scrollHeight } = ref;
229
+
230
+ if (!scrollHeight) ref.style.height = '';
231
+ else if (max) ref.style.height = `${Math.min(max, scrollHeight)}px`;
232
+ else ref.style.height = `${scrollHeight}px`;
233
+ };
234
+
235
+ $effect(() => {
236
+ // eslint-disable-next-line no-unused-expressions -- used to trigger resize textarea on value change
237
+ value;
238
+ resize();
239
+ });
240
+
241
+ const errorMessage = $derived.by(() => {
242
+ if (valid || valid === undefined) return;
243
+ if (error) return error;
244
+ if (!validation) return;
245
+ return error ?? validationMessage;
246
+ });
247
+
248
+ const showMessage = $derived(message || errorMessage || error || validation);
249
+ const messageId = $derived(showMessage ? (messageProps?.id ?? `neo-textarea-message-${crypto.randomUUID()}`) : undefined);
250
+
251
+ const context = $derived<NeoInputContext<HTMLTextAreaElement>>({
252
+ // Ref
253
+ ref,
254
+
255
+ // Methods
256
+ mark,
257
+ clear,
258
+
259
+ // State
260
+ value,
261
+ touched,
262
+ dirty,
263
+ valid,
264
+ readonly,
265
+ disabled,
266
+
267
+ // Styles
268
+ elevation,
269
+ hover,
270
+ borderless,
271
+ rounded,
272
+ glass,
273
+ start,
274
+ skeleton,
275
+ });
276
+
277
+ const inFn = $derived(toTransition(inAction ?? transitionAction));
278
+ const inProps = $derived(toTransitionProps(inAction ?? transitionAction));
279
+ const outFn = $derived(toTransition(outAction ?? transitionAction));
280
+ const outProps = $derived(toTransitionProps(outAction ?? transitionAction));
281
+
282
+ const useFn = $derived(toAction(use));
283
+ const useProps = $derived(toActionProps(use));
284
+ </script>
285
+
286
+ {#snippet after()}
287
+ <!-- Affix (loafing, clear, placeholder) -->
288
+ {#if affix}
289
+ <span class="neo-textarea-affix" role="none" onclick={focus}>
290
+ {#if loading}
291
+ <span out:fade={enterDefaultTransition}>
292
+ <IconCircleLoading width="1.1875rem" height="1.1875rem" />
293
+ </span>
294
+ {:else if close}
295
+ <button class="neo-textarea-clear" aria-label="clear" in:fade out:fade={enterDefaultTransition} onclick={() => clear()}>
296
+ <IconClear width="1.1875rem" height="1.1875rem" />
297
+ </button>
298
+ {:else}
299
+ <span class="neo-textarea-affix-validation" in:fade={leaveDefaultTransition}>
300
+ {#if validation && valid === false}
301
+ <IconAlert width="1.1875rem" height="1.1875rem" />
302
+ {:else if validation && valid === true && touched}
303
+ <IconConfirm width="1.1875rem" height="1.1875rem" />
304
+ {/if}
305
+ </span>
306
+ {/if}
307
+ </span>
308
+ {/if}
309
+
310
+ <!-- Suffix -->
311
+ {#if suffix}
312
+ <svelte:element this={suffixTag} class:neo-textarea-suffix={true} {disabled} {readonly} {...suffixProps}>
313
+ {@render suffix(context)}
314
+ </svelte:element>
315
+ {/if}
316
+ {/snippet}
317
+
318
+ {#snippet textarea()}
319
+ <textarea
320
+ {id}
321
+ {disabled}
322
+ {readonly}
323
+ aria-invalid={valid === undefined ? undefined : !valid}
324
+ aria-describedby={messageId}
325
+ bind:this={ref}
326
+ bind:value
327
+ class:neo-textarea={true}
328
+ class:affix={affix || suffix}
329
+ {rows}
330
+ onblur={onBlur}
331
+ onfocus={onFocus}
332
+ oninput={onInput}
333
+ onchange={onChange}
334
+ oninvalid={onInvalid}
335
+ use:useFn={useProps}
336
+ {...rest}
337
+ >
338
+ </textarea>
339
+ {/snippet}
340
+
341
+ {#snippet textareaGroup()}
342
+ <svelte:element
343
+ this={containerTag}
344
+ role="none"
345
+ data-position={position}
346
+ data-touched={touched}
347
+ data-dirty={dirty}
348
+ data-valid={valid}
349
+ class:neo-textarea-group={true}
350
+ class:readonly
351
+ class:borderless
352
+ class:rounded
353
+ class:glass
354
+ class:hover
355
+ class:start
356
+ class:skeleton
357
+ class:validation
358
+ class:disabled
359
+ class:raised={elevation > 3 || elevation + hover > 3}
360
+ class:inset={elevation < -3 || elevation + hover < -3}
361
+ class:flat={!elevation}
362
+ class:hover-flat={hoverFlat}
363
+ class:flat-hover={flatHover}
364
+ style:--neo-textarea-glass-blur={filter}
365
+ style:--neo-textarea-box-shadow={boxShadow}
366
+ style:--neo-textarea-hover-shadow={hoverShadow}
367
+ style:--neo-textarea-label-height={labelHeight}
368
+ style:--neo-textarea-label-width={labelWidth}
369
+ out:outFn={outProps}
370
+ in:inFn={inProps}
371
+ onmouseenter={onMouseEnter}
372
+ onmouseleave={onMouseLeave}
373
+ {...containerProps}
374
+ >
375
+ {#if label}
376
+ <div class="neo-textarea-label-container" class:floating={isFloating} role="none" onclick={focus}>
377
+ <label bind:this={labelRef} for={id} class:neo-textarea-label={true} class:first class:rounded class:required={rest.required} {...labelProps}>
378
+ {#if typeof label === 'string'}
379
+ {label}
380
+ {:else}
381
+ {@render label(context)}
382
+ {/if}
383
+ </label>
384
+ {@render textarea()}
385
+ </div>
386
+ {:else}
387
+ {@render textarea()}
388
+ {/if}
389
+ {@render after()}
390
+ </svelte:element>
391
+ {/snippet}
392
+
393
+ {#if showMessage}
394
+ <NeoValidation
395
+ tag={wrapperTag}
396
+ error={errorMessage}
397
+ {context}
398
+ {message}
399
+ {messageId}
400
+ {messageTag}
401
+ {messageProps}
402
+ in={inAction}
403
+ out={outAction}
404
+ transition={transitionAction}
405
+ {...wrapperProps}
406
+ >
407
+ {@render textareaGroup()}
408
+ </NeoValidation>
409
+ {:else}
410
+ {@render textareaGroup()}
411
+ {/if}
412
+
413
+ <style>.neo-textarea-group,
414
+ .neo-textarea,
415
+ .neo-textarea-clear,
416
+ .neo-textarea-affix,
417
+ .neo-textarea-suffix {
418
+ display: flex;
419
+ box-sizing: border-box;
420
+ font: inherit;
421
+ text-decoration: none;
422
+ outline: none;
423
+ transition: color 0.3s ease, margin 0.3s ease, padding 0.3s ease, background-color 0.3s ease, backdrop-filter 0.3s ease, border-color 0.3s ease, border-radius 0.3s ease, box-shadow 0.3s ease-out;
424
+ }
425
+
426
+ .neo-textarea-clear, .neo-textarea-suffix:is(button, a) {
427
+ cursor: pointer;
428
+ }
429
+ .neo-textarea-clear:focus-visible, .neo-textarea-suffix:focus-visible:is(button, a) {
430
+ color: var(--neo-textarea-focus-color, var(--neo-text-color-focused));
431
+ }
432
+ .neo-textarea-clear:hover, .neo-textarea-suffix:hover:is(button, a) {
433
+ color: var(--neo-textarea-hover-color, var(--neo-text-color-hover));
434
+ }
435
+ .neo-textarea-clear:active, .neo-textarea-suffix:active:is(button, a) {
436
+ color: var(--neo-textarea-active-color, var(--neo-text-color-hover-active));
437
+ scale: 0.9;
438
+ }
439
+ .neo-textarea-clear:disabled, .neo-textarea-suffix:disabled:is(button, a) {
440
+ color: var(--neo-text-color-disabled);
441
+ cursor: not-allowed;
442
+ scale: 1;
443
+ }
444
+
445
+ .neo-textarea {
446
+ flex: 1 1 auto;
447
+ align-self: center;
448
+ min-width: 100%;
449
+ max-width: 100%;
450
+ min-height: fit-content;
451
+ padding: 0.75rem;
452
+ color: inherit;
453
+ text-overflow: ellipsis;
454
+ background-color: transparent;
455
+ border: none;
456
+ border-radius: var(--neo-textarea-border-radius, var(--neo-border-radius));
457
+ outline: none;
458
+ }
459
+ .neo-textarea.affix {
460
+ padding: 0.75rem 2.5rem 0.75rem 0.75rem;
461
+ }
462
+ .neo-textarea-affix, .neo-textarea-suffix {
463
+ align-items: center;
464
+ margin: 0.25rem;
465
+ padding: 0.5rem;
466
+ }
467
+ .neo-textarea-suffix {
468
+ position: absolute;
469
+ right: 0.125rem;
470
+ bottom: 0.125rem;
471
+ color: var(--neo-textarea-suffix-color, inherit);
472
+ background-color: var(--neo-textarea-suffix-bg-color, transparent);
473
+ border: none;
474
+ border-radius: var(--neo-textarea-suffix-border-radius, var(--neo-border-radius));
475
+ }
476
+ .neo-textarea-affix {
477
+ position: absolute;
478
+ top: 0.125rem;
479
+ right: 0.125rem;
480
+ display: inline-grid;
481
+ grid-template-areas: "affix";
482
+ min-width: 2rem;
483
+ min-height: 2rem;
484
+ border: none;
485
+ border-radius: var(--neo-textarea-affix-border-radius, var(--neo-border-radius));
486
+ }
487
+ .neo-textarea-affix > * {
488
+ grid-area: affix;
489
+ }
490
+ .neo-textarea-affix-validation {
491
+ width: 100%;
492
+ height: 100%;
493
+ }
494
+ .neo-textarea::placeholder {
495
+ color: var(--neo-textarea-placeholder-color, var(--neo-text-color-disabled));
496
+ transition: opacity 0.3s ease;
497
+ }
498
+ .neo-textarea:read-only {
499
+ cursor: initial;
500
+ }
501
+ .neo-textarea:disabled {
502
+ color: var(--neo-text-color-disabled);
503
+ cursor: not-allowed;
504
+ resize: none;
505
+ }
506
+
507
+ .neo-textarea-clear {
508
+ align-items: center;
509
+ justify-content: center;
510
+ color: var(--neo-textarea-clear-color, inherit);
511
+ background-color: var(--neo-background-color-darker);
512
+ border: none;
513
+ border-radius: 50%;
514
+ aspect-ratio: 1;
515
+ }
516
+ .neo-textarea-clear:focus-visible {
517
+ color: var(--neo-close-color-focused, rgba(255, 0, 0, 0.75));
518
+ background-color: var(--neo-close-bg-color-focused, rgba(255, 0, 0, 0.05));
519
+ }
520
+ .neo-textarea-clear:hover {
521
+ color: var(--neo-color-warning, rgba(255, 0, 0, 0.75));
522
+ background-color: var(--neo-close-bg-color-focused, rgba(255, 0, 0, 0.05));
523
+ }
524
+ .neo-textarea-clear:disabled {
525
+ color: var(--neo-text-color-disabled);
526
+ cursor: not-allowed;
527
+ }
528
+
529
+ .neo-textarea-label-container {
530
+ display: flex;
531
+ flex: 1 1 auto;
532
+ flex-direction: column;
533
+ width: 100%;
534
+ }
535
+ .neo-textarea-label-container .neo-textarea-label {
536
+ display: flex;
537
+ min-height: var(--neo-textarea-label-height);
538
+ padding: 0 0.75rem;
539
+ overflow: hidden;
540
+ color: var(--neo-textarea-label-color, inherit);
541
+ text-wrap: stable;
542
+ text-overflow: ellipsis;
543
+ cursor: inherit;
544
+ transition: padding 0.3s ease, color 0.3s ease, font-size 0.3s ease, line-height 0.3s ease, top 0.3s ease, left 0.3s ease, right 0.3s ease, translate 0.3s ease;
545
+ }
546
+ .neo-textarea-label-container .neo-textarea-label.first {
547
+ transition: none;
548
+ }
549
+ .neo-textarea-label-container .neo-textarea-label.required::after {
550
+ margin-left: 0.1rem;
551
+ color: var(--neo-textarea-required-color, var(--neo-color-error-75));
552
+ font-size: var(--neo-font-size);
553
+ content: "*";
554
+ }
555
+ .neo-textarea-label-container.floating .neo-textarea-label {
556
+ color: var(--neo-textarea-floating-label-color, var(--neo-text-color-disabled));
557
+ translate: 0 calc(50% + 0.7rem - var(--neo-textarea-label-height) / 2);
558
+ }
559
+ .neo-textarea-label-container.floating .neo-textarea-label.required::after {
560
+ color: var(--neo-textarea-required-color, var(--neo-color-error-50));
561
+ }
562
+ .neo-textarea-label-container.floating ::placeholder {
563
+ opacity: 0;
564
+ }
565
+
566
+ .neo-textarea-group {
567
+ position: relative;
568
+ margin: var(--neo-shadow-margin, 0.6rem);
569
+ color: var(--neo-textarea-text-color, inherit);
570
+ background-color: var(--neo-textarea-bg-color, inherit);
571
+ border: var(--neo-border-width, 1px) var(--neo-textarea-border-color, transparent) solid;
572
+ border-radius: var(--neo-textarea-border-radius, var(--neo-border-radius));
573
+ box-shadow: var(--neo-textarea-box-shadow, var(--neo-box-shadow-flat));
574
+ cursor: text;
575
+ }
576
+ .neo-textarea-group.readonly {
577
+ cursor: initial;
578
+ }
579
+ .neo-textarea-group.borderless {
580
+ border-color: transparent !important;
581
+ }
582
+ .neo-textarea-group.raised {
583
+ margin: var(--neo-shadow-margin-lg, 1.125rem);
584
+ }
585
+ .neo-textarea-group.inset {
586
+ padding: 0.25rem;
587
+ }
588
+ .neo-textarea-group.hover.flat-hover:hover, .neo-textarea-group.hover.flat-hover:focus-within, .neo-textarea-group.flat:not(.borderless, .hover-flat:hover, .hover-flat:focus-within) {
589
+ border-color: var(--neo-textarea-border-color, var(--neo-border-color));
590
+ }
591
+ .neo-textarea-group:focus-within, .neo-textarea-group.hover:hover {
592
+ box-shadow: var(--neo-textarea-hover-shadow, var(--neo-box-shadow-flat));
593
+ }
594
+ .neo-textarea-group.disabled {
595
+ box-shadow: var(--neo-box-shadow-flat) !important;
596
+ opacity: var(--neo-textarea-opacity-disabled, var(--neo-opacity-disabled));
597
+ }
598
+ .neo-textarea-group.disabled:not(.borderless) {
599
+ border-color: var(--neo-btn-border-color-disabled, var(--neo-border-color-disabled)) !important;
600
+ }
601
+ .neo-textarea-group.disabled .neo-textarea-label {
602
+ color: unset;
603
+ }
604
+ .neo-textarea-group.rounded {
605
+ border-radius: var(--neo-textarea-border-radius, var(--neo-border-radius-lg));
606
+ }
607
+ .neo-textarea-group.rounded .neo-textarea {
608
+ --neo-scrollbar-button-height: 0.75rem;
609
+ padding: 0.75rem 1rem;
610
+ border-radius: var(--neo-border-radius-lg, 2rem);
611
+ }
612
+ .neo-textarea-group.rounded .neo-textarea.affix {
613
+ padding: 0.75rem 2.75rem 0.75rem 1rem;
614
+ }
615
+ .neo-textarea-group.rounded .neo-textarea-suffix, .neo-textarea-group.rounded .neo-textarea-affix {
616
+ right: 0.365rem;
617
+ }
618
+ .neo-textarea-group.rounded .neo-textarea-label-container {
619
+ padding-left: 0.5rem;
620
+ }
621
+ .neo-textarea-group.rounded .neo-textarea-label-container .neo-textarea-label {
622
+ padding: 0 1rem;
623
+ }
624
+ .neo-textarea-group[data-position=top] {
625
+ --neo-textarea-margin-top: calc(var(--neo-shadow-margin, 0.6rem) + var(--neo-textarea-label-height, var(--neo-line-height)));
626
+ margin-top: var(--neo-textarea-margin-top);
627
+ }
628
+ .neo-textarea-group[data-position=top] .neo-textarea-label-container .neo-textarea-label {
629
+ position: absolute;
630
+ top: calc(0% - var(--neo-textarea-margin-top));
631
+ }
632
+ .neo-textarea-group[data-position=left] {
633
+ --neo-textarea-margin-left: calc(var(--neo-shadow-margin, 0.6rem) + var(--neo-textarea-label-width, auto));
634
+ margin-left: var(--neo-textarea-margin-left);
635
+ }
636
+ .neo-textarea-group[data-position=left] .neo-textarea-label-container .neo-textarea-label {
637
+ position: absolute;
638
+ top: 0.75rem;
639
+ left: calc(0% - var(--neo-textarea-margin-left));
640
+ }
641
+ .neo-textarea-group[data-position=right] {
642
+ --neo-textarea-margin-right: calc(var(--shadow-margin, 0.6rem) + var(--neo-textarea-label-width, auto));
643
+ margin-right: var(--neo-textarea-margin-right);
644
+ }
645
+ .neo-textarea-group[data-position=right] .neo-textarea-label-container .neo-textarea-label {
646
+ position: absolute;
647
+ top: 0.75rem;
648
+ right: calc(0% - var(--neo-textarea-margin-right));
649
+ }
650
+ .neo-textarea-group[data-position=inside] .neo-textarea-label-container .neo-textarea {
651
+ padding: 0 1rem 0.5rem;
652
+ }
653
+ .neo-textarea-group[data-position=inside] .neo-textarea-label-container .neo-textarea-label {
654
+ padding: 0.75rem 1rem 0.2rem;
655
+ line-height: var(--neo-line-height-xs, 1rem);
656
+ }
657
+ .neo-textarea-group[data-position=inside] .neo-textarea-label-container:not(.floating) .neo-textarea-label {
658
+ font-size: var(--neo-font-size-sm, 0.875rem);
659
+ }
660
+ .neo-textarea-group[data-position=top] .neo-textarea-label-container.floating .neo-textarea-label, .neo-textarea-group[data-position=left] .neo-textarea-label-container.floating .neo-textarea-label, .neo-textarea-group[data-position=right] .neo-textarea-label-container.floating .neo-textarea-label {
661
+ top: 0;
662
+ }
663
+ .neo-textarea-group[data-position=left] .neo-textarea-label-container.floating .neo-textarea-label {
664
+ left: 0.5rem;
665
+ }
666
+ .neo-textarea-group[data-position=right] .neo-textarea-label-container.floating .neo-textarea-label {
667
+ right: calc(100% - var(--neo-textarea-label-width) - 0.5rem);
668
+ }
669
+ .neo-textarea-group.glass {
670
+ --neo-skeleton-color: var(--neo-glass-skeleton-color);
671
+ background-color: var(--neo-textarea-bg-color, var(--neo-glass-background-color));
672
+ border-color: var(--neo-textarea-border-color, var(--neo-glass-top-border-color) var(--neo-glass-right-border-color) var(--neo-glass-bottom-border-color) var(--neo-glass-left-border-color));
673
+ backdrop-filter: var(--neo-textarea-glass-blur, var(--neo-blur-4) var(--neo-saturate-2));
674
+ }
675
+ .neo-textarea-group.validation[data-valid=false] {
676
+ --neo-textarea-label-color: var(--neo-textarea-label-color-error, var(--neo-color-error));
677
+ --neo-textarea-floating-label-color: var(--neo-textarea-floating-label-color-error, var(--neo-color-error-50));
678
+ }
679
+ .neo-textarea-group.validation[data-valid=false] .neo-textarea-affix-validation {
680
+ color: var(--neo-textarea-validation-color-error, var(--neo-color-error));
681
+ }
682
+ .neo-textarea-group.validation[data-valid=true] {
683
+ --neo-textarea-label-color: var(--neo-textarea-label-color-success, var(--neo-color-success));
684
+ --neo-textarea-floating-label-color: var(--neo-textarea-floating-label-color-success, var(--neo-color-success-50));
685
+ }
686
+ .neo-textarea-group.validation[data-valid=true] .neo-textarea-affix-validation {
687
+ color: var(--neo-textarea-validation-color-success, var(--neo-color-success));
688
+ }
689
+ @starting-style {
690
+ .neo-textarea-group.start {
691
+ box-shadow: var(--neo-box-shadow-flat);
692
+ }
693
+ .neo-textarea-group.start:not(.borderless) {
694
+ border-color: var(--neo-textarea-border-color, var(--neo-border-color));
695
+ }
696
+ }
697
+ .neo-textarea-group.skeleton {
698
+ box-shadow: var(--neo-box-shadow-flat);
699
+ pointer-events: none;
700
+ --neo-skeleton-color-start: var(--neo-skeleton-color);
701
+ --neo-skeleton-color-end: oklch(from var(--neo-skeleton-color) calc(l - var(--neo-skeleton-color-step, 0.05)) c h);
702
+ color: var(--neo-skeleton-color-start);
703
+ background-color: var(--neo-skeleton-color-start);
704
+ border-color: var(--neo-skeleton-color-start);
705
+ transition: background-color 1s ease, color 1s ease, border-color 1s ease;
706
+ animation: skeleton 3s var(--neo-transition-skeleton) infinite;
707
+ animation-delay: 1s;
708
+ }
709
+ .neo-textarea-group.skeleton::before, .neo-textarea-group.skeleton::after,
710
+ .neo-textarea-group.skeleton :global(> *::before),
711
+ .neo-textarea-group.skeleton :global(> *::after),
712
+ .neo-textarea-group.skeleton :global(> *) {
713
+ visibility: hidden;
714
+ pointer-events: none;
715
+ }
716
+ @keyframes skeleton {
717
+ 0% {
718
+ color: var(--neo-skeleton-color-start);
719
+ background-color: var(--neo-skeleton-color-start);
720
+ border-color: var(--neo-skeleton-color-start);
721
+ }
722
+ 40% {
723
+ color: var(--neo-skeleton-color-end);
724
+ background-color: var(--neo-skeleton-color-end);
725
+ border-color: var(--neo-skeleton-color-end);
726
+ }
727
+ 80% {
728
+ color: var(--neo-skeleton-color-start);
729
+ background-color: var(--neo-skeleton-color-start);
730
+ border-color: var(--neo-skeleton-color-start);
731
+ }
732
+ 100% {
733
+ color: var(--neo-skeleton-color-start);
734
+ background-color: var(--neo-skeleton-color-start);
735
+ border-color: var(--neo-skeleton-color-start);
736
+ }
737
+ }
738
+ .neo-textarea-group.skeleton.glass {
739
+ --neo-skeleton-color: var(--neo-glass-skeleton-color);
740
+ }
741
+ .neo-textarea-group .neo-textarea {
742
+ scrollbar-gutter: stable;
743
+ }
744
+ .neo-textarea-group .neo-textarea::-webkit-scrollbar {
745
+ width: var(--neo-scrollbar-width, 0.45rem);
746
+ background-color: transparent;
747
+ border: none;
748
+ cursor: pointer;
749
+ }
750
+ .neo-textarea-group .neo-textarea::-webkit-scrollbar-button {
751
+ height: var(--neo-scrollbar-button-height, 2px);
752
+ }
753
+ .neo-textarea-group .neo-textarea::-webkit-scrollbar-thumb {
754
+ background-color: var(--neo-scrollbar-color);
755
+ border: none;
756
+ border-radius: var(--neo-border-radius);
757
+ cursor: pointer;
758
+ }
759
+ .neo-textarea-group .neo-textarea::-webkit-scrollbar-corner {
760
+ background-color: transparent;
761
+ background-clip: border-box;
762
+ border: none;
763
+ outline: none;
764
+ }
765
+ .neo-textarea-group .neo-textarea::-webkit-resizer {
766
+ background: url("~/assets/neo-icon-resizer-bottom-right.svg") no-repeat bottom;
767
+ background-clip: border-box;
768
+ }</style>