@x33025/sveltely 0.1.6 → 0.1.8

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.
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import Spinner from '../components/Library/Spinner/Spinner.svelte';
3
+ import VStack from '../components/Library/VStack/VStack.svelte';
4
+
5
+ type Props = {
6
+ text: string;
7
+ };
8
+
9
+ let { text }: Props = $props();
10
+ </script>
11
+
12
+ <div class="sveltely-loader-overlay" role="status" aria-live="polite" aria-label={text}>
13
+ <VStack align="center" justify="center" gap="var(--sveltely-gap)">
14
+ <Spinner />
15
+ <span class="sveltely-loader-text">{text}</span>
16
+ </VStack>
17
+ </div>
18
+
19
+ <style>
20
+ .sveltely-loader-overlay {
21
+ position: absolute;
22
+ inset: 0;
23
+ z-index: 20;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ border-radius: inherit;
28
+ background: var(--sveltely-background-color, white);
29
+ color: var(--sveltely-primary-color, black);
30
+ padding: var(--sveltely-padding-y, 0.75rem) var(--sveltely-padding-x, 0.75rem);
31
+ pointer-events: auto;
32
+ text-align: center;
33
+ }
34
+
35
+ .sveltely-loader-text {
36
+ max-width: 100%;
37
+ overflow: hidden;
38
+ color: var(--sveltely-secondary-color, currentColor);
39
+ font-size: calc(var(--sveltely-font-size, 1rem) * 0.875);
40
+ line-height: 1.25;
41
+ text-overflow: ellipsis;
42
+ white-space: nowrap;
43
+ }
44
+ </style>
@@ -0,0 +1,6 @@
1
+ type Props = {
2
+ text: string;
3
+ };
4
+ declare const LoaderOverlay: import("svelte").Component<Props, {}, "">;
5
+ type LoaderOverlay = ReturnType<typeof LoaderOverlay>;
6
+ export default LoaderOverlay;
@@ -0,0 +1,11 @@
1
+ import type { Action } from 'svelte/action';
2
+ type LoaderTrigger = boolean | PromiseLike<unknown> | null | undefined;
3
+ export type LoaderOptions = LoaderTrigger | [text: string, loading: LoaderTrigger] | {
4
+ text?: string;
5
+ loading?: LoaderTrigger;
6
+ active?: LoaderTrigger;
7
+ promise?: PromiseLike<unknown> | null;
8
+ disabled?: boolean;
9
+ };
10
+ export declare const loader: Action<HTMLElement, LoaderOptions>;
11
+ export {};
@@ -0,0 +1,117 @@
1
+ import { mount, unmount } from 'svelte';
2
+ import LoaderOverlay from './LoaderOverlay.svelte';
3
+ const DEFAULT_TEXT = 'Loading';
4
+ const isPromiseLike = (value) => typeof value === 'object' &&
5
+ value !== null &&
6
+ 'then' in value &&
7
+ typeof value.then === 'function';
8
+ const parseOptions = (value = false) => {
9
+ if (Array.isArray(value)) {
10
+ return {
11
+ text: value[0] || DEFAULT_TEXT,
12
+ loading: value[1],
13
+ disabled: false
14
+ };
15
+ }
16
+ if (typeof value === 'object' && value !== null && !isPromiseLike(value)) {
17
+ return {
18
+ text: value.text || DEFAULT_TEXT,
19
+ loading: value.loading ?? value.active ?? value.promise ?? false,
20
+ disabled: value.disabled ?? false
21
+ };
22
+ }
23
+ return {
24
+ text: DEFAULT_TEXT,
25
+ loading: value,
26
+ disabled: false
27
+ };
28
+ };
29
+ export const loader = (node, initialOptions = false) => {
30
+ if (typeof document === 'undefined') {
31
+ return {
32
+ update() { },
33
+ destroy() { }
34
+ };
35
+ }
36
+ const originalPosition = node.style.position;
37
+ const originalAriaBusy = node.getAttribute('aria-busy');
38
+ let options = parseOptions(initialOptions);
39
+ let overlay = null;
40
+ let overlayText = '';
41
+ let promiseVersion = 0;
42
+ let appliedPosition = false;
43
+ const ensureHostPosition = () => {
44
+ if (getComputedStyle(node).position !== 'static')
45
+ return;
46
+ node.style.position = 'relative';
47
+ appliedPosition = true;
48
+ };
49
+ const resetHostPosition = () => {
50
+ if (!appliedPosition)
51
+ return;
52
+ if (node.style.position === 'relative') {
53
+ node.style.position = originalPosition;
54
+ }
55
+ appliedPosition = false;
56
+ };
57
+ const show = () => {
58
+ ensureHostPosition();
59
+ node.setAttribute('aria-busy', 'true');
60
+ if (overlay && overlayText === options.text) {
61
+ return;
62
+ }
63
+ if (overlay) {
64
+ void unmount(overlay);
65
+ }
66
+ overlayText = options.text;
67
+ overlay = mount(LoaderOverlay, {
68
+ target: node,
69
+ props: {
70
+ text: options.text
71
+ }
72
+ });
73
+ };
74
+ const hide = () => {
75
+ if (overlay) {
76
+ void unmount(overlay);
77
+ overlay = null;
78
+ overlayText = '';
79
+ }
80
+ if (originalAriaBusy === null) {
81
+ node.removeAttribute('aria-busy');
82
+ }
83
+ else {
84
+ node.setAttribute('aria-busy', originalAriaBusy);
85
+ }
86
+ resetHostPosition();
87
+ };
88
+ const render = () => {
89
+ const currentVersion = ++promiseVersion;
90
+ if (options.disabled || !options.loading) {
91
+ hide();
92
+ return;
93
+ }
94
+ show();
95
+ if (!isPromiseLike(options.loading))
96
+ return;
97
+ options.loading.then(() => {
98
+ if (currentVersion === promiseVersion)
99
+ hide();
100
+ }, () => {
101
+ if (currentVersion === promiseVersion)
102
+ hide();
103
+ });
104
+ };
105
+ render();
106
+ return {
107
+ update(nextOptions = false) {
108
+ options = parseOptions(nextOptions);
109
+ render();
110
+ },
111
+ destroy() {
112
+ promiseVersion++;
113
+ hide();
114
+ resetHostPosition();
115
+ }
116
+ };
117
+ };
@@ -20,5 +20,11 @@
20
20
  <div class="text-3xl font-semibold text-[var(--sveltely-primary-color)]">
