vanilla-jui 1.4.8 → 1.5.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 (53) hide show
  1. package/README.md +10 -4
  2. package/dist/index.d.ts +402 -977
  3. package/dist/index.js +1 -1
  4. package/dist/index.umd.js +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -3
  7. package/src/components/accordion.ts +467 -349
  8. package/src/components/flow.ts +779 -1133
  9. package/src/components/form.ts +666 -509
  10. package/src/components/menu.ts +199 -326
  11. package/src/components/modal.ts +510 -747
  12. package/src/components/offcanvas.ts +281 -361
  13. package/src/components/pagination.ts +207 -374
  14. package/src/components/swiper.ts +688 -1168
  15. package/src/components/tabs.ts +468 -788
  16. package/src/components/toc.ts +191 -244
  17. package/src/core/component.ts +332 -0
  18. package/src/core/motion.ts +298 -0
  19. package/src/core/presence.ts +233 -0
  20. package/src/core/scheduler.ts +91 -0
  21. package/src/core/view.ts +31 -0
  22. package/src/css/accordion.css +94 -3
  23. package/src/css/content.css +18 -24
  24. package/src/css/drop.css +4 -1
  25. package/src/css/form.css +8 -0
  26. package/src/css/menu.css +10 -5
  27. package/src/css/modal.css +2 -1
  28. package/src/css/offcanvas.css +5 -17
  29. package/src/css/swiper.css +2 -1
  30. package/src/css/tabs.css +8 -17
  31. package/src/css/themes/default/accordion.css +0 -48
  32. package/src/css/themes/default/swiper.css +1 -1
  33. package/src/css/themes/default/tabs.css +4 -6
  34. package/src/css/toast.css +1 -17
  35. package/src/css/tooltip.css +2 -1
  36. package/src/index.ts +8 -3
  37. package/src/locales/index.ts +3 -0
  38. package/src/primitives/drop.ts +253 -262
  39. package/src/primitives/loading.ts +22 -4
  40. package/src/primitives/parabola.ts +123 -164
  41. package/src/primitives/sticky.ts +297 -0
  42. package/src/primitives/theme.ts +164 -222
  43. package/src/primitives/toast.ts +309 -284
  44. package/src/primitives/tooltip.ts +65 -66
  45. package/src/utilities/browser.ts +38 -0
  46. package/src/utilities/dom.ts +0 -54
  47. package/src/utilities/refs.ts +61 -0
  48. package/src/utilities/state.ts +73 -0
  49. package/src/utilities/timer.ts +11 -31
  50. package/src/validation/validator.ts +289 -389
  51. package/src/components/sticky.ts +0 -297
  52. package/src/core/Component.ts +0 -377
  53. package/src/utilities/scheduler.ts +0 -156
@@ -1,4 +1,4 @@
1
- import { jsx } from 'vanilla-signal';
1
+ import { insert, jsx } from 'vanilla-signal';
2
2
 
3
3
  import { type DOMReference, resolveElement } from '../utilities/dom.ts';
4
4
  import { randomId } from '../utilities/id.ts';
@@ -45,21 +45,10 @@ interface ResolvedParabolaProps extends Record<string, unknown> {
45
45
  onHidden: ((parabola: ParabolaInstance) => void) | null;
46
46
  }
47
47
 
48
- interface ParabolaDOM {
49
- root: HTMLElement | null;
50
- from: Element | null;
51
- to: Element | null;
52
- balls: Set<HTMLElement>;
53
- }
54
-
55
48
  interface ParabolaRuntime {
56
49
  destroyed: boolean;
57
50
  }
58
51
 
