lapikit 0.4.12 → 0.4.13

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.
@@ -4,6 +4,9 @@ const lapikitComponents = [
4
4
  'btn',
5
5
  'icon',
6
6
  'avatar',
7
+ 'dropdown',
8
+ 'popover',
9
+ 'tooltip',
7
10
  'textfield',
8
11
  'appbar',
9
12
  'btn',
@@ -0,0 +1,249 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from 'svelte';
3
+ import { useClassName, useStyles, clickOutside } from '../../utils/index.js';
4
+ import { makeComponentProps } from '../../compiler/mapped-code.js';
5
+ import { getPositions } from './dropdown.svelte.js';
6
+ import type { DropdownProps, ModelDropdownProps } from './dropdown.types.js';
7
+
8
+ let {
9
+ ref = $bindable(),
10
+ children,
11
+ activator,
12
+ dark = false,
13
+ light = false,
14
+ rounded,
15
+ position = 'bottom',
16
+ closeOnClick = false,
17
+ openOnHover = false,
18
+ color,
19
+ background,
20
+ class: className = '',
21
+ style: styleAttr = '',
22
+ 's-class': sClass,
23
+ 's-style': sStyle,
24
+ ...rest
25
+ }: DropdownProps = $props();
26
+
27
+ let safePosition = $derived(
28
+ position === 'top' || position === 'bottom' || position === 'left' || position === 'right'
29
+ ? position
30
+ : 'bottom'
31
+ );
32
+
33
+ let { classProps, styleProps, restProps } = $derived(
34
+ makeComponentProps(rest as Record<string, unknown>)
35
+ );
36
+
37
+ let componentClass = $derived(
38
+ useClassName({
39
+ baseClass: 'kit-dropdown-content',
40
+ className: `${className ?? ''}`.trim(),
41
+ sClass,
42
+ classProps
43
+ })
44
+ );
45
+
46
+ let componentStyle = $derived(
47
+ useStyles({
48
+ styleAttr,
49
+ sStyle,
50
+ styleProps
51
+ })
52
+ );
53
+
54
+ let mergedStyle = $derived(
55
+ [
56
+ componentStyle,
57
+ background ? `--kit-dropdown-background:${background}` : '',
58
+ color ? `--kit-dropdown-color:${color}` : '',
59
+ typeof rounded === 'string' && rounded.includes('px')
60
+ ? `--kit-dropdown-radius:${rounded}`
61
+ : ''
62
+ ]
63
+ .filter(Boolean)
64
+ .join('; ')
65
+ );
66
+
67
+ const positioner = getPositions();
68
+
69
+ let contentRef = $state<HTMLElement | null>(null);
70
+ let activatorRef = $state<HTMLElement | PointerEvent | null>(null);
71
+ let open = $state(false);
72
+ let axis = $state(positioner.values);
73
+ let innerHeight = $state(0);
74
+ let innerWidth = $state(0);
75
+ let scrollX = $state(0);
76
+ let scrollY = $state(0);
77
+ let timeoutId = $state<ReturnType<typeof setTimeout> | null>(null);
78
+
79
+ const clearHoverTimeout = () => {
80
+ if (timeoutId) {
81
+ clearTimeout(timeoutId);
82
+ timeoutId = null;
83
+ }
84
+ };
85
+
86
+ const updatePosition = () => {
87
+ if (!contentRef || !activatorRef) return;
88
+ positioner.update(activatorRef, contentRef, safePosition, false, true);
89
+ axis = positioner.values;
90
+ };
91
+
92
+ const handleToggle = (element: HTMLElement | PointerEvent | null) => {
93
+ if (element === null) return;
94
+ activatorRef = element;
95
+ open = !open;
96
+ };
97
+
98
+ const handleClose = () => {
99
+ clearHoverTimeout();
100
+ open = false;
101
+ };
102
+
103
+ const handleContentClick = () => {
104
+ if (closeOnClick && open) open = false;
105
+ };
106
+
107
+ const handleMouseEvent = (
108
+ state: 'open' | 'close',
109
+ element: HTMLElement | PointerEvent | null
110
+ ) => {
111
+ if (!openOnHover) return;
112
+
113
+ if (state === 'open') {
114
+ clearHoverTimeout();
115
+ if (element) activatorRef = element;
116
+ open = true;
117
+ return;
118
+ }
119
+
120
+ timeoutId = setTimeout(() => {
121
+ open = false;
122
+ timeoutId = null;
123
+ }, 150);
124
+ };
125
+
126
+ let model: ModelDropdownProps = {
127
+ get open() {
128
+ return open;
129
+ },
130
+ close: () => handleClose(),
131
+ toggle: (element) => handleToggle(element)
132
+ };
133
+
134
+ $effect(() => {
135
+ if (open && contentRef && activatorRef) {
136
+ updatePosition();
137
+ }
138
+ });
139
+
140
+ $effect(() => {
141
+ if (
142
+ open &&
143
+ contentRef &&
144
+ activatorRef &&
145
+ (scrollX > 0 || scrollY > 0 || innerHeight > 0 || innerWidth > 0)
146
+ ) {
147
+ updatePosition();
148
+ }
149
+ });
150
+
151
+ $effect(() => {
152
+ if (scrollX || scrollY) open = false;
153
+ });
154
+
155
+ $effect(() => {
156
+ ref = contentRef;
157
+ });
158
+
159
+ onDestroy(() => {
160
+ clearHoverTimeout();
161
+ });
162
+ </script>
163
+
164
+ <svelte:window bind:innerHeight bind:innerWidth bind:scrollX bind:scrollY />
165
+
166
+ {@render activator?.(model, (state, element) => handleMouseEvent(state, element))}
167
+
168
+ {#if open}
169
+ <div
170
+ bind:this={contentRef}
171
+ class={componentClass}
172
+ style={`left:${axis.x}px; top:${axis.y}px; ${mergedStyle}`}
173
+ role="menu"
174
+ data-light={light || undefined}
175
+ data-dark={dark || undefined}
176
+ data-rounded={rounded}
177
+ data-position={axis.location ?? safePosition}
178
+ onmouseover={() => handleMouseEvent('open', activatorRef)}
179
+ onmouseleave={() => handleMouseEvent('close', activatorRef)}
180
+ onclick={(event) => {
181
+ event.stopPropagation();
182
+ handleContentClick();
183
+ }}
184
+ use:clickOutside={{ exclude: [contentRef, activatorRef], onClose: handleClose }}
185
+ {...restProps}
186
+ >
187
+ {@render children?.()}
188
+ </div>
189
+ {/if}
190
+
191
+ <style>
192
+ .kit-dropdown-content {
193
+ --kit-dropdown-background: var(--kit-surface-1);
194
+ --kit-dropdown-color: var(--kit-fg);
195
+ --kit-dropdown-radius: 12px;
196
+ --kit-dropdown-border: color-mix(in oklab, var(--kit-fg), transparent 88%);
197
+ --kit-dropdown-shadow: 0 18px 40px -18px color-mix(in oklab, black 24%, transparent);
198
+
199
+ position: fixed;
200
+ z-index: 1800;
201
+ min-width: 12rem;
202
+ max-width: min(22rem, calc(100vw - 1rem));
203
+ padding: 0.375rem;
204
+ border: 1px solid var(--kit-dropdown-border);
205
+ border-radius: var(--kit-dropdown-radius);
206
+ background: var(--kit-dropdown-background);
207
+ color: var(--kit-dropdown-color);
208
+ box-shadow: var(--kit-dropdown-shadow);
209
+ transform-origin: top left;
210
+ animation: kit-dropdown-enter 140ms ease;
211
+ }
212
+
213
+ .kit-dropdown-content[data-light='true'] {
214
+ --kit-dropdown-background: color-mix(in oklab, white 94%, var(--kit-surface-1));
215
+ --kit-dropdown-color: var(--kit-fg);
216
+ }
217
+
218
+ .kit-dropdown-content[data-dark='true'] {
219
+ --kit-dropdown-background: color-mix(in oklab, black 78%, var(--kit-surface-3));
220
+ --kit-dropdown-color: white;
221
+ --kit-dropdown-border: color-mix(in oklab, white, transparent 80%);
222
+ }
223
+
224
+ .kit-dropdown-content[data-rounded='0'] {
225
+ --kit-dropdown-radius: 0;
226
+ }
227
+ .kit-dropdown-content[data-rounded='xs'] {
228
+ --kit-dropdown-radius: 2px;
229
+ }
230
+ .kit-dropdown-content[data-rounded='sm'] {
231
+ --kit-dropdown-radius: 4px;
232
+ }
233
+ .kit-dropdown-content[data-rounded='md'] {
234
+ --kit-dropdown-radius: 8px;
235
+ }
236
+ .kit-dropdown-content[data-rounded='lg'] {
237
+ --kit-dropdown-radius: 16px;
238
+ }
239
+ .kit-dropdown-content[data-rounded='xl'] {
240
+ --kit-dropdown-radius: 99999px;
241
+ }
242
+
243
+ @keyframes kit-dropdown-enter {
244
+ from {
245
+ opacity: 0;
246
+ scale: 0.98;
247
+ }
248
+ }
249
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { DropdownProps } from './dropdown.types.ts';
2
+ declare const Dropdown: import("svelte").Component<DropdownProps, {}, "ref">;
3
+ type Dropdown = ReturnType<typeof Dropdown>;
4
+ export default Dropdown;
@@ -0,0 +1,148 @@
1
+ import { innerWidth, innerHeight } from 'svelte/reactivity/window';
2
+ export function getPositions() {
3
+ // state
4
+ const axis = $state({
5
+ x: 0,
6
+ y: 0,
7
+ location: null
8
+ });
9
+ return {
10
+ get values() {
11
+ return axis;
12
+ },
13
+ update(activator, element, location, centered, avoidCollisions) {
14
+ if (!activator || !element)
15
+ return;
16
+ const elementRect = element.getBoundingClientRect();
17
+ if (!(activator instanceof HTMLElement)) {
18
+ if (activator.clientX + elementRect.width > innerWidth.current) {
19
+ axis.x = activator.clientX - elementRect.width;
20
+ }
21
+ else {
22
+ axis.x = activator.clientX;
23
+ }
24
+ if (activator.clientY + elementRect.height > innerHeight.current) {
25
+ axis.y = activator.clientY - elementRect.height;
26
+ }
27
+ else {
28
+ axis.y = activator.clientY;
29
+ }
30
+ }
31
+ else if (activator instanceof HTMLElement) {
32
+ const activatorRect = activator.getBoundingClientRect();
33
+ const spacing = 0;
34
+ const _activator = activatorRect.y + activatorRect.height;
35
+ const _element = elementRect.height + spacing;
36
+ if (location === 'top' || location === 'bottom') {
37
+ if (avoidCollisions) {
38
+ if (location === 'top') {
39
+ if (activatorRect.y - _element < 0) {
40
+ axis.y = activatorRect.bottom + spacing;
41
+ axis.location = 'bottom';
42
+ }
43
+ else {
44
+ axis.y = activatorRect.top - _element;
45
+ axis.location = 'top';
46
+ }
47
+ }
48
+ else {
49
+ if (_activator + _element > innerHeight.current) {
50
+ axis.y = activatorRect.top - _element;
51
+ axis.location = 'top';
52
+ }
53
+ else {
54
+ axis.y = activatorRect.bottom + spacing;
55
+ axis.location = 'bottom';
56
+ }
57
+ }
58
+ }
59
+ else {
60
+ if (location === 'top') {
61
+ axis.y = activatorRect.top - _element;
62
+ axis.location = 'top';
63
+ }
64
+ else {
65
+ axis.y = activatorRect.bottom + spacing;
66
+ axis.location = 'bottom';
67
+ }
68
+ }
69
+ if (centered &&
70
+ activatorRect.left - (elementRect.width - activatorRect.width) / 2 > 0 &&
71
+ activatorRect.left + elementRect.width < innerWidth.current) {
72
+ axis.x = activatorRect.left - (elementRect.width - activatorRect.width) / 2;
73
+ }
74
+ else if (activatorRect.left + elementRect.width > innerWidth.current) {
75
+ axis.x = activatorRect.left - (elementRect.width - activatorRect.width);
76
+ }
77
+ else {
78
+ axis.x = activatorRect.left;
79
+ }
80
+ }
81
+ else if (location === 'left' || location === 'right') {
82
+ if (avoidCollisions) {
83
+ if (location === 'left' && !(activatorRect.left - elementRect.width < 0)) {
84
+ axis.x = activatorRect.left - (elementRect.width + spacing);
85
+ axis.location = 'left';
86
+ }
87
+ else {
88
+ if (activatorRect.left + activatorRect.width + elementRect.width + spacing >
89
+ innerWidth.current) {
90
+ axis.x = activatorRect.left - (elementRect.width + spacing);
91
+ axis.location = 'left';
92
+ }
93
+ else {
94
+ axis.x = activatorRect.left + activatorRect.width + spacing;
95
+ axis.location = 'right';
96
+ }
97
+ }
98
+ }
99
+ else {
100
+ if (location === 'left') {
101
+ axis.x = activatorRect.left - (elementRect.width + spacing);
102
+ axis.location = 'left';
103
+ }
104
+ else {
105
+ axis.x = activatorRect.left + activatorRect.width + spacing;
106
+ axis.location = 'right';
107
+ }
108
+ }
109
+ if (centered &&
110
+ activatorRect.top - (elementRect.height - activatorRect.height) / 2 > 0 &&
111
+ activatorRect.top + elementRect.height < innerHeight.current) {
112
+ axis.y = activatorRect.top - (elementRect.height - activatorRect.height) / 2;
113
+ }
114
+ else if (activatorRect.y + elementRect.height > innerHeight.current) {
115
+ axis.y = activatorRect.y - elementRect.height + activatorRect.height;
116
+ }
117
+ else {
118
+ axis.y = activatorRect.y;
119
+ }
120
+ }
121
+ else {
122
+ if (centered &&
123
+ activatorRect.left - (elementRect.width - activatorRect.width) / 2 > 0 &&
124
+ activatorRect.left + elementRect.width < innerWidth.current) {
125
+ axis.x = activatorRect.left - (elementRect.width - activatorRect.width) / 2;
126
+ }
127
+ else if (activatorRect.left + elementRect.width > innerWidth.current) {
128
+ axis.x = activatorRect.left - (elementRect.width - activatorRect.width);
129
+ }
130
+ else {
131
+ axis.x = activatorRect.left;
132
+ }
133
+ if (centered &&
134
+ activatorRect.top - (elementRect.height - activatorRect.height) / 2 > 0 &&
135
+ activatorRect.top + elementRect.height < innerHeight.current) {
136
+ axis.y = activatorRect.top - (elementRect.height - activatorRect.height) / 2;
137
+ }
138
+ else if (activatorRect.bottom + elementRect.height > innerHeight.current) {
139
+ axis.y = activatorRect.top - elementRect.height;
140
+ }
141
+ else {
142
+ axis.y = activatorRect.bottom;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ };
148
+ }
@@ -0,0 +1,28 @@
1
+ import type { Component, PropValue, RoundedType } from '../../utils/types/index.js';
2
+ import type { Snippet } from 'svelte';
3
+ export type PositionElement = {
4
+ x: number;
5
+ y: number;
6
+ location: 'top' | 'bottom' | 'left' | 'right' | null;
7
+ };
8
+ export type ModelDropdownProps = {
9
+ open: boolean;
10
+ close: () => void;
11
+ toggle: (element: HTMLElement | PointerEvent | null) => void;
12
+ };
13
+ export interface DropdownProps extends Component {
14
+ ref?: HTMLElement | null;
15
+ dark?: boolean;
16
+ light?: boolean;
17
+ rounded?: RoundedType | string;
18
+ position?: 'top' | 'bottom' | 'left' | 'right';
19
+ openOnHover?: boolean;
20
+ closeOnClick?: boolean;
21
+ color?: string;
22
+ background?: string;
23
+ activator?: Snippet<[
24
+ ModelDropdownProps,
25
+ (state: 'open' | 'close', element: HTMLElement | PointerEvent | null) => void
26
+ ]>;
27
+ [key: string]: PropValue | Record<string, PropValue> | unknown;
28
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -6,6 +6,9 @@ export { default as KitIcon } from './icon/icon.svelte';
6
6
  export { default as KitCard } from './card/card.svelte';
7
7
  export { default as KitChip } from './chip/chip.svelte';
8
8
  export { default as KitAvatar } from './avatar/avatar.svelte';
9
+ export { default as KitDropdown } from './dropdown/dropdown.svelte';
10
+ export { default as KitPopover } from './popover/popover.svelte';
11
+ export { default as KitTooltip } from './tooltip/tooltip.svelte';
9
12
  export { default as KitTextfield } from './textfield/textfield.svelte';
10
13
  export { default as KitToolbar } from './toolbar/toolbar.svelte';
11
14
  export { default as KitList } from './list/list.svelte';
@@ -7,6 +7,9 @@ export { default as KitIcon } from './icon/icon.svelte';
7
7
  export { default as KitCard } from './card/card.svelte';
8
8
  export { default as KitChip } from './chip/chip.svelte';
9
9
  export { default as KitAvatar } from './avatar/avatar.svelte';
10
+ export { default as KitDropdown } from './dropdown/dropdown.svelte';
11
+ export { default as KitPopover } from './popover/popover.svelte';
12
+ export { default as KitTooltip } from './tooltip/tooltip.svelte';
10
13
  export { default as KitTextfield } from './textfield/textfield.svelte';
11
14
  export { default as KitToolbar } from './toolbar/toolbar.svelte';
12
15
  export { default as KitList } from './list/list.svelte';
@@ -0,0 +1,188 @@
1
+ <script lang="ts">
2
+ import { useClassName, useStyles, clickOutside } from '../../utils/index.js';
3
+ import { makeComponentProps } from '../../compiler/mapped-code.js';
4
+ import { getPositions } from './popover.svelte.js';
5
+ import type { ModelPopoverProps, PopoverProps } from './popover.types.js';
6
+
7
+ let {
8
+ ref = $bindable(),
9
+ open = $bindable(false),
10
+ children,
11
+ activator,
12
+ dark = false,
13
+ light = false,
14
+ rounded,
15
+ position = 'bottom',
16
+ color,
17
+ background,
18
+ class: className = '',
19
+ style: styleAttr = '',
20
+ 's-class': sClass,
21
+ 's-style': sStyle,
22
+ ...rest
23
+ }: PopoverProps = $props();
24
+
25
+ let safePosition = $derived(
26
+ position === 'top' || position === 'bottom' || position === 'left' || position === 'right'
27
+ ? position
28
+ : 'bottom'
29
+ );
30
+
31
+ let { classProps, styleProps, restProps } = $derived(
32
+ makeComponentProps(rest as Record<string, unknown>)
33
+ );
34
+
35
+ let componentClass = $derived(
36
+ useClassName({
37
+ baseClass: 'kit-popover-content',
38
+ className: `${className ?? ''}`.trim(),
39
+ sClass,
40
+ classProps
41
+ })
42
+ );
43
+
44
+ let componentStyle = $derived(
45
+ useStyles({
46
+ styleAttr,
47
+ sStyle,
48
+ styleProps
49
+ })
50
+ );
51
+
52
+ let mergedStyle = $derived(
53
+ [
54
+ componentStyle,
55
+ background ? `--kit-popover-background:${background}` : '',
56
+ color ? `--kit-popover-color:${color}` : '',
57
+ typeof rounded === 'string' && rounded.includes('px') ? `--kit-popover-radius:${rounded}` : ''
58
+ ]
59
+ .filter(Boolean)
60
+ .join('; ')
61
+ );
62
+
63
+ const positioner = getPositions();
64
+
65
+ let contentRef = $state<HTMLElement | null>(null);
66
+ let activatorRef = $state<HTMLElement | null>(null);
67
+ let axis = $state(positioner.values);
68
+ let innerHeight = $state(0);
69
+ let innerWidth = $state(0);
70
+ let scrollX = $state(0);
71
+ let scrollY = $state(0);
72
+
73
+ const handleToggle = (element: HTMLElement | null) => {
74
+ if (element === null) return;
75
+ activatorRef = element;
76
+ open = !open;
77
+ };
78
+
79
+ let model: ModelPopoverProps = {
80
+ toggle: (element) => handleToggle(element)
81
+ };
82
+
83
+ const updatePosition = () => {
84
+ if (!contentRef || !activatorRef) return;
85
+ positioner.update(activatorRef, contentRef, safePosition, false, true);
86
+ axis = positioner.values;
87
+ };
88
+
89
+ $effect(() => {
90
+ if (open && contentRef && activatorRef) {
91
+ updatePosition();
92
+ }
93
+ });
94
+
95
+ $effect(() => {
96
+ if (
97
+ open &&
98
+ contentRef &&
99
+ activatorRef &&
100
+ (scrollX > 0 || scrollY > 0 || innerHeight > 0 || innerWidth > 0)
101
+ ) {
102
+ updatePosition();
103
+ }
104
+ });
105
+
106
+ $effect(() => {
107
+ if (scrollX || scrollY) open = false;
108
+ });
109
+
110
+ $effect(() => {
111
+ ref = contentRef;
112
+ });
113
+ </script>
114
+
115
+ <svelte:window bind:innerHeight bind:innerWidth bind:scrollX bind:scrollY />
116
+
117
+ {@render activator?.(model)}
118
+
119
+ {#if open}
120
+ <div
121
+ bind:this={contentRef}
122
+ class={componentClass}
123
+ style={`left:${axis.x}px; top:${axis.y}px; ${mergedStyle}`}
124
+ role="dialog"
125
+ data-light={light || undefined}
126
+ data-dark={dark || undefined}
127
+ data-rounded={rounded}
128
+ data-position={axis.location ?? safePosition}
129
+ use:clickOutside={{ exclude: [contentRef, activatorRef], onClose: () => (open = false) }}
130
+ {...restProps}
131
+ >
132
+ {@render children?.()}
133
+ </div>
134
+ {/if}
135
+
136
+ <style>
137
+ .kit-popover-content {
138
+ --kit-popover-background: var(--kit-surface-1);
139
+ --kit-popover-color: var(--kit-fg);
140
+ --kit-popover-radius: 12px;
141
+ --kit-popover-border: color-mix(in oklab, var(--kit-fg), transparent 88%);
142
+ --kit-popover-shadow: 0 18px 40px -18px color-mix(in oklab, black 24%, transparent);
143
+
144
+ position: fixed;
145
+ z-index: 1900;
146
+ display: inline-block;
147
+ width: auto;
148
+ max-width: min(28rem, calc(100vw - 1rem));
149
+ padding: 1rem;
150
+ border: 1px solid var(--kit-popover-border);
151
+ border-radius: var(--kit-popover-radius);
152
+ background: var(--kit-popover-background);
153
+ color: var(--kit-popover-color);
154
+ box-shadow: var(--kit-popover-shadow);
155
+ opacity: 1;
156
+ transition: opacity 140ms ease;
157
+ }
158
+
159
+ .kit-popover-content[data-light='true'] {
160
+ --kit-popover-background: color-mix(in oklab, white 94%, var(--kit-surface-1));
161
+ --kit-popover-color: var(--kit-fg);
162
+ }
163
+
164
+ .kit-popover-content[data-dark='true'] {
165
+ --kit-popover-background: color-mix(in oklab, black 78%, var(--kit-surface-3));
166
+ --kit-popover-color: white;
167
+ --kit-popover-border: color-mix(in oklab, white, transparent 80%);
168
+ }
169
+
170
+ .kit-popover-content[data-rounded='0'] {
171
+ --kit-popover-radius: 0;
172
+ }
173
+ .kit-popover-content[data-rounded='xs'] {
174
+ --kit-popover-radius: 2px;
175
+ }
176
+ .kit-popover-content[data-rounded='sm'] {
177
+ --kit-popover-radius: 4px;
178
+ }
179
+ .kit-popover-content[data-rounded='md'] {
180
+ --kit-popover-radius: 8px;
181
+ }
182
+ .kit-popover-content[data-rounded='lg'] {
183
+ --kit-popover-radius: 16px;
184
+ }
185
+ .kit-popover-content[data-rounded='xl'] {
186
+ --kit-popover-radius: 99999px;
187
+ }
188
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { PopoverProps } from './popover.types.ts';
2
+ declare const Popover: import("svelte").Component<PopoverProps, {}, "ref" | "open">;
3
+ type Popover = ReturnType<typeof Popover>;
4
+ export default Popover;