21
21
  <AnimatedNumber {value} />
22
22
  </div>
23
- <Button label="Change number" variant="solid" onclick={bump} />
23
+ <Button
24
+ label="Change number"
25
+ background="var(--sveltely-active-color)"
26
+ borderColor="var(--sveltely-active-color)"
27
+ color="var(--sveltely-background-color)"
28
+ onclick={bump}
29
+ />
24
30
  </div>
@@ -3,8 +3,9 @@
3
3
  import type { Component } from 'svelte';
4
4
  import type { HTMLButtonAttributes } from 'svelte/elements';
5
5
  import { tooltip } from '../../../actions/tooltip';
6
+ import Button from '../Button';
6
7
  import Spinner from '../Spinner';
7
- import { extractStyleProps, surfaceStyle, type StyleProps } from '../../../style/surface';
8
+ import { extractStyleProps, type StyleProps } from '../../../style/surface';
8
9
 
9
10
  type Props = {
10
11
  icon?: Component<{ class?: string; size?: number | string }>;
@@ -45,6 +46,12 @@
45
46
  const isControlledLoading = $derived(loading !== undefined);
46
47
  const effectiveLoading = $derived(isControlledLoading ? loading : internalLoading);
47
48
  const effectiveDisabled = $derived(disabled || (disableWhileLoading && effectiveLoading));
49
+ const buttonStyleProps = $derived({
50
+ ...styleProps,
51
+ background: styleProps.background ?? 'var(--sveltely-active-color)',
52
+ borderColor: styleProps.borderColor ?? 'var(--sveltely-active-color)',
53
+ color: styleProps.color ?? 'var(--sveltely-background-color)'
54
+ });
48
55
 
49
56
  const handleClick = async () => {
50
57
  if (effectiveDisabled) return;
@@ -78,8 +85,6 @@
78
85
  const toRem = (value: number | string | undefined) =>
79
86
  value === undefined ? undefined : typeof value === 'number' ? `${value}rem` : value;
80
87
 
81
- const triggerStyle = $derived.by(() => surfaceStyle(styleProps, 'async-button'));
82
-
83
88
  const iconStyle = $derived.by(() => {
84
89
  const declarations: string[] = [];
85
90
 
@@ -101,103 +106,47 @@
101
106
  });
102
107
  </script>
103
108
 
104
- <button
105
- {type}
106
- use:tooltip={{ label: errorLabel ?? '', disabled: !errorLabel }}
107
- class="async-button inline-flex items-center disabled:cursor-not-allowed disabled:opacity-50 {variant ===
108
- 'iconOnly'
109
- ? 'async-button-icon-only'
110
- : ''}"
111
- style={triggerStyle}
112
- disabled={effectiveDisabled}
113
- aria-busy={effectiveLoading}
114
- aria-label={variant === 'iconOnly' ? label : undefined}
115
- data-error={error ? 'true' : 'false'}
116
- {...props}
117
- onclick={handleClick}
118
- >
119
- <span class="async-button-icon-frame inline-grid shrink-0 place-items-center" style={iconStyle}>
120
- {#if effectiveLoading}
121
- <Spinner size={iconSize !== undefined ? toRem(iconSize) : 'var(--async-button-icon-size)'} />
122
- {:else if error}
123
- <CircleAlertIcon class="async-button-icon text-red-600" />
124
- {:else if icon}
125
- {@const Icon = icon}
126
- <Icon class="async-button-icon" />
109
+ <span use:tooltip={{ label: errorLabel ?? '', disabled: !errorLabel }} class="async-button">
110
+ <Button
111
+ {type}
112
+ {...buttonStyleProps}
113
+ disabled={effectiveDisabled}
114
+ aria-busy={effectiveLoading}
115
+ aria-label={variant === 'iconOnly' ? label : undefined}
116
+ data-error={error ? 'true' : 'false'}
117
+ {...props}
118
+ onclick={handleClick}
119
+ >
120
+ <span class="async-button-icon-frame inline-grid shrink-0 place-items-center" style={iconStyle}>
121
+ {#if effectiveLoading}
122
+ <Spinner size={iconSize !== undefined ? toRem(iconSize) : 'var(--button-icon-size)'} />
123
+ {:else if error}
124
+ <CircleAlertIcon class="async-button-icon text-red-600" />
125
+ {:else if icon}
126
+ {@const Icon = icon}
127
+ <Icon class="async-button-icon" />
128
+ {/if}
129
+ </span>
130
+ {#if variant === 'iconAndLabel'}
131
+ <span class="async-button-text" style={labelStyle}>{label}</span>
127
132
  {/if}
128
- </span>
129
- {#if variant === 'iconAndLabel'}
130
- <span class="async-button-text" style={labelStyle}>{label}</span>
131
- {/if}
132
- </button>
133
+ </Button>
134
+ </span>
133
135
 
134
136
  <style>
135
137
  .async-button {
136
- --async-button-font-size: var(--sveltely-font-size);
137
- --async-button-scale: calc(var(--async-button-font-size) / 1rem);
138
- --async-button-icon-size: calc(var(--async-button-font-size) * 1.143);
139
- border: 1px solid var(--async-button-border-color, var(--sveltely-active-color));
140
- border-radius: var(--async-button-border-radius, var(--sveltely-border-radius));
141
- background: var(--async-button-background, var(--sveltely-active-color));
142
- color: var(--async-button-color, var(--sveltely-background-color));
143
- gap: var(--async-button-gap, var(--sveltely-gap));
144
- font-size: var(--async-button-font-size);
145
- line-height: 1.25;
146
- padding: var(
147
- --async-button-padding-y,
148
- calc(var(--sveltely-padding-y) * 0.67 * var(--async-button-scale))
149
- )
150
- var(--async-button-padding-x, calc(var(--sveltely-padding-x) * var(--async-button-scale)));
151
- transition:
152
- color 150ms,
153
- border-color 150ms,
154
- background-color 150ms;
155
- }
156
-
157
- .async-button:hover {
158
- background: var(--async-button-hover-background, var(--sveltely-active-hover-color));
159
- }
160
-
161
- .async-button-icon-only {
162
- padding: var(
163
- --async-button-padding-y,
164
- calc(var(--sveltely-padding-y) * 0.67 * var(--async-button-scale))
165
- )
166
- var(
167
- --async-button-padding-x,
168
- calc(var(--sveltely-padding-x) * 0.67 * var(--async-button-scale))
169
- );
170
- min-width: calc(
171
- (
172
- var(
173
- --async-button-padding-x,
174
- calc(var(--sveltely-padding-x) * 0.67 * var(--async-button-scale))
175
- ) *
176
- 2
177
- ) +
178
- 1rem
179
- );
180
- min-height: calc(
181
- (
182
- var(
183
- --async-button-padding-y,
184
- calc(var(--sveltely-padding-y) * 0.67 * var(--async-button-scale))
185
- ) *
186
- 2
187
- ) +
188
- 1rem
189
- );
138
+ display: inline-flex;
190
139
  }
191
140
 
192
141
  .async-button-icon-frame {
193
142
  display: inline-flex;
194
143
  align-items: center;
195
144
  justify-content: center;
196
- width: var(--async-button-icon-size);
197
- height: var(--async-button-icon-size);
145
+ width: var(--button-icon-size);
146
+ height: var(--button-icon-size);
198
147
  }
199
148
 
200
- .async-button-icon {
149
+ :global(.async-button-icon) {
201
150
  width: 100%;
202
151
  height: 100%;
203
152
  }
@@ -1,7 +1,7 @@
1
1
  <script module lang="ts">
2
2
  export const demo = {
3
3
  name: 'Button',
4
- description: 'Token-aware button primitive with optional icon support.',
4
+ description: 'Token-aware button primitive with composable content.',
5
5
  columnSpan: 2
6
6
  };
7
7
  </script>
@@ -14,6 +14,14 @@
14
14
 
15
15
  <HStack align="center" gap={0.75}>
16
16
  <Button label="Default" />
17
- <Button label="Solid" variant="solid" />
18
- <Button icon={SaveIcon} label="With icon" />
17
+ <Button
18
+ label="Solid"
19
+ background="var(--sveltely-active-color)"
20
+ borderColor="var(--sveltely-active-color)"
21
+ color="var(--sveltely-background-color)"
22
+ />
23
+ <Button>
24
+ <SaveIcon />
25
+ <span>With icon</span>
26
+ </Button>
19
27
  </HStack>
@@ -1,48 +1,33 @@
1
1
  <script lang="ts">
2
- import type { Component, Snippet } from 'svelte';
2
+ import type { Snippet } from 'svelte';
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
4
  import { extractStyleProps, surfaceStyle, type StyleProps } from '../../../style/surface';
5
5
 
6
6
  type Props = {
7
7
  children?: Snippet;
8
8
  label?: string;
9
- icon?: Component<{ class?: string; size?: number | string }>;
10
- iconSize?: number | string;
11
- iconColor?: string;
12
- variant?: 'default' | 'solid' | 'ghost';
13
9
  } & StyleProps &
14
10
  Omit<HTMLButtonAttributes, 'children' | 'class' | 'style'>;
15
11
 
16
- let {
17
- children,
18
- label,
19
- icon,
20
- iconSize,
21
- iconColor,
22
- variant = 'default',
23
- disabled = false,
24
- type = 'button',
25
- ...restProps
26
- }: Props = $props();
12
+ let { children, label, disabled = false, type = 'button', ...restProps }: Props = $props();
27
13
 
28
14
  const extractedStyleProps = $derived.by(() => extractStyleProps(restProps));
29
15
  const styleProps = $derived(extractedStyleProps.styleProps);
30
16
  const props = $derived(extractedStyleProps.restProps);
31
17
 
32
- const toRem = (value: number | string | undefined) =>
33
- value === undefined ? undefined : typeof value === 'number' ? `${value}rem` : value;
34
-
35
- const rootStyle = $derived.by(() => surfaceStyle(styleProps, 'button'));
36
- const iconStyle = $derived.by(() => {
37
- const declarations: string[] = [];
38
- if (iconColor !== undefined) {
39
- declarations.push(`color: ${iconColor};`);
18
+ const rootStyle = $derived.by(() => {
19
+ const { background, borderColor, color, ...surfaceProps } = styleProps;
20
+ const declarations = [surfaceStyle(surfaceProps, 'button')];
21
+ if (background !== undefined) {
22
+ declarations.push(`--button-background: ${background};`);
23
+ }
24
+ if (borderColor !== undefined) {
25
+ declarations.push(`--button-border-color: ${borderColor};`);
40
26
  }
41
- if (iconSize !== undefined) {
42
- const size = toRem(iconSize);
43
- declarations.push(`width: ${size};`, `height: ${size};`);
27
+ if (color !== undefined) {
28
+ declarations.push(`--button-color: ${color};`);
44
29
  }
45
- return declarations.join(' ');
30
+ return declarations.filter(Boolean).join(' ');
46
31
  });
47
32
  const iconOnly = $derived(!children && !label);
48
33
  </script>
@@ -52,18 +37,10 @@
52
37
  class="button inline-flex items-center justify-center disabled:cursor-not-allowed disabled:opacity-50 {iconOnly
53
38
  ? 'button-icon-only'
54
39
  : ''}"
55
- data-variant={variant}
56
40
  style={rootStyle}
57
41
  {disabled}
58
42
  {...props}
59
43
  >
60
- {#if icon}
61
- {@const Icon = icon}
62
- <span class="button-icon-frame inline-grid shrink-0 place-items-center" style={iconStyle}>
63
- <Icon class="button-icon" />
64
- </span>
65
- {/if}
66
-
67
44
  {#if children}
68
45
  {@render children()}
69
46
  {:else if label}
@@ -91,27 +68,15 @@
91
68
  background-color 150ms;
92
69
  }
93
70
 
94
- .button[data-variant='default']:hover {
95
- background: var(--button-hover-background, var(--sveltely-hover-color));
96
- }
97
-
98
- .button[data-variant='solid'] {
99
- border-color: var(--button-solid-border-color, var(--sveltely-active-color));
100
- background: var(--button-solid-background, var(--sveltely-active-color));
101
- color: var(--button-solid-color, var(--sveltely-background-color));
102
- }
103
-
104
- .button[data-variant='solid']:hover {
105
- background: var(--button-solid-hover-background, var(--sveltely-active-hover-color));
106
- }
107
-
108
- .button[data-variant='ghost'] {
109
- border-color: transparent;
110
- background: transparent;
111
- }
112
-
113
- .button[data-variant='ghost']:hover {
114
- background: var(--button-ghost-hover-background, var(--sveltely-hover-color));
71
+ .button:hover {
72
+ background: var(
73
+ --button-hover-background,
74
+ color-mix(
75
+ in oklab,
76
+ var(--button-background, var(--sveltely-background-color)) 88%,
77
+ var(--button-color, var(--sveltely-primary-color))
78
+ )
79
+ );
115
80
  }
116
81
 
117
82
  .button-icon-only {
@@ -127,16 +92,9 @@
127
92
  );
128
93
  }
129
94
 
130
- .button-icon-frame {
131
- display: inline-flex;
132
- align-items: center;
133
- justify-content: center;
95
+ .button :global(svg) {
134
96
  width: var(--button-icon-size);
135
97
  height: var(--button-icon-size);
136
- }
137
-
138
- .button-icon {
139
- width: 100%;
140
- height: 100%;
98
+ flex-shrink: 0;
141
99
  }
142
100
  </style>
@@ -1,17 +1,10 @@
1
- import type { Component, Snippet } from 'svelte';
1
+ import type { Snippet } from 'svelte';
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
3
  import { type StyleProps } from '../../../style/surface';
4
4
  type Props = {
5
5
  children?: Snippet;
6
6
  label?: string;
7
- icon?: Component<{
8
- class?: string;
9
- size?: number | string;
10
- }>;
11
- iconSize?: number | string;
12
- iconColor?: string;
13
- variant?: 'default' | 'solid' | 'ghost';
14
7
  } & StyleProps & Omit<HTMLButtonAttributes, 'children' | 'class' | 'style'>;
15
- declare const Button: Component<Props, {}, "">;
8
+ declare const Button: import("svelte").Component<Props, {}, "">;
16
9
  type Button = ReturnType<typeof Button>;
17
10
  export default Button;
@@ -32,7 +32,6 @@
32
32
  shrink,
33
33
  basis,
34
34
  border,
35
- overflow,
36
35
  align,
37
36
  justify,
38
37
  fontSize,
@@ -58,7 +57,6 @@
58
57
  shrink,
59
58
  basis,
60
59
  border,
61
- overflow,
62
60
  align,
63
61
  justify
64
62
  });
@@ -0,0 +1,74 @@
1
+ <script module lang="ts">
2
+ export const demo = {
3
+ name: 'Loader',
4
+ description: 'Overlay loading state for any component.',
5
+ columnSpan: 1,
6
+ rowSpan: 2
7
+ };
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import { loader } from '../../../actions/loader';
12
+ import Button from '../Button';
13
+
14
+ let pending = $state<Promise<void> | null>(null);
15
+
16
+ const startLoading = () => {
17
+ const next = new Promise<void>((resolve) => {
18
+ setTimeout(resolve, 1600);
19
+ });
20
+
21
+ pending = next;
22
+ next.finally(() => {
23
+ if (pending === next) {
24
+ pending = null;
25
+ }
26
+ });
27
+ };
28
+ </script>
29
+
30
+ <div class="loader-demo" use:loader={{ text: 'Loading preview...', promise: pending }}>
31
+ <div class="loader-demo-copy">
32
+ <strong>Preview panel</strong>
33
+ <span>Click the button to cover this component with the loader overlay.</span>
34
+ </div>
35
+ <Button label="Show loader" onclick={startLoading} disabled={pending !== null} />
36
+ </div>
37
+
38
+ <style>
39
+ .loader-demo {
40
+ display: flex;
41
+ min-height: 8rem;
42
+ width: 100%;
43
+ flex-direction: column;
44
+ align-items: flex-start;
45
+ justify-content: space-between;
46
+ gap: var(--sveltely-gap);
47
+ border: 1px solid var(--sveltely-border-color);
48
+ border-radius: var(--sveltely-border-radius);
49
+ background: color-mix(
50
+ in oklab,
51
+ var(--sveltely-background-color) 94%,
52
+ var(--sveltely-primary-color)
53
+ );
54
+ padding: var(--sveltely-padding-y) var(--sveltely-padding-x);
55
+ }
56
+
57
+ .loader-demo-copy {
58
+ display: flex;
59
+ min-width: 0;
60
+ flex-direction: column;
61
+ gap: calc(var(--sveltely-gap) * 0.5);
62
+ }
63
+
64
+ .loader-demo-copy strong {
65
+ font-size: var(--sveltely-font-size);
66
+ line-height: 1.25;
67
+ }
68
+
69
+ .loader-demo-copy span {
70
+ color: var(--sveltely-secondary-color);
71
+ font-size: calc(var(--sveltely-font-size) * 0.875);
72
+ line-height: 1.3;
73
+ }
74
+ </style>
@@ -0,0 +1,9 @@
1
+ export declare const demo: {
2
+ name: string;
3
+ description: string;
4
+ columnSpan: number;
5
+ rowSpan: number;
6
+ };
7
+ declare const Loader: import("svelte").Component<Record<string, never>, {}, "">;
8
+ type Loader = ReturnType<typeof Loader>;
9
+ export default Loader;
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { tick } from 'svelte';
3
3
  import type { Snippet } from 'svelte';
4
- import { surfaceStyle, type StyleProps } from '../../../style/surface';
4
+ import { extractLayoutProps, layoutStyle, type LayoutProps } from '../../../style/layout';
5
+ import { extractStyleProps, surfaceStyle, type StyleProps } from '../../../style/surface';
5
6
  import type { ScrollAxis } from '../../../style/scroll';
6
7
 
7
8
  export type ScrollGeometry = {
@@ -38,7 +39,8 @@
38
39
  onScroll?: (geometry: ScrollGeometry) => void;
39
40
  scrollGradient?: boolean;
40
41
  scrollGradientSize?: number | string;
41
- } & StyleProps;
42
+ } & LayoutProps &
43
+ StyleProps;
42
44
 
43
45
  let {
44
46
  children,
@@ -48,36 +50,25 @@
48
50
  onScroll,
49
51
  scrollGradient = true,
50
52
  scrollGradientSize = '1rem',
51
- fontSize,
52
- paddingX,
53
- paddingY,
54
- gap,
55
- borderRadius,
56
- inset,
57
- background,
58
- borderColor,
59
- color
53
+ ...restProps
60
54
  }: Props = $props();
61
55
 
62
- const styleProps = $derived({
63
- fontSize,
64
- paddingX,
65
- paddingY,
66
- gap,
67
- borderRadius,
68
- inset,
69
- background,
70
- borderColor,
71
- color
72
- });
56
+ const extractedLayoutProps = $derived.by(() => extractLayoutProps(restProps));
57
+ const layoutProps = $derived(extractedLayoutProps.layoutProps);
58
+ const afterLayoutProps = $derived(extractedLayoutProps.restProps);
59
+ const extractedStyleProps = $derived.by(() => extractStyleProps(afterLayoutProps));
60
+ const styleProps = $derived(extractedStyleProps.styleProps);
73
61
  const viewportStyle = $derived.by(() => surfaceStyle(styleProps, 'scroll-view'));
74
62
  const contentStyle = $derived.by(() => surfaceStyle(contentStyles, 'scroll-view-content'));
75
- let canScrollToTop = $state(false);
76
- let canScrollToBottom = $state(false);
77
- const scrollGradientEnabled = $derived(scrollGradient && axis !== 'horizontal');
78
63
  const scrollGradientStyle = $derived(
79
64
  `--scroll-view-gradient-size: ${typeof scrollGradientSize === 'number' ? `${scrollGradientSize}rem` : scrollGradientSize};`
80
65
  );
66
+ const rootStyle = $derived.by(() =>
67
+ [layoutStyle(layoutProps), viewportStyle, scrollGradientStyle].filter(Boolean).join(' ')
68
+ );
69
+ let canScrollToTop = $state(false);
70
+ let canScrollToBottom = $state(false);
71
+ const scrollGradientEnabled = $derived(scrollGradient && axis !== 'horizontal');
81
72
 
82
73
  function syncScrollGradient(geometry: ScrollGeometry) {
83
74
  if (!scrollGradientEnabled) return;
@@ -146,10 +137,10 @@
146
137
  class:scroll-view-vertical={axis === 'vertical'}
147
138
  class:scroll-view-horizontal={axis === 'horizontal'}
148
139
  class:scroll-view-both={axis === 'both'}
149
- class:scroll-view-gradient={scrollGradientEnabled}
140
+ class:scroll-view-has-gradient={scrollGradientEnabled}
150
141
  class:scroll-view-can-scroll-up={canScrollToTop}
151
142
  class:scroll-view-can-scroll-down={canScrollToBottom}
152
- style={[viewportStyle, scrollGradientStyle].filter(Boolean).join(' ')}
143
+ style={rootStyle}
153
144
  onscroll={handleScroll}
154
145
  onwheel={handleWheel}
155
146
  >
@@ -1,4 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import { type LayoutProps } from '../../../style/layout';
2
3
  import { type StyleProps } from '../../../style/surface';
3
4
  import type { ScrollAxis } from '../../../style/scroll';
4
5
  export type ScrollGeometry = {
@@ -34,7 +35,7 @@ type Props = {
34
35
  onScroll?: (geometry: ScrollGeometry) => void;
35
36
  scrollGradient?: boolean;
36
37
  scrollGradientSize?: number | string;
37
- } & StyleProps;
38
+ } & LayoutProps & StyleProps;
38
39
  declare const ScrollView: import("svelte").Component<Props, {}, "viewport">;
39
40
  type ScrollView = ReturnType<typeof ScrollView>;
40
41
  export default ScrollView;
@@ -19,20 +19,31 @@
19
19
  let { demos }: Props = $props();
20
20
  </script>
21
21
 
22
- <Grid
23
- columns="repeat(auto-fit, minmax(min(100%, 18rem), 1fr))"
24
- autoRows={14}
25
- gap={1}
26
- paddingX="calc(var(--sveltely-padding-x) * 2)"
27
- paddingY="calc(var(--sveltely-padding-y) * 2)"
28
- overflow="auto"
29
- >
30
- {#each demos as entry}
31
- {@const DemoComponent = entry.component}
32
- <GridItem columnSpan={entry.columnSpan ?? 1} rowSpan={entry.rowSpan ?? 1}>
33
- <HeroCard title={entry.name} description={entry.description}>
34
- <DemoComponent />
35
- </HeroCard>
36
- </GridItem>
37
- {/each}
38
- </Grid>
22
+ <div class="component-grid-scroll">
23
+ <Grid
24
+ columns="repeat(auto-fit, minmax(min(100%, 18rem), 1fr))"
25
+ autoRows={14}
26
+ gap={1}
27
+ paddingX="calc(var(--sveltely-padding-x) * 2)"
28
+ paddingY="calc(var(--sveltely-padding-y) * 2)"
29
+ >
30
+ {#each demos as entry (entry.name)}
31
+ {@const DemoComponent = entry.component}
32
+ <GridItem columnSpan={entry.columnSpan ?? 1} rowSpan={entry.rowSpan ?? 1}>
33
+ <HeroCard title={entry.name} description={entry.description}>
34
+ <DemoComponent />
35
+ </HeroCard>
36
+ </GridItem>
37
+ {/each}
38
+ </Grid>
39
+ </div>
40
+
41
+ <style>
42
+ .component-grid-scroll {
43
+ width: 100%;
44
+ height: 100%;
45
+ min-width: 0;
46
+ min-height: 0;
47
+ overflow: auto;
48
+ }
49
+ </style>
@@ -32,13 +32,13 @@
32
32
  <p class="max-w-3xl text-sm text-[var(--sveltely-secondary-color)]">{description}</p>
33
33
  {/if}
34
34
  {/if}
35
- <VStack grow shrink overflow="hidden" paddingX={0} paddingY={0}>
35
+ <div class="hero-card-content">
36
36
  {#if children}
37
37
  <HStack minWidth="min-content" minHeight="100%" align="center" justify="center">
38
38
  {@render children()}
39
39
  </HStack>
40
40
  {/if}
41
- </VStack>
41
+ </div>
42
42
  </VStack>
43
43
 
44
44
  <style>
@@ -50,4 +50,11 @@
50
50
  p {
51
51
  max-width: none;
52
52
  }
53
+
54
+ .hero-card-content {
55
+ min-width: 0;
56
+ min-height: 0;
57
+ flex: 1 1 0;
58
+ overflow: hidden;
59
+ }
53
60
  </style>
package/dist/index.d.ts CHANGED
@@ -2,8 +2,10 @@ export { motion, hover } from './actions/motion';
2
2
  export { portalHost, portalContent } from './actions/portal';
3
3
  export { tooltip } from './actions/tooltip';
4
4
  export type { TooltipOptions } from './actions/tooltip';
5
- export { LayoutAlignment, LayoutJustification, LayoutOverflow, LayoutSize } from './style/layout';
6
- export type { LayoutAlignmentValue, LayoutJustificationValue, LayoutOverflowValue, LayoutProps, LayoutSizeValue } from './style/layout';
5
+ export { loader } from './actions/loader';
6
+ export type { LoaderOptions } from './actions/loader';
7
+ export { LayoutAlignment, LayoutJustification, LayoutSize } from './style/layout';
8
+ export type { LayoutAlignmentValue, LayoutJustificationValue, LayoutProps, LayoutSizeValue } from './style/layout';
7
9
  export { LabelOrientation } from './style/label';
8
10
  export type { LabelOrientationValue } from './style/label';
9
11
  export { ImageFit, ImageLoading } from './style/media';
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  export { motion, hover } from './actions/motion';
2
2
  export { portalHost, portalContent } from './actions/portal';
3
3
  export { tooltip } from './actions/tooltip';
4
- export { LayoutAlignment, LayoutJustification, LayoutOverflow, LayoutSize } from './style/layout';
4
+ export { loader } from './actions/loader';
5
+ export { LayoutAlignment, LayoutJustification, LayoutSize } from './style/layout';
5
6
  export { LabelOrientation } from './style/label';
6
7
  export { ImageFit, ImageLoading } from './style/media';
7
8
  export { ScrollAxis } from './style/scroll';
@@ -3,13 +3,6 @@ export declare const LayoutSize: {
3
3
  readonly full: "full";
4
4
  readonly fit: "fit";
5
5
  };
6
- export declare const LayoutOverflow: {
7
- readonly visible: "visible";
8
- readonly hidden: "hidden";
9
- readonly clip: "clip";
10
- readonly auto: "auto";
11
- readonly scroll: "scroll";
12
- };
13
6
  export declare const LayoutAlignment: {
14
7
  readonly start: "start";
15
8
  readonly center: "center";
@@ -26,11 +19,9 @@ export declare const LayoutJustification: {
26
19
  readonly evenly: "evenly";
27
20
  };
28
21
  export type LayoutSizeValue = (typeof LayoutSize)[keyof typeof LayoutSize];
29
- export type LayoutOverflowValue = (typeof LayoutOverflow)[keyof typeof LayoutOverflow];
30
22
  export type LayoutAlignmentValue = (typeof LayoutAlignment)[keyof typeof LayoutAlignment];
31
23
  export type LayoutJustificationValue = (typeof LayoutJustification)[keyof typeof LayoutJustification];
32
24
  export type LayoutSize = StringWithAutocomplete<LayoutSizeValue> | number;
33
- export type LayoutOverflow = LayoutOverflowValue;
34
25
  export type LayoutAlignment = LayoutAlignmentValue;
35
26
  export type LayoutJustification = LayoutJustificationValue;
36
27
  export type LayoutProps = {
@@ -45,7 +36,6 @@ export type LayoutProps = {
45
36
  shrink?: boolean | number;
46
37
  basis?: number | string;
47
38
  border?: boolean | string;
48
- overflow?: LayoutOverflow;
49
39
  align?: LayoutAlignment;
50
40
  justify?: LayoutJustification;
51
41
  };
@@ -2,13 +2,6 @@ export const LayoutSize = {
2
2
  full: 'full',
3
3
  fit: 'fit'
4
4
  };
5
- export const LayoutOverflow = {
6
- visible: 'visible',
7
- hidden: 'hidden',
8
- clip: 'clip',
9
- auto: 'auto',
10
- scroll: 'scroll'
11
- };
12
5
  export const LayoutAlignment = {
13
6
  start: 'start',
14
7
  center: 'center',
@@ -36,7 +29,6 @@ const LAYOUT_PROP_KEYS = new Set([
36
29
  'shrink',
37
30
  'basis',
38
31
  'border',
39
- 'overflow',
40
32
  'align',
41
33
  'justify'
42
34
  ]);
@@ -115,9 +107,6 @@ export const layoutStyle = (styles) => {
115
107
  : `1px solid ${styles.border}`;
116
108
  declarations.push(`border: ${borderValue};`);
117
109
  }
118
- if (styles.overflow !== undefined) {
119
- declarations.push(`overflow: ${styles.overflow};`);
120
- }
121
110
  if (styles.align !== undefined) {
122
111
  declarations.push(`align-items: ${alignValues[styles.align]};`);
123
112
  }
package/dist/style.css CHANGED
@@ -212,6 +212,9 @@
212
212
  .relative {
213
213
  position: relative;
214
214
  }
215
+ .static {
216
+ position: static;
217
+ }
215
218
  .inset-0 {
216
219
  inset: calc(var(--spacing) * 0);
217
220
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x33025/sveltely",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",