59
- interface ParabolaCache {
60
- delays: Map<string, () => void>;
61
- }
62
-
63
52
  interface ParabolaPath {
64
53
  startX: number;
65
54
  startY: number;
@@ -67,10 +56,10 @@ interface ParabolaPath {
67
56
  endY: number;
68
57
  }
69
58
 
70
- interface ParabolaInstance {
71
- props: ResolvedParabolaProps;
72
- dom: ParabolaDOM;
73
- runtime: ParabolaRuntime;
59
+ export interface ParabolaInstance {
60
+ readonly props: ResolvedParabolaProps;
61
+ readonly element: HTMLElement | null;
62
+ readonly runtime: ParabolaRuntime;
74
63
  show(): Promise<boolean>;
75
64
  destroy(): void;
76
65
  }
@@ -135,39 +124,24 @@ function normalizeProps(input: ParabolaProps): ResolvedParabolaProps {
135
124
  };
136
125
  }
137
126
 
138
- class Parabola implements ParabolaInstance {
139
- props: ResolvedParabolaProps;
140
- dom: ParabolaDOM;
141
- runtime: ParabolaRuntime;
142
- cache: ParabolaCache;
143
-
144
- constructor(props: ParabolaProps = {}) {
145
- this.props = normalizeProps(props);
146
- this.dom = {
147
- root: null,
148
- from: resolveElement(this.props.from),
149
- to: resolveElement(this.props.to),
150
- balls: new Set(),
151
- };
152
- this.runtime = {
153
- destroyed: false,
154
- };
155
- this.cache = {
156
- delays: new Map(),
157
- };
158
-
159
- this.createRoot();
160
- }
161
-
162
- private createRoot(): void {
163
- if (this.runtime.destroyed) return;
164
-
165
- if (this.dom.root) {
166
- if (!this.dom.root.isConnected) document.body.appendChild(this.dom.root);
127
+ export function createParabola(input: ParabolaProps = {}): ParabolaInstance {
128
+ const props = normalizeProps(input);
129
+ const runtime: ParabolaRuntime = { destroyed: false };
130
+ const balls = new Set<HTMLElement>();
131
+ const delays = new Map<string, () => void>();
132
+ const frames = new Map<HTMLElement, number>();
133
+ let root: HTMLElement | null = null;
134
+ let from = resolveElement(props.from);
135
+ let to = resolveElement(props.to);
136
+ let parabola!: ParabolaInstance;
137
+
138
+ const createRoot = (): void => {
139
+ if (runtime.destroyed) return;
140
+ if (root) {
141
+ if (!root.isConnected) insert(document.body, root);
167
142
  return;
168
143
  }
169
-
170
- this.dom.root = jsx('div', {
144
+ root = jsx('div', {
171
145
  'data-parabola': 'root',
172
146
  style: {
173
147
  position: 'fixed',
@@ -176,19 +150,31 @@ class Parabola implements ParabolaInstance {
176
150
  zIndex: '9999',
177
151
  },
178
152
  }) as HTMLElement;
179
- document.body.appendChild(this.dom.root);
180
- }
181
-
182
- private createBall(path: ParabolaPath): HTMLElement | null {
183
- if (this.runtime.destroyed) return null;
184
-
185
- this.createRoot();
186
- if (!this.dom.root) return null;
187
-
188
- const { color, size } = this.props.ball;
153
+ insert(document.body, root);
154
+ };
155
+ const removeRootIfIdle = (): void => {
156
+ if (!runtime.destroyed || balls.size > 0) return;
157
+ root?.remove();
158
+ root = null;
159
+ };
160
+ const removeBall = (ball: HTMLElement, notify: boolean): void => {
161
+ if (!balls.has(ball)) return;
162
+ const frame = frames.get(ball);
163
+ if (frame) cancelAnimationFrame(frame);
164
+ frames.delete(ball);
165
+ ball.remove();
166
+ balls.delete(ball);
167
+ if (notify) props.onHidden?.(parabola);
168
+ removeRootIfIdle();
169
+ };
170
+ const createBall = (path: ParabolaPath): HTMLElement | null => {
171
+ if (runtime.destroyed) return null;
172
+ createRoot();
173
+ if (!root) return null;
174
+ const { color, size } = props.ball;
189
175
 
190
176
  const ball = jsx('div', {
191
- className: this.props.className.ball,
177
+ className: props.className.ball,
192
178
  'data-parabola': 'ball',
193
179
  style: {
194
180
  backgroundColor: color,
@@ -204,20 +190,13 @@ class Parabola implements ParabolaInstance {
204
190
  top: `${path.startY}px`,
205
191
  },
206
192
  }) as HTMLElement;
207
- this.dom.root.appendChild(ball);
208
- this.dom.balls.add(ball);
209
-
193
+ insert(root, ball);
194
+ balls.add(ball);
210
195
  return ball;
211
- }
212
-
213
- private resolveTargets(): void {
214
- this.dom.from = resolveElement(this.props.from);
215
- this.dom.to = resolveElement(this.props.to);
216
- }
217
-
218
- private calculatePath(): ParabolaPath | null {
219
- const fromRect = this.dom.from?.getBoundingClientRect();
220
- const toRect = this.dom.to?.getBoundingClientRect();
196
+ };
197
+ const calculatePath = (): ParabolaPath | null => {
198
+ const fromRect = from?.getBoundingClientRect();
199
+ const toRect = to?.getBoundingClientRect();
221
200
 
222
201
  if (!fromRect || !toRect) return null;
223
202
 
@@ -226,7 +205,7 @@ class Parabola implements ParabolaInstance {
226
205
 
227
206
  let startX: number;
228
207
  let startY: number;
229
- switch (this.props.direction) {
208
+ switch (props.direction) {
230
209
  case 'top-left':
231
210
  startX = fL + fW * 0.2;
232
211
  startY = fT + fH * 0.2;
@@ -252,20 +231,15 @@ class Parabola implements ParabolaInstance {
252
231
  const endY = tT + tH / 2;
253
232
 
254
233
  return { startX, startY, endX, endY };
255
- }
256
-
257
- private easeOutCubic(t: number): number {
258
- return 1 - (1 - t) ** 3;
259
- }
260
-
261
- private animate(
234
+ };
235
+ const animate = (
262
236
  ball: HTMLElement,
263
237
  startX: number,
264
238
  startY: number,
265
239
  endX: number,
266
240
  endY: number,
267
241
  duration = 800
268
- ): void {
242
+ ): void => {
269
243
  if (!ball.isConnected) return;
270
244
 
271
245
  const startTime = performance.now();
@@ -274,11 +248,14 @@ class Parabola implements ParabolaInstance {
274
248
  const peakOffset = -100;
275
249
 
276
250
  const step = (currentTime: number): void => {
277
- if (!ball.isConnected) return;
251
+ if (runtime.destroyed || !ball.isConnected) {
252
+ frames.delete(ball);
253
+ return;
254
+ }
278
255
 
279
256
  const elapsed = currentTime - startTime;
280
257
  const progress = Math.min(elapsed / duration, 1);
281
- const eased = this.easeOutCubic(progress);
258
+ const eased = 1 - (1 - progress) ** 3;
282
259
 
283
260
  const currentX = startX + deltaX * eased;
284
261
  const currentY =
@@ -292,97 +269,79 @@ class Parabola implements ParabolaInstance {
292
269
  ball.style.transform = `translate(-50%, -50%) scale(${1 - eased * 0.3})`;
293
270
 
294
271
  if (progress < 1) {
295
- requestAnimationFrame(step);
272
+ frames.set(ball, requestAnimationFrame(step));
296
273
  } else {
297
- this.removeBall(ball, true);
274
+ frames.delete(ball);
275
+ removeBall(ball, true);
298
276
  }
299
277
  };
300
278
 
301
- requestAnimationFrame(step);
302
- }
303
-
304
- private startDelay(callback: () => void, onCancel: () => void): void {
279
+ frames.set(ball, requestAnimationFrame(step));
280
+ };
281
+ const startDelay = (callback: () => void, onCancel: () => void): void => {
305
282
  const key = `parabola-show-${randomId()}`;
306
- this.cache.delays.set(key, onCancel);
307
-
308
- timer.start(key, this.props.showDelay, () => {
309
- this.cache.delays.delete(key);
283
+ delays.set(key, onCancel);
284
+ timer.start(key, props.showDelay, () => {
285
+ delays.delete(key);
310
286
  callback();
311
287
  });
312
- }
313
-
314
- private cancelDelays(): void {
315
- for (const [key, onCancel] of this.cache.delays) {
288
+ };
289
+ const cancelDelays = (): void => {
290
+ for (const [key, onCancel] of delays) {
316
291
  timer.cancel(key);
317
292
  onCancel();
318
293
  }
319
- this.cache.delays.clear();
320
- }
321
-
322
- private removeBall(ball: HTMLElement, notify: boolean): void {
323
- if (!this.dom.balls.has(ball)) return;
324
-
325
- ball.remove();
326
- this.dom.balls.delete(ball);
327
-
328
- if (notify) this.props.onHidden?.(this);
329
- this.removeRootIfIdle();
330
- }
331
-
332
- private removeRootIfIdle(): void {
333
- if (!this.runtime.destroyed || this.dom.balls.size > 0) return;
334
-
335
- this.dom.root?.remove();
336
- this.dom.root = null;
337
- }
338
-
339
- show(): Promise<boolean> {
340
- if (this.runtime.destroyed) return Promise.resolve(false);
341
-
342
- return new Promise((resolve) => {
343
- this.startDelay(
344
- () => {
345
- if (this.runtime.destroyed) {
346
- resolve(false);
347
- return;
348
- }
349
-
350
- this.resolveTargets();
351
-
352
- const path = this.calculatePath();
353
- if (!path) {
354
- resolve(false);
355
- return;
356
- }
357
-
358
- const ball = this.createBall(path);
359
- if (!ball) {
360
- resolve(false);
361
- return;
362
- }
363
-
364
- this.props.onShow?.(this);
365
-
366
- this.animate(ball, path.startX, path.startY, path.endX, path.endY);
367
- resolve(true);
368
- },
369
- () => resolve(false)
370
- );
371
- });
372
- }
373
-
374
- destroy(): void {
375
- if (this.runtime.destroyed) return;
376
-
377
- this.cancelDelays();
378
-
379
- this.dom.from = null;
380
- this.dom.to = null;
381
- this.runtime.destroyed = true;
382
- this.removeRootIfIdle();
383
- }
384
- }
294
+ delays.clear();
295
+ };
385
296
 
386
- export function createParabola(props: ParabolaProps = {}): ParabolaInstance {
387
- return new Parabola(props);
297
+ parabola = {
298
+ props,
299
+ runtime,
300
+ get element() {
301
+ return root;
302
+ },
303
+ show() {
304
+ if (runtime.destroyed) return Promise.resolve(false);
305
+ return new Promise((resolve) => {
306
+ startDelay(
307
+ () => {
308
+ if (runtime.destroyed) {
309
+ resolve(false);
310
+ return;
311
+ }
312
+ from = resolveElement(props.from);
313
+ to = resolveElement(props.to);
314
+ const path = calculatePath();
315
+ if (!path) {
316
+ resolve(false);
317
+ return;
318
+ }
319
+
320
+ const ball = createBall(path);
321
+ if (!ball) {
322
+ resolve(false);
323
+ return;
324
+ }
325
+
326
+ props.onShow?.(parabola);
327
+ animate(ball, path.startX, path.startY, path.endX, path.endY);
328
+ resolve(true);
329
+ },
330
+ () => resolve(false)
331
+ );
332
+ });
333
+ },
334
+ destroy() {
335
+ if (runtime.destroyed) return;
336
+ cancelDelays();
337
+ from = null;
338
+ to = null;
339
+ runtime.destroyed = true;
340
+ for (const ball of balls) removeBall(ball, false);
341
+ frames.clear();
342
+ removeRootIfIdle();
343
+ },
344
+ };
345
+ createRoot();
346
+ return parabola;
388
347
  }
@@ -0,0 +1,297 @@
1
+ import { createDeepStore, flushSync } from 'vanilla-signal';
2
+ import { randomId } from '../utilities/id.ts';
3
+ import { type DOMReference, all, requireContainer } from '../utilities/dom.ts';
4
+ import { type ResolveSchema, resolveProps } from '../utilities/types.ts';
5
+
6
+ type StickyOverflow = 'destroy' | 'ignore';
7
+
8
+ interface StickyProps extends Record<string, unknown> {
9
+ target?: DOMReference;
10
+ parent?: DOMReference;
11
+ max?: number;
12
+ top?: number;
13
+ gap?: number;
14
+ overflow?: StickyOverflow;
15
+ reactive?: boolean;
16
+ onReBuild?: ((sticky: StickyInstance) => void) | null;
17
+ }
18
+
19
+ interface ResolvedStickyProps extends Record<string, unknown> {
20
+ target: DOMReference;
21
+ parent: DOMReference;
22
+ max: number;
23
+ top: number;
24
+ gap: number;
25
+ overflow: StickyOverflow;
26
+ reactive: boolean;
27
+ onReBuild: ((sticky: StickyInstance) => void) | null;
28
+ }
29
+
30
+ interface StickyStateItem {
31
+ key: string;
32
+ index: number;
33
+ top: number;
34
+ }
35
+
36
+ interface StickyState extends Record<string, unknown> {
37
+ items: StickyStateItem[];
38
+ }
39
+
40
+ interface StickyOriginalStyle {
41
+ element: HTMLElement;
42
+ originalPosition: string;
43
+ originalTop: string;
44
+ originalZIndex: string;
45
+ }
46
+
47
+ interface StickyRuntime {
48
+ built: boolean;
49
+ destroyed: boolean;
50
+ reBuilding: boolean;
51
+ reBuildFrameId: number;
52
+ }
53
+
54
+ interface StickyCache {
55
+ originalStyles: Map<HTMLElement, StickyOriginalStyle>;
56
+ keys: WeakMap<HTMLElement, string>;
57
+ }
58
+
59
+ interface StickyInstance {
60
+ readonly props: ResolvedStickyProps;
61
+ readonly state: StickyState;
62
+ readonly runtime: StickyRuntime;
63
+ build(): StickyInstance;
64
+ reBuild(): StickyInstance;
65
+ destroy(): void;
66
+ }
67
+
68
+ const STICKY_PROPS_SCHEMA = {
69
+ target: { default: null },
70
+ parent: { default: null },
71
+ max: {
72
+ default: 10,
73
+ type: 'number',
74
+ integer: true,
75
+ greaterThan: 0,
76
+ },
77
+ top: {
78
+ default: 16,
79
+ type: 'number',
80
+ min: 0,
81
+ },
82
+ gap: {
83
+ default: 16,
84
+ type: 'number',
85
+ min: 0,
86
+ },
87
+ overflow: {
88
+ default: 'destroy',
89
+ type: 'string',
90
+ enum: ['destroy', 'ignore'],
91
+ },
92
+ reactive: { default: false, type: 'boolean' },
93
+ onReBuild: { default: null, types: ['function', 'null'] },
94
+ } satisfies ResolveSchema<StickyProps>;
95
+
96
+ function normalizeProps(input: StickyProps): ResolvedStickyProps {
97
+ const props = resolveProps(input, STICKY_PROPS_SCHEMA, 'Sticky.props');
98
+ return {
99
+ target: props.target as DOMReference,
100
+ parent: props.parent as DOMReference,
101
+ max: props.max as number,
102
+ top: props.top as number,
103
+ gap: props.gap as number,
104
+ overflow: props.overflow as StickyOverflow,
105
+ reactive: props.reactive as boolean,
106
+ onReBuild: props.onReBuild as ResolvedStickyProps['onReBuild'],
107
+ };
108
+ }
109
+
110
+ function uniqueElements(elements: readonly Node[]): HTMLElement[] {
111
+ const htmlElements = elements.filter(
112
+ (element): element is HTMLElement => element instanceof HTMLElement
113
+ );
114
+ return Array.from(new Set(htmlElements));
115
+ }
116
+
117
+ function resolveParent(parent: DOMReference): Element | null {
118
+ if (parent === false || parent == null) return null;
119
+ return requireContainer(parent, 'Sticky.parent', 'element');
120
+ }
121
+
122
+ function resolveTarget(
123
+ target: DOMReference,
124
+ parent: Element | null
125
+ ): HTMLElement[] {
126
+ if (target === false || target == null) return [];
127
+
128
+ if (typeof target === 'string') {
129
+ if (!parent) {
130
+ return uniqueElements(requireContainer(target, 'Sticky.target', 'array'));
131
+ }
132
+
133
+ const elements = uniqueElements(all<HTMLElement>(target, parent));
134
+ if (elements.length === 0) {
135
+ throw new Error('Sticky.target: target not found.');
136
+ }
137
+ return elements;
138
+ }
139
+
140
+ const elements = uniqueElements(
141
+ requireContainer(target, 'Sticky.target', 'array')
142
+ );
143
+ if (!parent) return elements;
144
+
145
+ const scopedElements = elements.filter(
146
+ (element) => element === parent || parent.contains(element)
147
+ );
148
+ if (scopedElements.length === 0) {
149
+ throw new Error('Sticky.target: target not found in parent.');
150
+ }
151
+ return scopedElements;
152
+ }
153
+
154
+ export function createSticky(props: StickyProps = {}): StickyInstance {
155
+ const settings = normalizeProps(props);
156
+ const state = createDeepStore({ items: [] }) as StickyState;
157
+ const runtime: StickyRuntime = {
158
+ built: false,
159
+ destroyed: false,
160
+ reBuilding: false,
161
+ reBuildFrameId: 0,
162
+ };
163
+ const cache: StickyCache = {
164
+ originalStyles: new Map(),
165
+ keys: new WeakMap(),
166
+ };
167
+ let parent: Element | null = null;
168
+ let targets: HTMLElement[] = [];
169
+ let observer: MutationObserver | null = null;
170
+ let sticky: StickyInstance;
171
+
172
+ const resolveOverflow = (elements: HTMLElement[]): HTMLElement[] => {
173
+ if (elements.length <= settings.max) return elements;
174
+ return settings.overflow === 'ignore' ? [] : elements.slice(-settings.max);
175
+ };
176
+
177
+ const restore = (): void => {
178
+ for (const item of cache.originalStyles.values()) restoreItem(item);
179
+ };
180
+
181
+ const restoreItem = (item: StickyOriginalStyle): void => {
182
+ item.element.style.position = item.originalPosition;
183
+ item.element.style.top = item.originalTop;
184
+ item.element.style.zIndex = item.originalZIndex;
185
+ };
186
+
187
+ const itemKey = (element: HTMLElement, index: number): string => {
188
+ if (element.id) return element.id;
189
+ const cached = cache.keys.get(element);
190
+ if (cached) return cached;
191
+ const key = `sticky-${randomId()}-${index}`;
192
+ cache.keys.set(element, key);
193
+ return key;
194
+ };
195
+
196
+ const rememberOriginalStyle = (element: HTMLElement): void => {
197
+ if (cache.originalStyles.has(element)) return;
198
+ cache.originalStyles.set(element, {
199
+ element,
200
+ originalPosition: element.style.position,
201
+ originalTop: element.style.top,
202
+ originalZIndex: element.style.zIndex,
203
+ });
204
+ };
205
+
206
+ const resolveTargets = (): HTMLElement[] => {
207
+ return resolveOverflow(resolveTarget(settings.target, parent));
208
+ };
209
+
210
+ const apply = (startTop = settings.top): number => {
211
+ let nextTop = startTop;
212
+ const items: StickyStateItem[] = [];
213
+ const activeTargets = new Set(targets);
214
+
215
+ for (const [element, item] of cache.originalStyles) {
216
+ if (!activeTargets.has(element)) {
217
+ restoreItem(item);
218
+ cache.originalStyles.delete(element);
219
+ }
220
+ }
221
+
222
+ targets.forEach((element, index) => {
223
+ rememberOriginalStyle(element);
224
+ element.style.position = 'sticky';
225
+ element.style.top = `${nextTop}px`;
226
+ items.push({ key: itemKey(element, index), index, top: nextTop });
227
+ nextTop += element.offsetHeight + settings.gap;
228
+ });
229
+
230
+ flushSync(() => {
231
+ state.items = items;
232
+ });
233
+ settings.onReBuild?.(sticky);
234
+ return nextTop;
235
+ };
236
+
237
+ const observeParent = (): void => {
238
+ observer?.disconnect();
239
+ observer = null;
240
+ if (!settings.reactive) return;
241
+ const root = parent || document.body;
242
+ observer = new MutationObserver(() => {
243
+ if (runtime.reBuilding || runtime.destroyed || !runtime.built) return;
244
+ runtime.reBuilding = true;
245
+ runtime.reBuildFrameId = requestAnimationFrame(() => {
246
+ runtime.reBuilding = false;
247
+ runtime.reBuildFrameId = 0;
248
+ sticky.reBuild();
249
+ });
250
+ });
251
+ observer.observe(root, { childList: true, subtree: true });
252
+ };
253
+
254
+ const build = (): StickyInstance => {
255
+ if (runtime.destroyed) throw new Error('Sticky.build: instance destroyed');
256
+ if (runtime.built) return sticky;
257
+
258
+ parent = resolveParent(settings.parent);
259
+ targets = resolveTargets();
260
+ runtime.built = true;
261
+ observeParent();
262
+ if (targets.length === 0) {
263
+ flushSync(() => {
264
+ state.items = [];
265
+ });
266
+ return sticky;
267
+ }
268
+ apply();
269
+ return sticky;
270
+ };
271
+
272
+ const reBuild = (): StickyInstance => {
273
+ if (!runtime.destroyed && runtime.built) {
274
+ targets = resolveTargets();
275
+ apply();
276
+ }
277
+ return sticky;
278
+ };
279
+
280
+ const destroy = (): void => {
281
+ if (runtime.destroyed) return;
282
+ if (runtime.reBuildFrameId) cancelAnimationFrame(runtime.reBuildFrameId);
283
+ observer?.disconnect();
284
+ observer = null;
285
+ restore();
286
+ runtime.destroyed = true;
287
+ runtime.built = false;
288
+ runtime.reBuilding = false;
289
+ runtime.reBuildFrameId = 0;
290
+ cache.originalStyles.clear();
291
+ targets = [];
292
+ parent = null;
293
+ };
294
+
295
+ sticky = { props: settings, state, runtime, build, reBuild, destroy };
296
+ return sticky;
297
+ }