@adobe/data-lit 0.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/dist/decorators/apply-decorator.d.ts +1 -0
  3. package/dist/decorators/apply-decorator.js +19 -0
  4. package/dist/decorators/apply-decorator.js.map +1 -0
  5. package/dist/decorators/apply-service-decorators.d.ts +1 -0
  6. package/dist/decorators/apply-service-decorators.js +13 -0
  7. package/dist/decorators/apply-service-decorators.js.map +1 -0
  8. package/dist/decorators/index.d.ts +3 -0
  9. package/dist/decorators/index.js +5 -0
  10. package/dist/decorators/index.js.map +1 -0
  11. package/dist/decorators/require-service.d.ts +1 -0
  12. package/dist/decorators/require-service.js +14 -0
  13. package/dist/decorators/require-service.js.map +1 -0
  14. package/dist/elements/application-element.d.ts +9 -0
  15. package/dist/elements/application-element.js +42 -0
  16. package/dist/elements/application-element.js.map +1 -0
  17. package/dist/elements/application-host.d.ts +16 -0
  18. package/dist/elements/application-host.js +39 -0
  19. package/dist/elements/application-host.js.map +1 -0
  20. package/dist/elements/database-element.d.ts +10 -0
  21. package/dist/elements/database-element.js +40 -0
  22. package/dist/elements/database-element.js.map +1 -0
  23. package/dist/elements/index.d.ts +3 -0
  24. package/dist/elements/index.js +5 -0
  25. package/dist/elements/index.js.map +1 -0
  26. package/dist/functions/index.d.ts +1 -0
  27. package/dist/functions/index.js +3 -0
  28. package/dist/functions/index.js.map +1 -0
  29. package/dist/functions/iterate-self-and-ancestors.d.ts +1 -0
  30. package/dist/functions/iterate-self-and-ancestors.js +24 -0
  31. package/dist/functions/iterate-self-and-ancestors.js.map +1 -0
  32. package/dist/hooks/attach-decorator.d.ts +4 -0
  33. package/dist/hooks/attach-decorator.js +26 -0
  34. package/dist/hooks/attach-decorator.js.map +1 -0
  35. package/dist/hooks/component/component.d.ts +10 -0
  36. package/dist/hooks/component/component.js +3 -0
  37. package/dist/hooks/component/component.js.map +1 -0
  38. package/dist/hooks/component/stack.d.ts +6 -0
  39. package/dist/hooks/component/stack.js +16 -0
  40. package/dist/hooks/component/stack.js.map +1 -0
  41. package/dist/hooks/index.d.ts +19 -0
  42. package/dist/hooks/index.js +21 -0
  43. package/dist/hooks/index.js.map +1 -0
  44. package/dist/hooks/use-connected.d.ts +2 -0
  45. package/dist/hooks/use-connected.js +32 -0
  46. package/dist/hooks/use-connected.js.map +1 -0
  47. package/dist/hooks/use-debounce.d.ts +8 -0
  48. package/dist/hooks/use-debounce.js +21 -0
  49. package/dist/hooks/use-debounce.js.map +1 -0
  50. package/dist/hooks/use-drag-observe.d.ts +25 -0
  51. package/dist/hooks/use-drag-observe.js +35 -0
  52. package/dist/hooks/use-drag-observe.js.map +1 -0
  53. package/dist/hooks/use-drag-transaction.d.ts +7 -0
  54. package/dist/hooks/use-drag-transaction.js +35 -0
  55. package/dist/hooks/use-drag-transaction.js.map +1 -0
  56. package/dist/hooks/use-draggable.d.ts +15 -0
  57. package/dist/hooks/use-draggable.js +96 -0
  58. package/dist/hooks/use-draggable.js.map +1 -0
  59. package/dist/hooks/use-effect.d.ts +3 -0
  60. package/dist/hooks/use-effect.js +14 -0
  61. package/dist/hooks/use-effect.js.map +1 -0
  62. package/dist/hooks/use-element.d.ts +7 -0
  63. package/dist/hooks/use-element.js +67 -0
  64. package/dist/hooks/use-element.js.map +1 -0
  65. package/dist/hooks/use-memo.d.ts +1 -0
  66. package/dist/hooks/use-memo.js +13 -0
  67. package/dist/hooks/use-memo.js.map +1 -0
  68. package/dist/hooks/use-observable-values.d.ts +4 -0
  69. package/dist/hooks/use-observable-values.js +9 -0
  70. package/dist/hooks/use-observable-values.js.map +1 -0
  71. package/dist/hooks/use-observable.d.ts +2 -0
  72. package/dist/hooks/use-observable.js +13 -0
  73. package/dist/hooks/use-observable.js.map +1 -0
  74. package/dist/hooks/use-ref.d.ts +3 -0
  75. package/dist/hooks/use-ref.js +7 -0
  76. package/dist/hooks/use-ref.js.map +1 -0
  77. package/dist/hooks/use-resize-observer.d.ts +12 -0
  78. package/dist/hooks/use-resize-observer.js +23 -0
  79. package/dist/hooks/use-resize-observer.js.map +1 -0
  80. package/dist/hooks/use-state.d.ts +2 -0
  81. package/dist/hooks/use-state.js +15 -0
  82. package/dist/hooks/use-state.js.map +1 -0
  83. package/dist/hooks/use-updated.d.ts +4 -0
  84. package/dist/hooks/use-updated.js +40 -0
  85. package/dist/hooks/use-updated.js.map +1 -0
  86. package/dist/hooks/use-window-event.d.ts +1 -0
  87. package/dist/hooks/use-window-event.js +11 -0
  88. package/dist/hooks/use-window-event.js.map +1 -0
  89. package/dist/hooks/with-hooks.d.ts +2 -0
  90. package/dist/hooks/with-hooks.js +16 -0
  91. package/dist/hooks/with-hooks.js.map +1 -0
  92. package/dist/index.d.ts +4 -0
  93. package/dist/index.js +5 -0
  94. package/dist/index.js.map +1 -0
  95. package/package.json +30 -0
  96. package/src/decorators/apply-decorator.ts +26 -0
  97. package/src/decorators/apply-service-decorators.ts +15 -0
  98. package/src/decorators/index.ts +4 -0
  99. package/src/decorators/require-service.ts +20 -0
  100. package/src/elements/application-element.ts +42 -0
  101. package/src/elements/application-host.ts +43 -0
  102. package/src/elements/database-element.ts +42 -0
  103. package/src/elements/index.ts +5 -0
  104. package/src/functions/index.ts +3 -0
  105. package/src/functions/iterate-self-and-ancestors.ts +23 -0
  106. package/src/hooks/attach-decorator.ts +32 -0
  107. package/src/hooks/component/component.ts +12 -0
  108. package/src/hooks/component/stack.ts +19 -0
  109. package/src/hooks/index.ts +21 -0
  110. package/src/hooks/use-connected.ts +41 -0
  111. package/src/hooks/use-debounce.ts +26 -0
  112. package/src/hooks/use-drag-observe.ts +59 -0
  113. package/src/hooks/use-drag-transaction.ts +46 -0
  114. package/src/hooks/use-draggable.ts +112 -0
  115. package/src/hooks/use-effect.ts +19 -0
  116. package/src/hooks/use-element.ts +83 -0
  117. package/src/hooks/use-memo.ts +16 -0
  118. package/src/hooks/use-observable-values.ts +10 -0
  119. package/src/hooks/use-observable.ts +15 -0
  120. package/src/hooks/use-ref.ts +8 -0
  121. package/src/hooks/use-resize-observer.ts +31 -0
  122. package/src/hooks/use-state.ts +19 -0
  123. package/src/hooks/use-updated.ts +56 -0
  124. package/src/hooks/use-window-event.ts +16 -0
  125. package/src/hooks/with-hooks.ts +22 -0
  126. package/src/index.ts +6 -0
  127. package/tsconfig.json +11 -0
  128. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,20 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ export function requireService() {
4
+ return function (
5
+ target: any,
6
+ propertyKey: string,
7
+ descriptor: PropertyDescriptor
8
+ ) {
9
+ const originalRender = descriptor.value;
10
+
11
+ descriptor.value = function (this: any) {
12
+ if (!this.service) {
13
+ return null;
14
+ }
15
+ return originalRender.call(this);
16
+ };
17
+
18
+ return descriptor;
19
+ };
20
+ }
@@ -0,0 +1,42 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { LitElement } from 'lit';
4
+ import { property } from 'lit/decorators.js';
5
+ import { attachDecorator, withHooks } from '../hooks/index.js';
6
+ import { iterateSelfAndAncestors } from '../functions/index.js';
7
+ import { Service, isService } from '@adobe/data/service';
8
+
9
+ export class ApplicationElement<MainService extends Service> extends LitElement {
10
+ @property({ type: Object, reflect: false })
11
+ service!: MainService;
12
+
13
+ constructor() {
14
+ super();
15
+ attachDecorator(this, 'render', withHooks);
16
+ }
17
+
18
+ connectedCallback(): void {
19
+ super.connectedCallback();
20
+
21
+ if (!this.service) {
22
+ const service = this.findAncestorService();
23
+ if (service) {
24
+ this.service = service;
25
+ }
26
+ }
27
+ }
28
+
29
+ protected findAncestorService(): MainService | void {
30
+ for (const element of iterateSelfAndAncestors(this)) {
31
+ const { service } = element as Partial<ApplicationElement<MainService>>;
32
+ if (isService(service)) {
33
+ return service;
34
+ }
35
+ }
36
+ }
37
+
38
+ public override render() {
39
+ throw new Error('render function must be overridden');
40
+ }
41
+
42
+ }
@@ -0,0 +1,43 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { LitElement, nothing, TemplateResult, css } from 'lit';
4
+ import { customElement, property } from 'lit/decorators.js';
5
+ import { Service } from '@adobe/data/service';
6
+ import { ApplicationElement } from './application-element.js';
7
+
8
+ const tagName = "application-host";
9
+
10
+ declare global {
11
+ interface HTMLElementTagNameMap {
12
+ 'application-element': ApplicationElement<Service>;
13
+ }
14
+ }
15
+
16
+ @customElement(tagName)
17
+ export class ApplicationHost<MainService extends Service = Service> extends LitElement {
18
+ static styles = css`
19
+ :host {
20
+ display: flex;
21
+ flex: 1 1 auto;
22
+ }
23
+ `;
24
+
25
+ @property()
26
+ createService!: () => Promise<MainService>;
27
+
28
+ @property({})
29
+ renderElement!: () => TemplateResult;
30
+
31
+ @property({ attribute: false })
32
+ public service!: MainService;
33
+
34
+ override async connectedCallback() {
35
+ super.connectedCallback();
36
+ this.service = await this.createService();
37
+ }
38
+
39
+ override render() {
40
+ return this.service ? this.renderElement() : nothing;
41
+ }
42
+
43
+ }
@@ -0,0 +1,42 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { LitElement } from 'lit';
4
+ import { property } from 'lit/decorators.js';
5
+ import { iterateSelfAndAncestors } from '../functions/index.js';
6
+ import { Database } from '@adobe/data/ecs';
7
+ import { attachDecorator, withHooks } from '../index.js';
8
+
9
+ export abstract class DatabaseElement<P extends Database.Plugin> extends LitElement {
10
+
11
+ @property({ type: Object, reflect: false })
12
+ service!: Database.Plugin.ToDatabase<P>;
13
+
14
+ constructor() {
15
+ super();
16
+ attachDecorator(this, 'render', withHooks);
17
+ }
18
+
19
+ abstract get plugin(): P;
20
+
21
+ connectedCallback(): void {
22
+ if (!this.service) {
23
+ const service = this.findAncestorDatabase();
24
+ this.service = (service?.extend(this.plugin) ?? Database.create(this.plugin)) as unknown as Database.Plugin.ToDatabase<P>;
25
+ }
26
+ super.connectedCallback();
27
+ }
28
+
29
+ protected findAncestorDatabase(): Database | void {
30
+ for (const element of iterateSelfAndAncestors(this)) {
31
+ const { service } = element as Partial<DatabaseElement<any>>;
32
+ if (Database.is(service)) {
33
+ return service;
34
+ }
35
+ }
36
+ }
37
+
38
+ public override render() {
39
+ throw new Error('render function must be overridden');
40
+ }
41
+
42
+ }
@@ -0,0 +1,5 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ export * from "./application-host.js";
4
+ export * from "./application-element.js";
5
+ export * from "./database-element.js";
@@ -0,0 +1,3 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ export * from "./iterate-self-and-ancestors.js";
@@ -0,0 +1,23 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ export function* iterateSelfAndAncestors(element: Element): IterableIterator<Element> {
4
+ let current: Element | null = element;
5
+
6
+ while (current) {
7
+ yield current;
8
+
9
+ if (current instanceof HTMLSlotElement && current.assignedSlot) {
10
+ // Move to the slot's parent if the current element is assigned to a slot
11
+ current = current.assignedSlot;
12
+ } else if (current.parentNode instanceof Element) {
13
+ // Move up to the parent node if it is an element
14
+ current = current.parentNode;
15
+ } else if ((current.getRootNode() as ShadowRoot).host) {
16
+ // Move to the host element if we're at a shadow root
17
+ current = (current.getRootNode() as ShadowRoot).host;
18
+ } else {
19
+ // If no more ancestors, stop the iteration
20
+ current = null;
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,32 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ /**
4
+ * Attacheds a decorator to a method on an object and reassigns the resulting value back to the object.
5
+ */
6
+ export function attachDecorator<T, K extends keyof T>(
7
+ obj: T,
8
+ property: K,
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
10
+ decorator: (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T[K]>) => PropertyDescriptor
11
+ ): void {
12
+ // Get the descriptor of the property
13
+ let descriptor = Object.getOwnPropertyDescriptor(obj, property);
14
+
15
+ if (!descriptor) {
16
+ if (!obj[property]) {
17
+ throw new Error(`Property ${property.toString()} does not exist on object`);
18
+ }
19
+ descriptor = {
20
+ value: obj[property],
21
+ writable: true,
22
+ enumerable: true,
23
+ configurable: true,
24
+ };
25
+ }
26
+
27
+ // Apply the decorator to the descriptor
28
+ const decoratedDescriptor = decorator(obj, String(property), descriptor);
29
+
30
+ // Reassign the decorated method back to the object
31
+ obj[property] = decoratedDescriptor.value;
32
+ }
@@ -0,0 +1,12 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ export interface Component extends EventTarget {
4
+ /**
5
+ * Is this component active and connected to the containing dom?
6
+ */
7
+ readonly isConnected: boolean;
8
+ hookIndex: number
9
+ hooks: any[]
10
+ updatedListeners: Set<() => void>
11
+ requestUpdate(): void
12
+ }
@@ -0,0 +1,19 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import type { Component } from "./component.js";
4
+
5
+ const activeComponentStack: Component[] = [];
6
+ export const Component_stack = {
7
+ active(): Component {
8
+ return activeComponentStack[activeComponentStack.length - 1]!;
9
+ },
10
+ push(component: Component) {
11
+ component.hookIndex = 0;
12
+ component.hooks ??= [];
13
+ activeComponentStack.push(component)
14
+ },
15
+ pop() {
16
+ activeComponentStack.pop()
17
+ }
18
+ } as const;
19
+
@@ -0,0 +1,21 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ export * from "./component/component.js";
4
+ export * from "./component/stack.js";
5
+ export * from "./use-state.js";
6
+ export * from "./use-effect.js";
7
+ export * from "./use-connected.js";
8
+ export * from "./use-memo.js";
9
+ export * from "./use-ref.js";
10
+ export * from "./use-observable-values.js";
11
+ export * from "./use-observable.js";
12
+ export * from "./use-window-event.js";
13
+ export * from "./use-resize-observer.js";
14
+ export * from "./use-debounce.js";
15
+ export * from "./use-element.js";
16
+ export * from "./with-hooks.js";
17
+ export * from "./attach-decorator.js";
18
+ export * from "./use-drag-transaction.js";
19
+ export * from "./use-drag-observe.js";
20
+ export * from "./use-draggable.js";
21
+ export * from "./use-updated.js";
@@ -0,0 +1,41 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import type { EffectCallback } from "./use-effect.js";
4
+ import { useEffect } from "./use-effect.js";
5
+ import { Component_stack } from "./component/stack.js";
6
+
7
+ export function useConnected(callback: EffectCallback, dependencies?: unknown[]) {
8
+ const component = Component_stack.active();
9
+
10
+ let disconnect: (() => void) | void;
11
+ function onConnect() {
12
+ if (!disconnect) {
13
+ disconnect = callback();
14
+ }
15
+ }
16
+
17
+ function onDisconnect() {
18
+ if (disconnect) {
19
+ disconnect?.();
20
+ disconnect = undefined;
21
+ }
22
+ }
23
+
24
+ // TODO
25
+ if (component.isConnected) {
26
+ onConnect();
27
+ }
28
+
29
+ useEffect(() => {
30
+ component.addEventListener("connected", onConnect);
31
+ component.addEventListener("disconnected", onDisconnect);
32
+
33
+ return () => {
34
+ component.removeEventListener("connected", onConnect);
35
+ component.removeEventListener("disconnected", onDisconnect);
36
+
37
+ onDisconnect();
38
+ }
39
+
40
+ }, dependencies);
41
+ }
@@ -0,0 +1,26 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { useEffect } from "./use-effect.js";
4
+ import { useState } from "./use-state.js";
5
+
6
+ /**
7
+ * Hook to debounce a value. Returns the debounced value after the specified delay.
8
+ *
9
+ * @param value - The value to debounce
10
+ * @param delay - Delay in milliseconds before updating the debounced value
11
+ * @returns The debounced value
12
+ */
13
+ export function useDebounce<T>(value: T, delay: number): T {
14
+ const [debouncedValue, setDebouncedValue] = useState(value);
15
+
16
+ useEffect(() => {
17
+ const timeoutId = window.setTimeout(() => {
18
+ setDebouncedValue(value);
19
+ }, delay);
20
+
21
+ return () => clearTimeout(timeoutId);
22
+ }, [value, delay]);
23
+
24
+ return debouncedValue;
25
+ }
26
+
@@ -0,0 +1,59 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { DraggableProps, useDraggable } from './use-draggable.js';
4
+ import { useElement } from './use-element.js';
5
+ import { Observe } from '@adobe/data/observe';
6
+ import { Vec2 } from '@adobe/data/math';
7
+
8
+ export type DragObserveProps = Pick<
9
+ DraggableProps,
10
+ 'minDragDistance' | 'dragCursor' | 'addPlaceholder' | 'stopPropagation'
11
+ >;
12
+ export type DragStart = {
13
+ readonly type: 'start' | 'cancel';
14
+ };
15
+ export type DragMove = {
16
+ readonly type: 'move';
17
+ readonly delta: Vec2;
18
+ readonly position: Vec2;
19
+ };
20
+ export type DragEnd = {
21
+ readonly type: 'end';
22
+ readonly delta: Vec2;
23
+ readonly position: Vec2;
24
+ };
25
+ export type DragState = DragStart | DragMove | DragEnd;
26
+
27
+ /**
28
+ * This hook creates a drag observe function that can be used to observe the drag state of an element.
29
+ * This is normally not used directly, but rather through the `useDragTransaction` hook.
30
+ * @param props
31
+ * @returns
32
+ */
33
+ export function useDragObserve(props: DragObserveProps, dependencies: unknown[]) {
34
+ const [dragState, setDragState] = Observe.createEvent<DragState>();
35
+ const element = useElement();
36
+ useDraggable(element,
37
+ {
38
+ ...props,
39
+ onDragStart: _e => {
40
+ setDragState({ type: 'start' });
41
+ },
42
+ onDrag: (_e, position, delta) => {
43
+ setDragState({
44
+ type: 'move',
45
+ position,
46
+ delta,
47
+ });
48
+ },
49
+ onDragEnd: (_e, position, delta) => {
50
+ setDragState({ type: 'end', position, delta });
51
+ },
52
+ onDragCancel: () => {
53
+ setDragState({ type: 'cancel' });
54
+ },
55
+ },
56
+ dependencies
57
+ );
58
+ return dragState;
59
+ }
@@ -0,0 +1,46 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { DragEnd, DragMove, DragObserveProps, useDragObserve } from './use-drag-observe.js';
4
+ import { useEffect } from "./use-effect.js";
5
+ import type { AsyncArgsProvider } from '@adobe/data/ecs';
6
+ import { Observe } from '@adobe/data/observe';
7
+
8
+ export type DragTransactionProps<T> = DragObserveProps & {
9
+ transaction: (asyncArgs: AsyncArgsProvider<T>) => void;
10
+ update: (drag: DragMove | DragEnd) => T | void;
11
+ };
12
+
13
+ export function useDragTransaction<T>(props: DragTransactionProps<T>, dependencies: unknown[]) {
14
+ const { transaction, update } = props;
15
+ const dragObserve = useDragObserve(props, dependencies);
16
+ const startDragTransaction = () => {
17
+ let done = false;
18
+ // we start the transaction by calling it with an async generator that will yield the args asynchronously
19
+ transaction(
20
+ // we create the async generator by mapping the dragObserve state changes to transaction args
21
+ () =>
22
+ Observe.toAsyncGenerator(
23
+ // this uses the withMap function and the provided update and finish functions
24
+ // any type 'start' will just be ignored and filtered out
25
+ Observe.withFilter(dragObserve, value => {
26
+ if (value.type === 'end') {
27
+ done = true;
28
+ return update(value);
29
+ }
30
+ if (value.type === 'move') {
31
+ return update(value);
32
+ }
33
+ }),
34
+ _value => done
35
+ )
36
+ );
37
+ };
38
+ // now we will observe the drag state and start a new transaction whenever a drag starts
39
+ useEffect(() => {
40
+ return dragObserve(value => {
41
+ if (value.type === 'start') {
42
+ startDragTransaction();
43
+ }
44
+ });
45
+ }, dependencies);
46
+ }
@@ -0,0 +1,112 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { Vec2 } from '@adobe/data/math';
4
+ import { useEffect } from './use-effect.js';
5
+
6
+ function toCssUnitString(value: number): string {
7
+ return `${value}px`;
8
+ }
9
+
10
+ export interface DraggableProps {
11
+ // onDragStart should return the initial position of the element
12
+ onDragStart: (e: PointerEvent) => Vec2 | void;
13
+ onDrag: (e: PointerEvent, newPosition: Vec2, delta: Vec2) => void;
14
+ onDragEnd?: (e: PointerEvent, newPosition: Vec2, delta: Vec2) => void;
15
+ /**
16
+ * Called if this hook is destroyed before the drag is completed.
17
+ */
18
+ onDragCancel?: () => void;
19
+ minDragDistance?: number;
20
+ dragCursor?: string;
21
+ addPlaceholder?: boolean;
22
+ stopPropagation?: boolean;
23
+ }
24
+
25
+ export function useDraggable(element: HTMLElement, props: DraggableProps, dependencies: unknown[]) {
26
+ const { minDragDistance = 10, dragCursor = 'grab', addPlaceholder = false, stopPropagation = false } = props;
27
+ useEffect(() => {
28
+ let downPosition: Vec2 | null = null;
29
+ // the bounds of the element when the pointer was first pressed down.
30
+ let dragStartOffset: Vec2 | null = null;
31
+ let originalCursor = '';
32
+ let placeholder: HTMLElement | null = null;
33
+ let movePosition: Vec2 = [0, 0];
34
+ function notify(e: PointerEvent, dragListener: DraggableProps["onDrag"]) {
35
+ const delta = Vec2.subtract(movePosition, downPosition!);
36
+ dragListener(e, Vec2.add(dragStartOffset!, delta), delta);
37
+ }
38
+
39
+ function onPointerMove(e: PointerEvent) {
40
+ movePosition = [e.clientX, e.clientY];
41
+ if (Vec2.length(Vec2.subtract(movePosition, downPosition!)) >= minDragDistance) {
42
+ if (!dragStartOffset) {
43
+ dragStartOffset = [element.offsetLeft, element.offsetTop];
44
+ props.onDragStart(e);
45
+ if (dragCursor) {
46
+ originalCursor = element.style.cursor;
47
+ element.style.cursor = dragCursor;
48
+ }
49
+ // add a placeholder so the parent element doesn't change size when the element is dragged.
50
+ if (addPlaceholder) {
51
+ placeholder = document.createElement('div');
52
+ Object.assign(placeholder.style, {
53
+ position: 'absolute',
54
+ backgroundColor: 'pink',
55
+ left: toCssUnitString(element.offsetLeft),
56
+ top: toCssUnitString(element.offsetTop),
57
+ width: toCssUnitString(element.offsetWidth),
58
+ height: toCssUnitString(element.offsetHeight),
59
+ visibility: 'hidden',
60
+ });
61
+ element.parentElement?.appendChild(placeholder);
62
+ }
63
+ }
64
+ }
65
+ if (dragStartOffset) {
66
+ notify(e, props.onDrag);
67
+ }
68
+ if (stopPropagation) e.stopPropagation();
69
+ }
70
+ function cleanup() {
71
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
72
+ window.removeEventListener('pointerup', onPointerUp);
73
+ window.removeEventListener('pointermove', onPointerMove);
74
+ if (dragCursor) {
75
+ element.style.cursor = originalCursor;
76
+ }
77
+ if (placeholder) {
78
+ placeholder.remove();
79
+ placeholder = null;
80
+ }
81
+ }
82
+ function onPointerUp(e: PointerEvent) {
83
+ cleanup();
84
+ if (dragStartOffset && props.onDragEnd) {
85
+ notify(e, props.onDragEnd);
86
+ }
87
+ if (stopPropagation) e.stopPropagation();
88
+ downPosition = null;
89
+ dragStartOffset = null;
90
+ }
91
+ function onPointerDown(e: PointerEvent) {
92
+ // Only start drag transaction for left mouse button clicks
93
+ if (e.button !== 0) return;
94
+
95
+ window.addEventListener('pointermove', onPointerMove);
96
+ window.addEventListener('pointerup', onPointerUp);
97
+ downPosition = [e.clientX, e.clientY];
98
+ if (stopPropagation) e.stopPropagation();
99
+ }
100
+
101
+ element?.addEventListener('pointerdown', onPointerDown);
102
+ return () => {
103
+ element?.removeEventListener('pointerdown', onPointerDown);
104
+ if (downPosition) {
105
+ cleanup();
106
+ }
107
+ if (dragStartOffset) {
108
+ props.onDragCancel?.();
109
+ }
110
+ };
111
+ }, [element, ...(dependencies ?? [])]);
112
+ }
@@ -0,0 +1,19 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { equalsShallow } from "@adobe/data/equals-shallow";
4
+ import type { Component } from "./component/component.js"
5
+ import { Component_stack } from "./component/stack.js";
6
+
7
+ export type EffectCallback = () => (void | (() => void))
8
+ type EffectHookState = { dispose?: () => void, dependencies: unknown[] };
9
+
10
+ export function useEffect<T extends Component>(callback: EffectCallback, dependencies: unknown[] = []) {
11
+ const component = Component_stack.active() as T;
12
+ const hookIndex = component.hookIndex++;
13
+ const oldHookState = component.hooks[hookIndex] as EffectHookState | undefined;
14
+ const rerunEffect = !oldHookState || !equalsShallow(dependencies, oldHookState.dependencies);
15
+ if (rerunEffect) {
16
+ oldHookState?.dispose?.();
17
+ component.hooks[hookIndex] = { dispose: callback.call(component) ?? undefined, dependencies };
18
+ }
19
+ }
@@ -0,0 +1,83 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { Component_stack } from "./component/stack.js";
4
+ import { useEffect } from "./use-effect.js";
5
+ import { useState } from "./use-state.js";
6
+
7
+ /**
8
+ * Hook that returns the currently active component.
9
+ * If a querySelector is provided, it dynamically observes for child elements
10
+ * matching the selector and triggers rerender when found.
11
+ */
12
+ export function useElement<K extends keyof HTMLElementTagNameMap>(querySelector: K): HTMLElementTagNameMap[K] | null
13
+ export function useElement<T = HTMLElement>(): T
14
+ export function useElement<T = HTMLElement>(querySelector?: string): T | null {
15
+ const component = Component_stack.active();
16
+
17
+ // If no querySelector provided, return the active component
18
+ if (!querySelector) {
19
+ return component as unknown as T;
20
+ }
21
+
22
+ // Use state to track the found element
23
+ const [foundElement, setFoundElement] = useState<T | null>(null);
24
+
25
+ useEffect(() => {
26
+ const element = component as unknown as HTMLElement;
27
+
28
+ // Check if element already exists
29
+ const existingElement = element.querySelector(querySelector) as T | null;
30
+ if (existingElement) {
31
+ setFoundElement(existingElement);
32
+ return;
33
+ }
34
+
35
+ // Set up MutationObserver to watch for new child elements
36
+ const observer = new MutationObserver((mutations) => {
37
+ for (const mutation of mutations) {
38
+ if (mutation.type === 'childList') {
39
+ // Check if any added nodes match the selector
40
+ for (const node of mutation.addedNodes) {
41
+ if (node.nodeType === Node.ELEMENT_NODE) {
42
+ const element = node as Element;
43
+ // Check if the added node itself matches
44
+ if (element.matches(querySelector)) {
45
+ setFoundElement(element as T);
46
+ observer.disconnect();
47
+ return;
48
+ }
49
+ // Check if any descendant matches
50
+ const descendant = element.querySelector(querySelector) as T | null;
51
+ if (descendant) {
52
+ setFoundElement(descendant);
53
+ observer.disconnect();
54
+ return;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ });
61
+
62
+ // Start observing both element and shadowRoot
63
+ for (const observe of [element.shadowRoot, element]) {
64
+ if (observe) {
65
+ observer.observe(observe, {
66
+ childList: true,
67
+ subtree: true
68
+ });
69
+ }
70
+ }
71
+ observer.observe(element.shadowRoot ?? element, {
72
+ childList: true,
73
+ subtree: true
74
+ });
75
+
76
+ // Cleanup function
77
+ return () => {
78
+ observer.disconnect();
79
+ };
80
+ }, [querySelector]);
81
+
82
+ return foundElement;
83
+ }