@seahax/elemental 0.4.1 → 0.5.0

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 +82 -16
  2. package/dist/component.d.ts +5 -0
  3. package/dist/component.js +42 -19
  4. package/dist/component.js.map +1 -1
  5. package/dist/hooks/async.d.ts +11 -0
  6. package/dist/hooks/{loading.js → async.js} +3 -3
  7. package/dist/hooks/async.js.map +1 -0
  8. package/dist/hooks/{child-effect.js → child.js} +2 -2
  9. package/dist/hooks/child.js.map +1 -0
  10. package/dist/hooks/controller.d.ts +3 -0
  11. package/dist/hooks/controller.js +11 -0
  12. package/dist/hooks/controller.js.map +1 -0
  13. package/dist/hooks/disconnect.d.ts +2 -0
  14. package/dist/hooks/disconnect.js +9 -0
  15. package/dist/hooks/disconnect.js.map +1 -0
  16. package/dist/hooks/document.d.ts +3 -0
  17. package/dist/hooks/document.js +9 -0
  18. package/dist/hooks/document.js.map +1 -0
  19. package/dist/hooks/effect.js +9 -9
  20. package/dist/hooks/effect.js.map +1 -1
  21. package/dist/hooks/form.d.ts +9 -0
  22. package/dist/hooks/form.js +23 -0
  23. package/dist/hooks/form.js.map +1 -0
  24. package/dist/hooks/host.d.ts +1 -1
  25. package/dist/hooks/host.js +1 -1
  26. package/dist/hooks/host.js.map +1 -1
  27. package/dist/hooks/internals.d.ts +2 -0
  28. package/dist/hooks/internals.js +9 -0
  29. package/dist/hooks/internals.js.map +1 -0
  30. package/dist/hooks/parent.d.ts +3 -0
  31. package/dist/hooks/parent.js +9 -0
  32. package/dist/hooks/parent.js.map +1 -0
  33. package/dist/hooks/ref.d.ts +1 -2
  34. package/dist/hooks/ref.js +4 -14
  35. package/dist/hooks/ref.js.map +1 -1
  36. package/dist/hooks/route.d.ts +2 -2
  37. package/dist/hooks/route.js.map +1 -1
  38. package/dist/index.d.ts +7 -2
  39. package/dist/index.js +16 -11
  40. package/dist/internal/controller.d.ts +33 -0
  41. package/dist/internal/controller.js +72 -0
  42. package/dist/internal/controller.js.map +1 -0
  43. package/package.json +1 -1
  44. package/dist/hooks/child-effect.js.map +0 -1
  45. package/dist/hooks/context.d.ts +0 -3
  46. package/dist/hooks/context.js +0 -11
  47. package/dist/hooks/context.js.map +0 -1
  48. package/dist/hooks/loading.d.ts +0 -11
  49. package/dist/hooks/loading.js.map +0 -1
  50. package/dist/internal/context.d.ts +0 -13
  51. package/dist/internal/context.js +0 -32
  52. package/dist/internal/context.js.map +0 -1
  53. /package/dist/hooks/{child-effect.d.ts → child.d.ts} +0 -0
package/README.md CHANGED
@@ -14,8 +14,8 @@ Contains everything you need to build anything from a single component up to a f
14
14
  - No Dependencies
15
15
  - Tiny Bundle Size
16
16
 
17
- ![NPM Version](https://img.shields.io/npm/v/@seahax/elemental?color=red)
18
- [![bundlejs](https://deno.bundlejs.com/badge?q=@seahax/elemental@latest&treeshake=[*])](https://bundlejs.com/?q=%40seahax%2Felemental%40latest&treeshake=%5B*%5D)
17
+ [![NPM](https://img.shields.io/npm/v/%40seahax%2Felemental?style=for-the-badge&color=red)](https://www.npmjs.com/package/@seahax/elemental) [![BundleJS (GZIP)](https://img.shields.io/bundlejs/size/%40seahax/elemental?style=for-the-badge&label=bundlejs%20(gzip))
18
+ ](https://bundlejs.com/?q=%40seahax%2Felemental%40latest&treeshake=%5B*%5D)
19
19
 
20
20
 
21
21
  ## Define A Web Component
@@ -23,20 +23,30 @@ Contains everything you need to build anything from a single component up to a f
23
23
  ```ts
24
24
  import {
25
25
  defineComponent,
26
+ h,
26
27
  useRef,
27
28
  useStore,
28
29
  useAttributes,
30
+ useParent,
31
+ useDocument,
29
32
  useRoute,
30
- useLoading,
33
+ useAsync,
31
34
  useEffect,
32
35
  useChildEffect,
36
+ useDisconnectCallback,
37
+ useElementInternals,
33
38
  useHost,
34
- h,
35
39
  } from '@seahax/elemental';
36
40
 
37
41
  export const MyComponent = defineComponent((shadow) => {
38
- // This function is run once after the component is created, when it is
39
- // first connected to the document.
42
+ // This function is called every time the component is connected to the
43
+ // document.
44
+ //
45
+ // For the most part, a component's lifecycle should be handled as if it
46
+ // starts on connect and ends on disconnect, even though the component
47
+ // can be reconnected to the document. Restore internal state on
48
+ // connection from host attributes and properties. Effect hooks are
49
+ // designed to facilitate this pattern.
40
50
 
41
51
  // Create HTML elements and save references to them.
42
52
  const myInput = h('input');
@@ -58,9 +68,15 @@ export const MyComponent = defineComponent((shadow) => {
58
68
  // Use a reference (reactive state) bound to a (shared) store.
59
69
  const globalStateRef = useStore(myStore, select, mutate);
60
70
 
61
- // Use references (reactive state) bound to attributes.
71
+ // Use references (reactive state) bound to the component's attributes.
62
72
  const [dataValueRef, ...] = useAttributes('data-value', ...);
63
73
 
74
+ // Use a reference (reactive state) bound to the component's parent node.
75
+ const parentNode = useParent();
76
+
77
+ // Use a reference (reactive state) bound to the component's owner document.
78
+ const ownerDocument = useDocument();
79
+
64
80
  // Use a reference (reactive state) bound to route matching.
65
81
  const routeMatchRef = useRoute('/path/', {
66
82
  match: 'prefix', // 'exact' | 'prefix' | RegExp
@@ -68,7 +84,7 @@ export const MyComponent = defineComponent((shadow) => {
68
84
  });
69
85
 
70
86
  // Use a reference (reactive state) bound to an async loader function.
71
- const loadingStateRef = useLoading([
87
+ const asyncRef = useAsync([
72
88
  // dependency references
73
89
  ], async (signal, ...dependencyValues) => {
74
90
  // Reactive async code runs when the component is connected to the
@@ -84,15 +100,15 @@ export const MyComponent = defineComponent((shadow) => {
84
100
  globalStateRef,
85
101
  dataValueRef,
86
102
  routeMatchRef,
87
- routeStateRef,
88
- loadingStateRef,
103
+ asyncRef,
89
104
  ], (...dependencyValues) => {
90
105
  // Reactive code runs when the component is connected to the document,
91
106
  // and when any of the dependencies change.
92
107
 
93
108
  return () => {
94
- // Cleanup before the next effect callback and after the component
95
- // is disconnected from the document.
109
+ // Cleanup after dependency refs are changed (before the next effect
110
+ // callback) and after the component is disconnected from the
111
+ // document.
96
112
  };
97
113
  });
98
114
 
@@ -108,24 +124,74 @@ export const MyComponent = defineComponent((shadow) => {
108
124
  };
109
125
  });
110
126
 
111
- // Use the host element (generally only useful in reusable hooks).
127
+ // Register a document disconnection callback.
128
+ useDisconnectCallback(() => {
129
+ // Called when the component is disconnected from the document.
130
+ });
131
+
132
+ // Use the element's internals.
133
+ const elementInternals = useElementInternals();
134
+
135
+ // Use the component host element.
112
136
  const host = useHost();
113
137
  });
114
138
  ```
115
139
 
140
+ ## Enable Form Association
141
+
142
+ ```ts
143
+ import {
144
+ useElementInternals,
145
+ useForm,
146
+ useFormDisabled,
147
+ useFormResetCallback,
148
+ useFormRestoreCallback,
149
+ } from '@seahax/elemental';
150
+
151
+ const MyComponent = defineComponent(
152
+ (shadow) => {
153
+ // Use the element's internals.
154
+ const elementInternals = useElementInternals();
155
+
156
+ // Use a reference (reactive state) bound to the associated form.
157
+ const formRef = useForm();
158
+
159
+ // Use a reference (reactive state) bound to the form disabled state.
160
+ const formDisabledRef = useFormDisabled();
161
+
162
+ // Register a form reset callback.
163
+ useFormResetCallback(() => {
164
+ // Called when the associated form is reset. Only called on connect
165
+ // if the form was reset while the component was disconnected.
166
+ });
167
+
168
+ // Register a form restore callback.
169
+ useFormRestoreCallback((state, reason) => {
170
+ // Called when the associated form is restored. Only called on connect
171
+ // if the form was restored while the component was disconnected.
172
+ });
173
+ },
174
+ {
175
+ // Enable form association.
176
+ formAssociated: true,
177
+ }
178
+ );
179
+ ```
180
+
116
181
  ## Customize The Shadow Root
117
182
 
118
183
  ```ts
119
184
  const MyComponent = defineComponent(
120
185
  (shadow) => {
121
- // Renderer...
186
+ ...
122
187
  },
123
188
  {
124
- // Shadow root options.
189
+ // Use custom shadow root initialization options.
190
+ // (default: { mode: 'open' }).
125
191
  shadow: {
126
192
  mode: 'closed',
127
193
  ...
128
- }
194
+ },
129
195
  }
130
196
  );
131
197
  ```
@@ -3,11 +3,16 @@ type SafeProps<TProps> = any extends any ? {
3
3
  [P in keyof TProps as P extends keyof HTMLElement ? never : P]: TProps[P];
4
4
  } : never;
5
5
  export interface ComponentConstructor<TProps extends object> {
6
+ readonly formAssociated: boolean;
6
7
  new (): ComponentWithProps<TProps>;
7
8
  }
8
9
  export interface ComponentOptions<TProps extends object> {
10
+ /** Shadow root attachment options. */
9
11
  readonly shadow?: Partial<ShadowRootInit>;
12
+ /** Component custom property descriptors. */
10
13
  readonly props?: ComponentPropDescriptors<TProps>;
14
+ /** True to mark the component as form-associated. */
15
+ readonly formAssociated?: boolean;
11
16
  }
12
17
  export type ComponentPropDescriptors<TProps extends object> = {
13
18
  readonly [P in keyof SafeProps<TProps>]: ComponentPropDescriptorFactory<TProps[P]>;
package/dist/component.js CHANGED
@@ -1,34 +1,57 @@
1
- import { createContextController as e } from "./internal/context.js";
2
- import { useRef as t } from "./hooks/ref.js";
1
+ import { createController as e } from "./internal/controller.js";
3
2
  //#region src/component.ts
4
- function n(n, { props: r, shadow: i } = {}) {
3
+ function t(t, { props: n, shadow: r, formAssociated: i = !1 } = {}) {
5
4
  return class extends HTMLElement {
6
- #e = {};
7
- #t = e(this);
5
+ static formAssociated = i;
6
+ #e;
7
+ #t;
8
+ #n = {};
8
9
  constructor() {
9
- if (super(), r) {
10
- let e = this.#e;
11
- for (let [n, i] of Object.entries(r)) {
12
- if (n in this) continue;
13
- let r = i(e[n] = t(void 0), this);
14
- Object.defineProperty(this, n, r);
10
+ if (super(), this.#e = this.attachShadow({
11
+ ...r,
12
+ mode: r?.mode ?? "open"
13
+ }), this.#t = e({
14
+ host: this,
15
+ formAssociated: i,
16
+ render: () => t(this.#e, this.#n),
17
+ attachInternals: () => super.attachInternals()
18
+ }), n) {
19
+ let e = this.#n;
20
+ for (let [t, r] of Object.entries(n)) {
21
+ if (t in this) continue;
22
+ let n = r(e[t] = this.#t.createRef(void 0), this);
23
+ Object.defineProperty(this, t, n);
15
24
  }
16
25
  }
26
+ i && this.attachInternals();
27
+ }
28
+ attachInternals() {
29
+ return this.#t.attachInternals();
17
30
  }
18
31
  connectedCallback() {
19
- this.#t.connect(() => {
20
- n(this.attachShadow({
21
- ...i,
22
- mode: i?.mode ?? "open"
23
- }), this.#e);
24
- });
32
+ this.#t.connectedCallback();
33
+ }
34
+ connectedMoveCallback() {
35
+ this.#t.connectedMoveCallback();
25
36
  }
26
37
  disconnectedCallback() {
27
- this.#t.disconnect();
38
+ this.#t.disconnectedCallback();
39
+ }
40
+ adoptedCallback() {
41
+ this.#t.adoptedCallback();
42
+ }
43
+ formDisabledCallback(e) {
44
+ this.#t.formDisabledCallback(e);
45
+ }
46
+ formResetCallback() {
47
+ this.#t.formResetCallback();
48
+ }
49
+ formStateRestoreCallback(e, t) {
50
+ this.#t.formStateRestoreCallback(e, t);
28
51
  }
29
52
  };
30
53
  }
31
54
  //#endregion
32
- export { n as defineComponent };
55
+ export { t as defineComponent };
33
56
 
34
57
  //# sourceMappingURL=component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.js","names":["#propRefs","#contextController"],"sources":["../src/component.ts"],"sourcesContent":["import { type Ref, useRef } from './hooks/ref.ts';\nimport { createContextController } from './internal/context.ts';\n\ntype SafeProps<TProps> = any extends any\n ? { [P in keyof TProps as P extends keyof HTMLElement ? never : P]: TProps[P] }\n : never;\n\nexport interface ComponentConstructor<TProps extends object> {\n new (): ComponentWithProps<TProps>;\n}\n\nexport interface ComponentOptions<TProps extends object> {\n readonly shadow?: Partial<ShadowRootInit>;\n readonly props?: ComponentPropDescriptors<TProps>;\n}\n\nexport type ComponentPropDescriptors<TProps extends object> = {\n readonly [P in keyof SafeProps<TProps>]: ComponentPropDescriptorFactory<TProps[P]>;\n};\n\nexport type ComponentPropDescriptorFactory<TType> = (\n ref: Ref<TType | undefined>,\n host: HTMLElement,\n) => ComponentPropDescriptor<TType>;\n\nexport interface ComponentPropDescriptor<T> extends Omit<PropertyDescriptor, 'value' | 'get' | 'set'> {\n get(): T;\n set?(value: T): void;\n}\n\nexport type ComponentWithProps<TProps extends object> = HTMLElement & {\n -readonly [P in keyof SafeProps<TProps>]: TProps[P];\n};\n\nexport type ComponentShadowRoot<TProps extends object> = Omit<ShadowRoot, 'host'> & {\n readonly host: ComponentWithProps<TProps>;\n};\n\nexport type ComponentPropRefs<TProps extends object> = {\n readonly [P in keyof SafeProps<TProps>]: Ref<TProps[P] | undefined>;\n};\n\n/** Define a custom `HTMLElement` that is functional and reactive. */\nexport function defineComponent<TProps extends object = {}>(\n render: (shadowRoot: ComponentShadowRoot<TProps>, props: ComponentPropRefs<TProps>) => void,\n options?: ComponentOptions<TProps>,\n): ComponentConstructor<TProps>;\nexport function defineComponent(\n render: (\n shadowRoot: ComponentShadowRoot<Record<string, unknown>>,\n props: ComponentPropRefs<Record<string, unknown>>,\n ) => void,\n { props, shadow }: ComponentOptions<Record<string, unknown>> = {},\n): ComponentConstructor<{}> {\n return class extends HTMLElement {\n readonly #propRefs: ComponentPropRefs<Record<string, unknown>> = {};\n readonly #contextController = createContextController(this);\n\n constructor() {\n super();\n\n if (props) {\n const propRefs: Record<string, Ref<unknown>> = this.#propRefs;\n\n for (const [key, getDescriptor] of Object.entries(props)) {\n if (key in this) continue;\n const ref = (propRefs[key] = useRef<any>(undefined));\n const descriptor = getDescriptor(ref, this);\n Object.defineProperty(this, key, descriptor);\n }\n }\n }\n\n protected connectedCallback(): void {\n this.#contextController.connect(() => {\n const shadowRoot = this.attachShadow({ ...shadow, mode: shadow?.mode ?? 'open' });\n render(shadowRoot as ComponentShadowRoot<Record<string, unknown>>, this.#propRefs);\n });\n }\n\n protected disconnectedCallback(): void {\n this.#contextController.disconnect();\n }\n };\n}\n"],"mappings":";;;AA+CA,SAAgB,EACd,GAIA,EAAE,UAAO,cAAsD,EAAE,EACvC;AAC1B,QAAO,cAAc,YAAY;EAC/B,KAAiE,EAAE;EACnE,KAA8B,EAAwB,KAAK;EAE3D,cAAc;AAGZ,OAFA,OAAO,EAEH,GAAO;IACT,IAAM,IAAyC,MAAA;AAE/C,SAAK,IAAM,CAAC,GAAK,MAAkB,OAAO,QAAQ,EAAM,EAAE;AACxD,SAAI,KAAO,KAAM;KAEjB,IAAM,IAAa,EAAc,EADX,KAAO,EAAY,KAAA,EAAU,EACb,KAAK;AAC3C,YAAO,eAAe,MAAM,GAAK,EAAW;;;;EAKlD,oBAAoC;AAClC,SAAA,EAAwB,cAAc;AAEpC,MADmB,KAAK,aAAa;KAAE,GAAG;KAAQ,MAAM,GAAQ,QAAQ;KAAQ,CACzE,EAA4D,MAAA,EAAe;KAClF;;EAGJ,uBAAuC;AACrC,SAAA,EAAwB,YAAY"}
1
+ {"version":3,"file":"component.js","names":["#shadow","#controller","#props"],"sources":["../src/component.ts"],"sourcesContent":["import { type Ref } from './hooks/ref.ts';\nimport { type Controller, createController } from './internal/controller.ts';\n\ntype SafeProps<TProps> = any extends any\n ? { [P in keyof TProps as P extends keyof HTMLElement ? never : P]: TProps[P] }\n : never;\n\nexport interface ComponentConstructor<TProps extends object> {\n readonly formAssociated: boolean;\n new (): ComponentWithProps<TProps>;\n}\n\nexport interface ComponentOptions<TProps extends object> {\n /** Shadow root attachment options. */\n readonly shadow?: Partial<ShadowRootInit>;\n /** Component custom property descriptors. */\n readonly props?: ComponentPropDescriptors<TProps>;\n /** True to mark the component as form-associated. */\n readonly formAssociated?: boolean;\n}\n\nexport type ComponentPropDescriptors<TProps extends object> = {\n readonly [P in keyof SafeProps<TProps>]: ComponentPropDescriptorFactory<TProps[P]>;\n};\n\nexport type ComponentPropDescriptorFactory<TType> = (\n ref: Ref<TType | undefined>,\n host: HTMLElement,\n) => ComponentPropDescriptor<TType>;\n\nexport interface ComponentPropDescriptor<T> extends Omit<PropertyDescriptor, 'value' | 'get' | 'set'> {\n get(): T;\n set?(value: T): void;\n}\n\nexport type ComponentWithProps<TProps extends object> = HTMLElement & {\n -readonly [P in keyof SafeProps<TProps>]: TProps[P];\n};\n\nexport type ComponentShadowRoot<TProps extends object> = Omit<ShadowRoot, 'host'> & {\n readonly host: ComponentWithProps<TProps>;\n};\n\nexport type ComponentPropRefs<TProps extends object> = {\n readonly [P in keyof SafeProps<TProps>]: Ref<TProps[P] | undefined>;\n};\n\n/** Define a custom `HTMLElement` that is functional and reactive. */\nexport function defineComponent<TProps extends object = {}>(\n render: (shadowRoot: ComponentShadowRoot<TProps>, props: ComponentPropRefs<TProps>) => void,\n options?: ComponentOptions<TProps>,\n): ComponentConstructor<TProps>;\nexport function defineComponent(\n render: (\n shadowRoot: ComponentShadowRoot<Record<string, unknown>>,\n props: ComponentPropRefs<Record<string, unknown>>,\n ) => void,\n { props, shadow, formAssociated = false }: ComponentOptions<Record<string, unknown>> = {},\n): ComponentConstructor<{}> {\n return class extends HTMLElement {\n static readonly formAssociated = formAssociated;\n\n readonly #shadow: ComponentShadowRoot<Record<string, unknown>>;\n readonly #controller: Controller;\n readonly #props: ComponentPropRefs<Record<string, unknown>> = {};\n\n constructor() {\n super();\n\n this.#shadow = this.attachShadow({\n ...shadow,\n mode: shadow?.mode ?? 'open',\n }) as ComponentShadowRoot<Record<string, unknown>>;\n\n this.#controller = createController({\n host: this,\n formAssociated,\n render: () => render(this.#shadow, this.#props),\n attachInternals: () => super.attachInternals(),\n });\n\n if (props) {\n const propRefs: Record<string, Ref<unknown>> = this.#props;\n\n for (const [key, getDescriptor] of Object.entries(props)) {\n if (key in this) continue;\n const ref = (propRefs[key] = this.#controller.createRef<any>(undefined));\n const descriptor = getDescriptor(ref, this);\n Object.defineProperty(this, key, descriptor);\n }\n }\n\n if (formAssociated) {\n this.attachInternals();\n }\n }\n\n override attachInternals(): ElementInternals {\n return this.#controller.attachInternals();\n }\n\n protected connectedCallback(): void {\n this.#controller.connectedCallback();\n }\n\n protected connectedMoveCallback(): void {\n this.#controller.connectedMoveCallback();\n }\n\n protected disconnectedCallback(): void {\n this.#controller.disconnectedCallback();\n }\n\n protected adoptedCallback(): void {\n this.#controller.adoptedCallback();\n }\n\n protected formDisabledCallback(disabled: boolean): void {\n this.#controller.formDisabledCallback(disabled);\n }\n\n protected formResetCallback(): void {\n this.#controller.formResetCallback();\n }\n\n protected formStateRestoreCallback(state: string | File | FormData, reason: 'restore' | 'autocomplete'): void {\n this.#controller.formStateRestoreCallback(state, reason);\n }\n };\n}\n"],"mappings":";;AAoDA,SAAgB,EACd,GAIA,EAAE,UAAO,WAAQ,oBAAiB,OAAqD,EAAE,EAC/D;AAC1B,QAAO,cAAc,YAAY;EAC/B,OAAgB,iBAAiB;EAEjC;EACA;EACA,KAA8D,EAAE;EAEhE,cAAc;AAeZ,OAdA,OAAO,EAEP,MAAA,IAAe,KAAK,aAAa;IAC/B,GAAG;IACH,MAAM,GAAQ,QAAQ;IACvB,CAAC,EAEF,MAAA,IAAmB,EAAiB;IAClC,MAAM;IACN;IACA,cAAc,EAAO,MAAA,GAAc,MAAA,EAAY;IAC/C,uBAAuB,MAAM,iBAAiB;IAC/C,CAAC,EAEE,GAAO;IACT,IAAM,IAAyC,MAAA;AAE/C,SAAK,IAAM,CAAC,GAAK,MAAkB,OAAO,QAAQ,EAAM,EAAE;AACxD,SAAI,KAAO,KAAM;KAEjB,IAAM,IAAa,EAAc,EADX,KAAO,MAAA,EAAiB,UAAe,KAAA,EAAU,EACjC,KAAK;AAC3C,YAAO,eAAe,MAAM,GAAK,EAAW;;;AAIhD,GAAI,KACF,KAAK,iBAAiB;;EAI1B,kBAA6C;AAC3C,UAAO,MAAA,EAAiB,iBAAiB;;EAG3C,oBAAoC;AAClC,SAAA,EAAiB,mBAAmB;;EAGtC,wBAAwC;AACtC,SAAA,EAAiB,uBAAuB;;EAG1C,uBAAuC;AACrC,SAAA,EAAiB,sBAAsB;;EAGzC,kBAAkC;AAChC,SAAA,EAAiB,iBAAiB;;EAGpC,qBAA+B,GAAyB;AACtD,SAAA,EAAiB,qBAAqB,EAAS;;EAGjD,oBAAoC;AAClC,SAAA,EAAiB,mBAAmB;;EAGtC,yBAAmC,GAAiC,GAA0C;AAC5G,SAAA,EAAiB,yBAAyB,GAAO,EAAO"}
@@ -0,0 +1,11 @@
1
+ import { type ReadonlyRef, type Ref, type RefValues } from './ref.ts';
2
+ export interface AsyncValue<TValue> {
3
+ readonly value: TValue | undefined;
4
+ readonly error: unknown;
5
+ readonly isLoading: boolean;
6
+ }
7
+ export interface AsyncOptions {
8
+ readonly debounceMs?: number;
9
+ }
10
+ /** Use a reference (reactive state) bound to an async loader function. */
11
+ export declare function useAsync<const TDeps extends readonly ReadonlyRef<any>[], TValue>(deps: TDeps, callback: (signal: AbortSignal, ...values: RefValues<TDeps>) => Promise<TValue>, { debounceMs }?: AsyncOptions): Ref<AsyncValue<TValue>>;
@@ -1,6 +1,6 @@
1
1
  import { useRef as e } from "./ref.js";
2
2
  import { useEffect as t } from "./effect.js";
3
- //#region src/hooks/loading.ts
3
+ //#region src/hooks/async.ts
4
4
  function n(n, r, { debounceMs: i } = {}) {
5
5
  let a = e({
6
6
  value: void 0,
@@ -27,6 +27,6 @@ function n(n, r, { debounceMs: i } = {}) {
27
27
  }), t([], () => () => o = !0), a;
28
28
  }
29
29
  //#endregion
30
- export { n as useLoading };
30
+ export { n as useAsync };
31
31
 
32
- //# sourceMappingURL=loading.js.map
32
+ //# sourceMappingURL=async.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async.js","names":[],"sources":["../../src/hooks/async.ts"],"sourcesContent":["import { useEffect } from './effect.ts';\nimport { type ReadonlyRef, type Ref, type RefValues, useRef } from './ref.ts';\n\nexport interface AsyncValue<TValue> {\n readonly value: TValue | undefined;\n readonly error: unknown;\n readonly isLoading: boolean;\n}\n\nexport interface AsyncOptions {\n readonly debounceMs?: number;\n}\n\n/** Use a reference (reactive state) bound to an async loader function. */\nexport function useAsync<const TDeps extends readonly ReadonlyRef<any>[], TValue>(\n deps: TDeps,\n callback: (signal: AbortSignal, ...values: RefValues<TDeps>) => Promise<TValue>,\n { debounceMs }: AsyncOptions = {},\n): Ref<AsyncValue<TValue>> {\n const ref = useRef<AsyncValue<TValue>>({ value: undefined, error: undefined, isLoading: true });\n let skipDebounce = true;\n\n useEffect(deps, () => (...values) => {\n const ac = new AbortController();\n\n Promise.race(\n skipDebounce\n ? [Promise.resolve()]\n : [\n new Promise((resolve) => setTimeout(resolve, debounceMs)),\n new Promise((resolve) => ac.signal.addEventListener('abort', resolve, { once: true })),\n ],\n )\n .then(() => {\n if (ac.signal.aborted) return;\n return callback(ac.signal, ...(values as any));\n })\n .then((value) => {\n if (ac.signal.aborted) return;\n ref.value = { isLoading: false, value, error: undefined };\n })\n .catch((error: unknown) => {\n if (ac.signal.aborted) return;\n ref.value = { isLoading: false, value: undefined, error };\n });\n\n skipDebounce = false;\n return () => ac.abort();\n });\n\n // Skip the debounce again if the component unmounts.\n useEffect([], () => () => (skipDebounce = true));\n\n return ref;\n}\n"],"mappings":";;;AAcA,SAAgB,EACd,GACA,GACA,EAAE,kBAA6B,EAAE,EACR;CACzB,IAAM,IAAM,EAA2B;EAAE,OAAO,KAAA;EAAW,OAAO,KAAA;EAAW,WAAW;EAAM,CAAC,EAC3F,IAAe;AAiCnB,QA/BA,EAAU,UAAa,GAAG,MAAW;EACnC,IAAM,IAAK,IAAI,iBAAiB;AAwBhC,SAtBA,QAAQ,KACN,IACI,CAAC,QAAQ,SAAS,CAAC,GACnB,CACE,IAAI,SAAS,MAAY,WAAW,GAAS,EAAW,CAAC,EACzD,IAAI,SAAS,MAAY,EAAG,OAAO,iBAAiB,SAAS,GAAS,EAAE,MAAM,IAAM,CAAC,CAAC,CACvF,CACN,CACE,WAAW;AACN,UAAG,OAAO,QACd,QAAO,EAAS,EAAG,QAAQ,GAAI,EAAe;IAC9C,CACD,MAAM,MAAU;AACX,KAAG,OAAO,YACd,EAAI,QAAQ;IAAE,WAAW;IAAO;IAAO,OAAO,KAAA;IAAW;IACzD,CACD,OAAO,MAAmB;AACrB,KAAG,OAAO,YACd,EAAI,QAAQ;IAAE,WAAW;IAAO,OAAO,KAAA;IAAW;IAAO;IACzD,EAEJ,IAAe,UACF,EAAG,OAAO;GACvB,EAGF,EAAU,EAAE,cAAe,IAAe,GAAM,EAEzC"}
@@ -1,7 +1,7 @@
1
1
  import { useRef as e } from "./ref.js";
2
2
  import { useEffect as t } from "./effect.js";
3
3
  import { useHost as n } from "./host.js";
4
- //#region src/hooks/child-effect.ts
4
+ //#region src/hooks/child.ts
5
5
  function r(r) {
6
6
  let i = n(), a = e(0), o = new MutationObserver((e) => {
7
7
  e.some((e) => e.type === "childList") && (a.value = (a.value + 1) % (2 ** 53 - 1));
@@ -11,4 +11,4 @@ function r(r) {
11
11
  //#endregion
12
12
  export { r as useChildEffect };
13
13
 
14
- //# sourceMappingURL=child-effect.js.map
14
+ //# sourceMappingURL=child.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child.js","names":[],"sources":["../../src/hooks/child.ts"],"sourcesContent":["import { useEffect } from './effect.ts';\nimport { useHost } from './host.ts';\nimport { useRef } from './ref.ts';\n\n/** React to child list changes (non-recursive). */\nexport function useChildEffect(callback: () => (() => void) | void): void {\n const host = useHost();\n const ref = useRef(0);\n\n const observer = new MutationObserver((mutation) => {\n if (mutation.some((m) => m.type === 'childList')) {\n ref.value = (ref.value + 1) % Number.MAX_SAFE_INTEGER;\n }\n });\n\n useEffect([], () => {\n observer.observe(host, { childList: true });\n return () => observer.disconnect();\n });\n\n useEffect([ref], () => {\n return callback();\n });\n}\n"],"mappings":";;;;AAKA,SAAgB,EAAe,GAA2C;CACxE,IAAM,IAAO,GAAS,EAChB,IAAM,EAAO,EAAE,EAEf,IAAW,IAAI,kBAAkB,MAAa;AAClD,EAAI,EAAS,MAAM,MAAM,EAAE,SAAS,YAAY,KAC9C,EAAI,SAAS,EAAI,QAAQ;GAE3B;AAOF,CALA,EAAU,EAAE,SACV,EAAS,QAAQ,GAAM,EAAE,WAAW,IAAM,CAAC,QAC9B,EAAS,YAAY,EAClC,EAEF,EAAU,CAAC,EAAI,QACN,GAAU,CACjB"}
@@ -0,0 +1,3 @@
1
+ import { type Controller } from '../internal/controller.ts';
2
+ /** @internal Get the controller of the currently rendering component. */
3
+ export declare function useController(): Controller;
@@ -0,0 +1,11 @@
1
+ import { getController as e } from "../internal/controller.js";
2
+ //#region src/hooks/controller.ts
3
+ function t() {
4
+ let t = e();
5
+ if (!t) throw Error("hooks must be called by a render function");
6
+ return t;
7
+ }
8
+ //#endregion
9
+ export { t as useController };
10
+
11
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","names":[],"sources":["../../src/hooks/controller.ts"],"sourcesContent":["import { type Controller, getController } from '../internal/controller.ts';\n\n/** @internal Get the controller of the currently rendering component. */\nexport function useController(): Controller {\n const controller = getController();\n if (!controller) throw new Error('hooks must be called by a render function');\n return controller;\n}\n"],"mappings":";;AAGA,SAAgB,IAA4B;CAC1C,IAAM,IAAa,GAAe;AAClC,KAAI,CAAC,EAAY,OAAU,MAAM,4CAA4C;AAC7E,QAAO"}
@@ -0,0 +1,2 @@
1
+ /** Register a callback for disconnections. */
2
+ export declare function useDisconnectCallback(callback: () => void): void;
@@ -0,0 +1,9 @@
1
+ import { useController as e } from "./controller.js";
2
+ //#region src/hooks/disconnect.ts
3
+ function t(t) {
4
+ e().onDisconnect.push(t);
5
+ }
6
+ //#endregion
7
+ export { t as useDisconnectCallback };
8
+
9
+ //# sourceMappingURL=disconnect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disconnect.js","names":[],"sources":["../../src/hooks/disconnect.ts"],"sourcesContent":["import { useController } from './controller.ts';\n\n/** Register a callback for disconnections. */\nexport function useDisconnectCallback(callback: () => void): void {\n useController().onDisconnect.push(callback);\n}\n"],"mappings":";;AAGA,SAAgB,EAAsB,GAA4B;AAChE,IAAe,CAAC,aAAa,KAAK,EAAS"}
@@ -0,0 +1,3 @@
1
+ import type { ReadonlyRef } from './ref.ts';
2
+ /** Use a reference (reactive state) bound to the owner document. */
3
+ export declare function useDocument(): ReadonlyRef<Document>;
@@ -0,0 +1,9 @@
1
+ import { useController as e } from "./controller.js";
2
+ //#region src/hooks/document.ts
3
+ function t() {
4
+ return e().refDocument;
5
+ }
6
+ //#endregion
7
+ export { t as useDocument };
8
+
9
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","names":[],"sources":["../../src/hooks/document.ts"],"sourcesContent":["import { useController } from './controller.ts';\nimport type { ReadonlyRef } from './ref.ts';\n\n/** Use a reference (reactive state) bound to the owner document. */\nexport function useDocument(): ReadonlyRef<Document> {\n return useController().refDocument;\n}\n"],"mappings":";;AAIA,SAAgB,IAAqC;AACnD,QAAO,GAAe,CAAC"}
@@ -1,15 +1,15 @@
1
- import { createCallbacks as e } from "../internal/callbacks.js";
2
- import { useContext as t } from "./context.js";
1
+ import { useController as e } from "./controller.js";
2
+ import { useDisconnectCallback as t } from "./disconnect.js";
3
3
  //#region src/hooks/effect.ts
4
4
  function n(n, r) {
5
- let { onNotify: i, onDisconnect: a } = t(), o = e(), s = () => o.runAndClear(), c;
6
- i.push(() => {
5
+ let i, a, o = () => {
6
+ let e = i;
7
+ i = void 0, e?.();
8
+ };
9
+ e().onNotify.push(() => {
7
10
  let e = n.map((e) => e.value);
8
- if (c?.length === e.length && c?.every((t, n) => t === e[n])) return;
9
- c = e, s();
10
- let t = r(...c);
11
- t && o.push(() => t());
12
- }), a.push(s);
11
+ a?.length === e.length && a?.every((t, n) => t === e[n]) || (a = e, o(), i = r(...a));
12
+ }), t(o);
13
13
  }
14
14
  //#endregion
15
15
  export { n as useEffect };
@@ -1 +1 @@
1
- {"version":3,"file":"effect.js","names":[],"sources":["../../src/hooks/effect.ts"],"sourcesContent":["import { createCallbacks } from '../internal/callbacks.ts';\nimport { useContext } from './context.ts';\nimport type { ReadonlyRef, RefValues } from './ref.ts';\n\n/** React to observable (reference) changes. */\nexport function useEffect<const TDeps extends readonly ReadonlyRef<any>[]>(\n deps: TDeps,\n callback: (...values: RefValues<TDeps>) => (() => void) | void,\n): void {\n const { onNotify, onDisconnect } = useContext();\n const cleanupCallback = createCallbacks();\n const cleanup = (): void => cleanupCallback.runAndClear();\n let values: any[] | undefined;\n\n onNotify.push((): void => {\n const newValues = deps.map((dep) => dep.value);\n if (values?.length === newValues.length && values?.every((value, i) => value === newValues[i])) return;\n values = newValues;\n cleanup();\n const maybeCleanup = callback(...(values as any));\n if (maybeCleanup) cleanupCallback.push(() => maybeCleanup());\n });\n\n onDisconnect.push(cleanup);\n}\n"],"mappings":";;;AAKA,SAAgB,EACd,GACA,GACM;CACN,IAAM,EAAE,aAAU,oBAAiB,GAAY,EACzC,IAAkB,GAAiB,EACnC,UAAsB,EAAgB,aAAa,EACrD;AAWJ,CATA,EAAS,WAAiB;EACxB,IAAM,IAAY,EAAK,KAAK,MAAQ,EAAI,MAAM;AAC9C,MAAI,GAAQ,WAAW,EAAU,UAAU,GAAQ,OAAO,GAAO,MAAM,MAAU,EAAU,GAAG,CAAE;AAEhG,EADA,IAAS,GACT,GAAS;EACT,IAAM,IAAe,EAAS,GAAI,EAAe;AACjD,EAAI,KAAc,EAAgB,WAAW,GAAc,CAAC;GAC5D,EAEF,EAAa,KAAK,EAAQ"}
1
+ {"version":3,"file":"effect.js","names":[],"sources":["../../src/hooks/effect.ts"],"sourcesContent":["import { useController } from './controller.ts';\nimport { useDisconnectCallback } from './disconnect.ts';\nimport type { ReadonlyRef, RefValues } from './ref.ts';\n\n/** React to observable (reference) changes. */\nexport function useEffect<const TDeps extends readonly ReadonlyRef<any>[]>(\n deps: TDeps,\n callback: (...values: RefValues<TDeps>) => (() => void) | void,\n): void {\n let cleanupCallback: (() => void) | void;\n let values: any[] | undefined;\n\n const cleanup = () => {\n const callback = cleanupCallback;\n cleanupCallback = undefined;\n callback?.();\n };\n\n useController().onNotify.push((): void => {\n const newValues = deps.map((dep) => dep.value);\n if (values?.length === newValues.length && values?.every((value, i) => value === newValues[i])) return;\n values = newValues;\n cleanup();\n cleanupCallback = callback(...(values as any));\n });\n\n useDisconnectCallback(cleanup);\n}\n"],"mappings":";;;AAKA,SAAgB,EACd,GACA,GACM;CACN,IAAI,GACA,GAEE,UAAgB;EACpB,IAAM,IAAW;AAEjB,EADA,IAAkB,KAAA,GAClB,KAAY;;AAWd,CARA,GAAe,CAAC,SAAS,WAAiB;EACxC,IAAM,IAAY,EAAK,KAAK,MAAQ,EAAI,MAAM;AAC1C,KAAQ,WAAW,EAAU,UAAU,GAAQ,OAAO,GAAO,MAAM,MAAU,EAAU,GAAG,KAC9F,IAAS,GACT,GAAS,EACT,IAAkB,EAAS,GAAI,EAAe;GAC9C,EAEF,EAAsB,EAAQ"}
@@ -0,0 +1,9 @@
1
+ import type { ReadonlyRef } from './ref.ts';
2
+ /** Use a reference (reactive state) bound to the associated form. */
3
+ export declare function useForm(): ReadonlyRef<HTMLFormElement | null>;
4
+ /** Use a reference (reactive state) bound to the form disabled state. */
5
+ export declare function useFormDisabled(): ReadonlyRef<boolean>;
6
+ /** Register a callback for form resets. */
7
+ export declare function useFormResetCallback(callback: () => void): void;
8
+ /** Register a callback for form restorations. */
9
+ export declare function useFormRestoreCallback(callback: (state: string | File | FormData, reason: 'restore' | 'autocomplete') => void): void;
@@ -0,0 +1,23 @@
1
+ import { useController as e } from "./controller.js";
2
+ //#region src/hooks/form.ts
3
+ function t() {
4
+ return a().refForm;
5
+ }
6
+ function n() {
7
+ return a().refDisabled;
8
+ }
9
+ function r(e) {
10
+ a().onReset.push(e);
11
+ }
12
+ function i(e) {
13
+ a().onRestore.push(e);
14
+ }
15
+ function a() {
16
+ let { formAssociated: t } = e();
17
+ if (!t) throw Error("form hooks must be called in a form-associated component");
18
+ return t;
19
+ }
20
+ //#endregion
21
+ export { t as useForm, n as useFormDisabled, r as useFormResetCallback, i as useFormRestoreCallback };
22
+
23
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","names":[],"sources":["../../src/hooks/form.ts"],"sourcesContent":["import type { Controller } from '../internal/controller.ts';\nimport { useController } from './controller.ts';\nimport type { ReadonlyRef } from './ref.ts';\n\n/** Use a reference (reactive state) bound to the associated form. */\nexport function useForm(): ReadonlyRef<HTMLFormElement | null> {\n return useFormAssociated().refForm;\n}\n\n/** Use a reference (reactive state) bound to the form disabled state. */\nexport function useFormDisabled(): ReadonlyRef<boolean> {\n return useFormAssociated().refDisabled;\n}\n\n/** Register a callback for form resets. */\nexport function useFormResetCallback(callback: () => void): void {\n useFormAssociated().onReset.push(callback);\n}\n\n/** Register a callback for form restorations. */\nexport function useFormRestoreCallback(\n callback: (state: string | File | FormData, reason: 'restore' | 'autocomplete') => void,\n): void {\n useFormAssociated().onRestore.push(callback);\n}\n\nfunction useFormAssociated(): Controller['formAssociated'] & {} {\n const { formAssociated } = useController();\n if (!formAssociated) throw new Error('form hooks must be called in a form-associated component');\n return formAssociated;\n}\n"],"mappings":";;AAKA,SAAgB,IAA+C;AAC7D,QAAO,GAAmB,CAAC;;AAI7B,SAAgB,IAAwC;AACtD,QAAO,GAAmB,CAAC;;AAI7B,SAAgB,EAAqB,GAA4B;AAC/D,IAAmB,CAAC,QAAQ,KAAK,EAAS;;AAI5C,SAAgB,EACd,GACM;AACN,IAAmB,CAAC,UAAU,KAAK,EAAS;;AAG9C,SAAS,IAAuD;CAC9D,IAAM,EAAE,sBAAmB,GAAe;AAC1C,KAAI,CAAC,EAAgB,OAAU,MAAM,2DAA2D;AAChG,QAAO"}
@@ -1,2 +1,2 @@
1
- /** Get the component host element. */
1
+ /** Use the component host element. */
2
2
  export declare function useHost(): HTMLElement;
@@ -1,4 +1,4 @@
1
- import { useContext as e } from "./context.js";
1
+ import { useController as e } from "./controller.js";
2
2
  //#region src/hooks/host.ts
3
3
  function t() {
4
4
  return e().host;
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","names":[],"sources":["../../src/hooks/host.ts"],"sourcesContent":["import { useContext } from './context.ts';\n\n/** Get the component host element. */\nexport function useHost(): HTMLElement {\n return useContext().host;\n}\n"],"mappings":";;AAGA,SAAgB,IAAuB;AACrC,QAAO,GAAY,CAAC"}
1
+ {"version":3,"file":"host.js","names":[],"sources":["../../src/hooks/host.ts"],"sourcesContent":["import { useController } from './controller.ts';\n\n/** Use the component host element. */\nexport function useHost(): HTMLElement {\n return useController().host;\n}\n"],"mappings":";;AAGA,SAAgB,IAAuB;AACrC,QAAO,GAAe,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** Use the element internals. */
2
+ export declare function useElementInternals(): ElementInternals;
@@ -0,0 +1,9 @@
1
+ import { useController as e } from "./controller.js";
2
+ //#region src/hooks/internals.ts
3
+ function t() {
4
+ return e().attachInternals();
5
+ }
6
+ //#endregion
7
+ export { t as useElementInternals };
8
+
9
+ //# sourceMappingURL=internals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internals.js","names":[],"sources":["../../src/hooks/internals.ts"],"sourcesContent":["import { useController } from './controller.ts';\n\n/** Use the element internals. */\nexport function useElementInternals(): ElementInternals {\n return useController().attachInternals();\n}\n"],"mappings":";;AAGA,SAAgB,IAAwC;AACtD,QAAO,GAAe,CAAC,iBAAiB"}
@@ -0,0 +1,3 @@
1
+ import type { ReadonlyRef } from './ref.ts';
2
+ /** Use a reference (reactive state) bound to the parent node. */
3
+ export declare function useParent(): ReadonlyRef<ParentNode | null>;
@@ -0,0 +1,9 @@
1
+ import { useController as e } from "./controller.js";
2
+ //#region src/hooks/parent.ts
3
+ function t() {
4
+ return e().refParent;
5
+ }
6
+ //#endregion
7
+ export { t as useParent };
8
+
9
+ //# sourceMappingURL=parent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parent.js","names":[],"sources":["../../src/hooks/parent.ts"],"sourcesContent":["import { useController } from './controller.ts';\nimport type { ReadonlyRef } from './ref.ts';\n\n/** Use a reference (reactive state) bound to the parent node. */\nexport function useParent(): ReadonlyRef<ParentNode | null> {\n return useController().refParent;\n}\n"],"mappings":";;AAIA,SAAgB,IAA4C;AAC1D,QAAO,GAAe,CAAC"}
@@ -1,3 +1,4 @@
1
+ import type { $$ref } from '../internal/controller.ts';
1
2
  export interface Ref<T> extends ReadonlyRef<T> {
2
3
  value: T;
3
4
  }
@@ -9,7 +10,5 @@ export interface ReadonlyRef<T> {
9
10
  export type RefValues<T> = T extends readonly any[] ? {
10
11
  [K in keyof T]: T[K] extends ReadonlyRef<infer V> ? V : never;
11
12
  } : never;
12
- declare const $$ref: unique symbol;
13
13
  /** Use a reference (reactive state) value. */
14
14
  export declare function useRef<T>(initialValue: T, onChange?: (value: T) => void): Ref<T>;
15
- export {};
package/dist/hooks/ref.js CHANGED
@@ -1,19 +1,9 @@
1
- import { useContext as e } from "./context.js";
1
+ import { useController as e } from "./controller.js";
2
2
  //#region src/hooks/ref.ts
3
- var t = Symbol();
4
- function n(n, r) {
5
- let { notify: i } = e(), a = n;
6
- return {
7
- [t]: !0,
8
- get value() {
9
- return a;
10
- },
11
- set value(e) {
12
- e !== a && (a = e, r?.(a), i());
13
- }
14
- };
3
+ function t(t, n) {
4
+ return e().createRef(t, n);
15
5
  }
16
6
  //#endregion
17
- export { n as useRef };
7
+ export { t as useRef };
18
8
 
19
9
  //# sourceMappingURL=ref.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ref.js","names":[],"sources":["../../src/hooks/ref.ts"],"sourcesContent":["import { useContext } from './context.ts';\n\nexport interface Ref<T> extends ReadonlyRef<T> {\n value: T;\n}\n\nexport interface ReadonlyRef<T> {\n /** @hidden */\n [$$ref]: unknown;\n readonly value: T;\n}\n\nexport type RefValues<T> = T extends readonly any[]\n ? { [K in keyof T]: T[K] extends ReadonlyRef<infer V> ? V : never }\n : never;\n\nconst $$ref = Symbol();\n\n/** Use a reference (reactive state) value. */\nexport function useRef<T>(initialValue: T, onChange?: (value: T) => void): Ref<T> {\n const { notify } = useContext();\n let value = initialValue;\n\n return {\n [$$ref]: true,\n get value() {\n return value;\n },\n set value(newValue) {\n if (newValue === value) return;\n value = newValue;\n onChange?.(value);\n notify();\n },\n };\n}\n"],"mappings":";;AAgBA,IAAM,IAAQ,QAAQ;AAGtB,SAAgB,EAAU,GAAiB,GAAuC;CAChF,IAAM,EAAE,cAAW,GAAY,EAC3B,IAAQ;AAEZ,QAAO;GACJ,IAAQ;EACT,IAAI,QAAQ;AACV,UAAO;;EAET,IAAI,MAAM,GAAU;AACd,SAAa,MACjB,IAAQ,GACR,IAAW,EAAM,EACjB,GAAQ;;EAEX"}
1
+ {"version":3,"file":"ref.js","names":[],"sources":["../../src/hooks/ref.ts"],"sourcesContent":["import type { $$ref } from '../internal/controller.ts';\nimport { useController } from './controller.ts';\n\nexport interface Ref<T> extends ReadonlyRef<T> {\n value: T;\n}\n\nexport interface ReadonlyRef<T> {\n /** @hidden */\n [$$ref]: unknown;\n readonly value: T;\n}\n\nexport type RefValues<T> = T extends readonly any[]\n ? { [K in keyof T]: T[K] extends ReadonlyRef<infer V> ? V : never }\n : never;\n\n/** Use a reference (reactive state) value. */\nexport function useRef<T>(initialValue: T, onChange?: (value: T) => void): Ref<T> {\n return useController().createRef(initialValue, onChange);\n}\n"],"mappings":";;AAkBA,SAAgB,EAAU,GAAiB,GAAuC;AAChF,QAAO,GAAe,CAAC,UAAU,GAAc,EAAS"}
@@ -3,8 +3,8 @@ export interface RouteOptions {
3
3
  readonly match?: 'prefix' | 'exact' | RegExp;
4
4
  readonly source?: 'pathname' | 'hash';
5
5
  }
6
- export type RouteMatchArray = readonly [string, ...string[]] & {
6
+ export type RouteMatch = readonly [string, ...string[]] & {
7
7
  readonly groups: Record<string, string>;
8
8
  };
9
9
  /** Use a reference (reactive state) bound to route matching. */
10
- export declare function useRoute(path: string | readonly string[], { match, source }?: RouteOptions): Ref<RouteMatchArray | null>;
10
+ export declare function useRoute(path: string | readonly string[], { match, source }?: RouteOptions): Ref<RouteMatch | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"route.js","names":[],"sources":["../../src/hooks/route.ts"],"sourcesContent":["import { getRouter } from '../router.ts';\nimport { useEffect } from './effect.ts';\nimport { type Ref, useRef } from './ref.ts';\nimport { useStore } from './store.ts';\n\nexport interface RouteOptions {\n readonly match?: 'prefix' | 'exact' | RegExp;\n readonly source?: 'pathname' | 'hash';\n}\n\nexport type RouteMatchArray = readonly [string, ...string[]] & { readonly groups: Record<string, string> };\n\n/** Use a reference (reactive state) bound to route matching. */\nexport function useRoute(\n path: string | readonly string[],\n { match = 'prefix', source = 'pathname' }: RouteOptions = {},\n): Ref<RouteMatchArray | null> {\n const matchRx = match === 'exact' ? /^$/u : match === 'prefix' ? /^.*$/u : match;\n const paths = Array.isArray(path) ? (path as readonly string[]) : [path as string];\n const refRoute = useStore(getRouter(), (state) => state[source]);\n const refMatch = useRef<RouteMatchArray | null>(getMatch(refRoute.value));\n\n useEffect([refRoute], (route) => {\n refMatch.value = getMatch(route);\n });\n\n return refMatch;\n\n function getMatch(route: string): RouteMatchArray | null {\n const prefix = paths.find((path) => route.endsWith(path)) ?? null;\n if (prefix == null) return null;\n route = route.slice(prefix.length);\n return route.match(matchRx) as RouteMatchArray | null;\n }\n}\n"],"mappings":";;;;;AAaA,SAAgB,EACd,GACA,EAAE,WAAQ,UAAU,YAAS,eAA6B,EAAE,EAC/B;CAC7B,IAAM,IAAU,MAAU,UAAU,QAAQ,MAAU,WAAW,UAAU,GACrE,IAAQ,MAAM,QAAQ,EAAK,GAAI,IAA6B,CAAC,EAAe,EAC5E,IAAW,EAAS,GAAW,GAAG,MAAU,EAAM,GAAQ,EAC1D,IAAW,EAA+B,EAAS,EAAS,MAAM,CAAC;AAMzE,QAJA,EAAU,CAAC,EAAS,GAAG,MAAU;AAC/B,IAAS,QAAQ,EAAS,EAAM;GAChC,EAEK;CAEP,SAAS,EAAS,GAAuC;EACvD,IAAM,IAAS,EAAM,MAAM,MAAS,EAAM,SAAS,EAAK,CAAC,IAAI;AAG7D,SAFI,KAAU,OAAa,QAC3B,IAAQ,EAAM,MAAM,EAAO,OAAO,EAC3B,EAAM,MAAM,EAAQ"}
1
+ {"version":3,"file":"route.js","names":[],"sources":["../../src/hooks/route.ts"],"sourcesContent":["import { getRouter } from '../router.ts';\nimport { useEffect } from './effect.ts';\nimport { type Ref, useRef } from './ref.ts';\nimport { useStore } from './store.ts';\n\nexport interface RouteOptions {\n readonly match?: 'prefix' | 'exact' | RegExp;\n readonly source?: 'pathname' | 'hash';\n}\n\nexport type RouteMatch = readonly [string, ...string[]] & { readonly groups: Record<string, string> };\n\n/** Use a reference (reactive state) bound to route matching. */\nexport function useRoute(\n path: string | readonly string[],\n { match = 'prefix', source = 'pathname' }: RouteOptions = {},\n): Ref<RouteMatch | null> {\n const matchRx = match === 'exact' ? /^$/u : match === 'prefix' ? /^.*$/u : match;\n const paths = Array.isArray(path) ? (path as readonly string[]) : [path as string];\n const refRoute = useStore(getRouter(), (state) => state[source]);\n const refMatch = useRef<RouteMatch | null>(getMatch(refRoute.value));\n\n useEffect([refRoute], (route) => {\n refMatch.value = getMatch(route);\n });\n\n return refMatch;\n\n function getMatch(route: string): RouteMatch | null {\n const prefix = paths.find((path) => route.endsWith(path)) ?? null;\n if (prefix == null) return null;\n route = route.slice(prefix.length);\n return route.match(matchRx) as RouteMatch | null;\n }\n}\n"],"mappings":";;;;;AAaA,SAAgB,EACd,GACA,EAAE,WAAQ,UAAU,YAAS,eAA6B,EAAE,EACpC;CACxB,IAAM,IAAU,MAAU,UAAU,QAAQ,MAAU,WAAW,UAAU,GACrE,IAAQ,MAAM,QAAQ,EAAK,GAAI,IAA6B,CAAC,EAAe,EAC5E,IAAW,EAAS,GAAW,GAAG,MAAU,EAAM,GAAQ,EAC1D,IAAW,EAA0B,EAAS,EAAS,MAAM,CAAC;AAMpE,QAJA,EAAU,CAAC,EAAS,GAAG,MAAU;AAC/B,IAAS,QAAQ,EAAS,EAAM;GAChC,EAEK;CAEP,SAAS,EAAS,GAAkC;EAClD,IAAM,IAAS,EAAM,MAAM,MAAS,EAAM,SAAS,EAAK,CAAC,IAAI;AAG7D,SAFI,KAAU,OAAa,QAC3B,IAAQ,EAAM,MAAM,EAAO,OAAO,EAC3B,EAAM,MAAM,EAAQ"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,14 @@
1
1
  export * from './component.ts';
2
+ export * from './hooks/async.ts';
2
3
  export * from './hooks/attributes.ts';
3
- export * from './hooks/child-effect.ts';
4
+ export * from './hooks/child.ts';
5
+ export * from './hooks/disconnect.ts';
6
+ export * from './hooks/document.ts';
4
7
  export * from './hooks/effect.ts';
8
+ export * from './hooks/form.ts';
5
9
  export * from './hooks/host.ts';
6
- export * from './hooks/loading.ts';
10
+ export * from './hooks/internals.ts';
11
+ export * from './hooks/parent.ts';
7
12
  export * from './hooks/ref.ts';
8
13
  export * from './hooks/route.ts';
9
14
  export * from './hooks/store.ts';
package/dist/index.js CHANGED
@@ -1,13 +1,18 @@
1
1
  import { useRef as e } from "./hooks/ref.js";
2
2
  import { defineComponent as t } from "./component.js";
3
- import { useEffect as n } from "./hooks/effect.js";
4
- import { useHost as r } from "./hooks/host.js";
5
- import { useAttributes as i } from "./hooks/attributes.js";
6
- import { useChildEffect as a } from "./hooks/child-effect.js";
7
- import { useLoading as o } from "./hooks/loading.js";
8
- import { createStore as s } from "./store.js";
9
- import { getRouter as c } from "./router.js";
10
- import { useStore as l } from "./hooks/store.js";
11
- import { useRoute as u } from "./hooks/route.js";
12
- import { html as d } from "./html.js";
13
- export { s as createStore, t as defineComponent, c as getRouter, d as h, d as html, i as useAttributes, a as useChildEffect, n as useEffect, r as useHost, o as useLoading, e as useRef, u as useRoute, l as useStore };
3
+ import { useDisconnectCallback as n } from "./hooks/disconnect.js";
4
+ import { useEffect as r } from "./hooks/effect.js";
5
+ import { useAsync as i } from "./hooks/async.js";
6
+ import { useHost as a } from "./hooks/host.js";
7
+ import { useAttributes as o } from "./hooks/attributes.js";
8
+ import { useChildEffect as s } from "./hooks/child.js";
9
+ import { useDocument as c } from "./hooks/document.js";
10
+ import { useForm as l, useFormDisabled as u, useFormResetCallback as d, useFormRestoreCallback as f } from "./hooks/form.js";
11
+ import { useElementInternals as p } from "./hooks/internals.js";
12
+ import { useParent as m } from "./hooks/parent.js";
13
+ import { createStore as h } from "./store.js";
14
+ import { getRouter as g } from "./router.js";
15
+ import { useStore as _ } from "./hooks/store.js";
16
+ import { useRoute as v } from "./hooks/route.js";
17
+ import { html as y } from "./html.js";
18
+ export { h as createStore, t as defineComponent, g as getRouter, y as h, y as html, i as useAsync, o as useAttributes, s as useChildEffect, n as useDisconnectCallback, c as useDocument, r as useEffect, p as useElementInternals, l as useForm, u as useFormDisabled, d as useFormResetCallback, f as useFormRestoreCallback, a as useHost, m as useParent, e as useRef, v as useRoute, _ as useStore };
@@ -0,0 +1,33 @@
1
+ import { type ReadonlyRef, type Ref } from '../hooks/ref.ts';
2
+ import { type Callbacks } from './callbacks.ts';
3
+ export interface Controller {
4
+ readonly host: HTMLElement;
5
+ readonly onNotify: Callbacks;
6
+ readonly onDisconnect: Callbacks;
7
+ readonly refDocument: ReadonlyRef<Document>;
8
+ readonly refParent: ReadonlyRef<ParentNode | null>;
9
+ readonly formAssociated: {
10
+ readonly refForm: ReadonlyRef<HTMLFormElement | null>;
11
+ readonly refDisabled: ReadonlyRef<boolean>;
12
+ readonly onReset: Callbacks;
13
+ readonly onRestore: Callbacks<[state: string | File | FormData, reason: 'restore' | 'autocomplete']>;
14
+ } | undefined;
15
+ readonly createRef: <T>(value: T, onChange?: (value: T) => void) => Ref<T>;
16
+ readonly attachInternals: () => ElementInternals;
17
+ readonly connectedCallback: () => void;
18
+ readonly connectedMoveCallback: () => void;
19
+ readonly disconnectedCallback: () => void;
20
+ readonly adoptedCallback: () => void;
21
+ readonly formDisabledCallback: (disabled: boolean) => void;
22
+ readonly formResetCallback: () => void;
23
+ readonly formStateRestoreCallback: (state: string | File | FormData, reason: 'restore' | 'autocomplete') => void;
24
+ }
25
+ export interface ControllerConfig {
26
+ readonly host: HTMLElement;
27
+ readonly formAssociated: boolean;
28
+ readonly render: (controller: Controller) => void;
29
+ readonly attachInternals: () => ElementInternals;
30
+ }
31
+ export declare const $$ref: unique symbol;
32
+ export declare function getController(): Controller | undefined;
33
+ export declare function createController({ host, formAssociated, render, attachInternals }: ControllerConfig): Controller;
@@ -0,0 +1,72 @@
1
+ import { createCallbacks as e } from "./callbacks.js";
2
+ //#region src/internal/controller.ts
3
+ var t = Symbol(), n = [];
4
+ function r() {
5
+ return n.at(-1);
6
+ }
7
+ function i({ host: r, formAssociated: i, render: a, attachInternals: o }) {
8
+ let s = !1, c = !1, l, u, d = e(), f = (e, n) => {
9
+ let r = e;
10
+ return {
11
+ [t]: !0,
12
+ get value() {
13
+ return r;
14
+ },
15
+ set value(e) {
16
+ e !== r && (r = e, n?.(r), !(!c || s) && (s = !0, queueMicrotask(() => {
17
+ s = !1, d.run();
18
+ })));
19
+ }
20
+ };
21
+ }, p = {
22
+ host: r,
23
+ onNotify: d,
24
+ onDisconnect: e(),
25
+ refDocument: f(r.ownerDocument),
26
+ refParent: f(r.parentNode),
27
+ formAssociated: i ? {
28
+ refForm: f(null),
29
+ refDisabled: f(!1),
30
+ onReset: e(),
31
+ onRestore: e()
32
+ } : void 0,
33
+ createRef: f,
34
+ attachInternals: () => l ??= o(),
35
+ connectedCallback: () => {
36
+ c = !0, p.refParent.value = r.parentNode;
37
+ try {
38
+ n.push(p), a(p);
39
+ } finally {
40
+ n.pop();
41
+ }
42
+ u?.type === "reset" ? p.formAssociated?.onReset.run() : u?.type === "restore" && p.formAssociated?.onRestore.run(u.state, u.reason), p.onNotify.run();
43
+ },
44
+ connectedMoveCallback: () => {
45
+ p.refParent.value = r.parentNode, p.onNotify.run();
46
+ },
47
+ disconnectedCallback: () => {
48
+ p.onNotify.clear(), p.formAssociated?.onReset.clear(), p.formAssociated?.onRestore.clear(), p.onDisconnect.runAndClear();
49
+ },
50
+ adoptedCallback: () => {
51
+ p.refDocument.value = r.ownerDocument, p.onNotify.run();
52
+ },
53
+ formDisabledCallback: (e) => {
54
+ p.formAssociated && (p.formAssociated.refDisabled.value = e, p.onNotify.run());
55
+ },
56
+ formResetCallback: () => {
57
+ p.formAssociated && (c ? p.formAssociated.onReset.run() : u = { type: "reset" });
58
+ },
59
+ formStateRestoreCallback: (e, t) => {
60
+ p.formAssociated && (c ? p.formAssociated.onRestore.run(e, t) : u = {
61
+ type: "restore",
62
+ state: e,
63
+ reason: t
64
+ });
65
+ }
66
+ };
67
+ return p;
68
+ }
69
+ //#endregion
70
+ export { i as createController, r as getController };
71
+
72
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","names":[],"sources":["../../src/internal/controller.ts"],"sourcesContent":["import { type ReadonlyRef, type Ref } from '../hooks/ref.ts';\nimport { type Callbacks, createCallbacks } from './callbacks.ts';\n\nexport interface Controller {\n readonly host: HTMLElement;\n readonly onNotify: Callbacks;\n readonly onDisconnect: Callbacks;\n readonly refDocument: ReadonlyRef<Document>;\n readonly refParent: ReadonlyRef<ParentNode | null>;\n readonly formAssociated:\n | {\n readonly refForm: ReadonlyRef<HTMLFormElement | null>;\n readonly refDisabled: ReadonlyRef<boolean>;\n readonly onReset: Callbacks;\n readonly onRestore: Callbacks<[state: string | File | FormData, reason: 'restore' | 'autocomplete']>;\n }\n | undefined;\n readonly createRef: <T>(value: T, onChange?: (value: T) => void) => Ref<T>;\n readonly attachInternals: () => ElementInternals;\n readonly connectedCallback: () => void;\n readonly connectedMoveCallback: () => void;\n readonly disconnectedCallback: () => void;\n readonly adoptedCallback: () => void;\n readonly formDisabledCallback: (disabled: boolean) => void;\n readonly formResetCallback: () => void;\n readonly formStateRestoreCallback: (state: string | File | FormData, reason: 'restore' | 'autocomplete') => void;\n}\n\nexport interface ControllerConfig {\n readonly host: HTMLElement;\n readonly formAssociated: boolean;\n readonly render: (controller: Controller) => void;\n readonly attachInternals: () => ElementInternals;\n}\n\nexport const $$ref = Symbol();\n\nconst controllers: Controller[] = [];\n\nexport function getController(): Controller | undefined {\n return controllers.at(-1);\n}\n\nexport function createController({ host, formAssociated, render, attachInternals }: ControllerConfig): Controller {\n let notifying = false;\n let connected = false;\n let internals: ElementInternals | undefined;\n\n let deferredFormUpdate:\n | { readonly type: 'reset' }\n | { readonly type: 'restore'; state: string | File | FormData; reason: 'restore' | 'autocomplete' }\n | undefined;\n\n const onNotify = createCallbacks();\n\n const createRef = <T>(initialValue: T, onChange?: (value: T) => void): Ref<T> => {\n let value = initialValue;\n\n return {\n [$$ref]: true,\n get value() {\n return value;\n },\n set value(newValue) {\n if (newValue === value) return;\n value = newValue;\n onChange?.(value);\n if (!connected || notifying) return;\n notifying = true;\n\n queueMicrotask(() => {\n notifying = false;\n onNotify.run();\n });\n },\n };\n };\n\n const controller = {\n host,\n onNotify,\n onDisconnect: createCallbacks(),\n refDocument: createRef(host.ownerDocument),\n refParent: createRef(host.parentNode),\n formAssociated: formAssociated\n ? {\n refForm: createRef<HTMLFormElement | null>(null),\n refDisabled: createRef(false),\n onReset: createCallbacks(),\n onRestore: createCallbacks(),\n }\n : undefined,\n createRef,\n attachInternals: () => {\n return (internals ??= attachInternals());\n },\n connectedCallback: () => {\n connected = true;\n controller.refParent.value = host.parentNode;\n\n try {\n controllers.push(controller);\n render(controller);\n } finally {\n controllers.pop();\n }\n\n if (deferredFormUpdate?.type === 'reset') {\n controller.formAssociated?.onReset.run();\n } else if (deferredFormUpdate?.type === 'restore') {\n controller.formAssociated?.onRestore.run(deferredFormUpdate.state, deferredFormUpdate.reason);\n }\n\n controller.onNotify.run();\n },\n connectedMoveCallback: () => {\n controller.refParent.value = host.parentNode;\n controller.onNotify.run();\n },\n disconnectedCallback: () => {\n controller.onNotify.clear();\n controller.formAssociated?.onReset.clear();\n controller.formAssociated?.onRestore.clear();\n controller.onDisconnect.runAndClear();\n },\n adoptedCallback: () => {\n controller.refDocument.value = host.ownerDocument;\n controller.onNotify.run();\n },\n formDisabledCallback: (disabled: boolean) => {\n if (!controller.formAssociated) return;\n controller.formAssociated.refDisabled.value = disabled;\n controller.onNotify.run();\n },\n formResetCallback: () => {\n if (!controller.formAssociated) return;\n if (connected) controller.formAssociated.onReset.run();\n else deferredFormUpdate = { type: 'reset' };\n },\n formStateRestoreCallback: (state, reason) => {\n if (!controller.formAssociated) return;\n if (connected) controller.formAssociated.onRestore.run(state, reason);\n else deferredFormUpdate = { type: 'restore', state, reason };\n },\n } satisfies Controller;\n\n return controller;\n}\n"],"mappings":";;AAmCA,IAAa,IAAQ,QAAQ,EAEvB,IAA4B,EAAE;AAEpC,SAAgB,IAAwC;AACtD,QAAO,EAAY,GAAG,GAAG;;AAG3B,SAAgB,EAAiB,EAAE,SAAM,mBAAgB,WAAQ,sBAAiD;CAChH,IAAI,IAAY,IACZ,IAAY,IACZ,GAEA,GAKE,IAAW,GAAiB,EAE5B,KAAgB,GAAiB,MAA0C;EAC/E,IAAI,IAAQ;AAEZ,SAAO;IACJ,IAAQ;GACT,IAAI,QAAQ;AACV,WAAO;;GAET,IAAI,MAAM,GAAU;AACd,UAAa,MACjB,IAAQ,GACR,IAAW,EAAM,EACb,GAAC,KAAa,OAClB,IAAY,IAEZ,qBAAqB;AAEnB,KADA,IAAY,IACZ,EAAS,KAAK;MACd;;GAEL;IAGG,IAAa;EACjB;EACA;EACA,cAAc,GAAiB;EAC/B,aAAa,EAAU,EAAK,cAAc;EAC1C,WAAW,EAAU,EAAK,WAAW;EACrC,gBAAgB,IACZ;GACE,SAAS,EAAkC,KAAK;GAChD,aAAa,EAAU,GAAM;GAC7B,SAAS,GAAiB;GAC1B,WAAW,GAAiB;GAC7B,GACD,KAAA;EACJ;EACA,uBACU,MAAc,GAAiB;EAEzC,yBAAyB;AAEvB,GADA,IAAY,IACZ,EAAW,UAAU,QAAQ,EAAK;AAElC,OAAI;AAEF,IADA,EAAY,KAAK,EAAW,EAC5B,EAAO,EAAW;aACV;AACR,MAAY,KAAK;;AASnB,GANI,GAAoB,SAAS,UAC/B,EAAW,gBAAgB,QAAQ,KAAK,GAC/B,GAAoB,SAAS,aACtC,EAAW,gBAAgB,UAAU,IAAI,EAAmB,OAAO,EAAmB,OAAO,EAG/F,EAAW,SAAS,KAAK;;EAE3B,6BAA6B;AAE3B,GADA,EAAW,UAAU,QAAQ,EAAK,YAClC,EAAW,SAAS,KAAK;;EAE3B,4BAA4B;AAI1B,GAHA,EAAW,SAAS,OAAO,EAC3B,EAAW,gBAAgB,QAAQ,OAAO,EAC1C,EAAW,gBAAgB,UAAU,OAAO,EAC5C,EAAW,aAAa,aAAa;;EAEvC,uBAAuB;AAErB,GADA,EAAW,YAAY,QAAQ,EAAK,eACpC,EAAW,SAAS,KAAK;;EAE3B,uBAAuB,MAAsB;AACtC,KAAW,mBAChB,EAAW,eAAe,YAAY,QAAQ,GAC9C,EAAW,SAAS,KAAK;;EAE3B,yBAAyB;AAClB,KAAW,mBACZ,IAAW,EAAW,eAAe,QAAQ,KAAK,GACjD,IAAqB,EAAE,MAAM,SAAS;;EAE7C,2BAA2B,GAAO,MAAW;AACtC,KAAW,mBACZ,IAAW,EAAW,eAAe,UAAU,IAAI,GAAO,EAAO,GAChE,IAAqB;IAAE,MAAM;IAAW;IAAO;IAAQ;;EAE/D;AAED,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seahax/elemental",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Functional, reactive, web component base library.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- {"version":3,"file":"child-effect.js","names":[],"sources":["../../src/hooks/child-effect.ts"],"sourcesContent":["import { useEffect } from './effect.ts';\nimport { useHost } from './host.ts';\nimport { useRef } from './ref.ts';\n\n/** React to child list changes (non-recursive). */\nexport function useChildEffect(callback: () => (() => void) | void): void {\n const host = useHost();\n const ref = useRef(0);\n\n const observer = new MutationObserver((mutation) => {\n if (mutation.some((m) => m.type === 'childList')) {\n ref.value = (ref.value + 1) % Number.MAX_SAFE_INTEGER;\n }\n });\n\n useEffect([], () => {\n observer.observe(host, { childList: true });\n return () => observer.disconnect();\n });\n\n useEffect([ref], () => {\n return callback();\n });\n}\n"],"mappings":";;;;AAKA,SAAgB,EAAe,GAA2C;CACxE,IAAM,IAAO,GAAS,EAChB,IAAM,EAAO,EAAE,EAEf,IAAW,IAAI,kBAAkB,MAAa;AAClD,EAAI,EAAS,MAAM,MAAM,EAAE,SAAS,YAAY,KAC9C,EAAI,SAAS,EAAI,QAAQ;GAE3B;AAOF,CALA,EAAU,EAAE,SACV,EAAS,QAAQ,GAAM,EAAE,WAAW,IAAM,CAAC,QAC9B,EAAS,YAAY,EAClC,EAEF,EAAU,CAAC,EAAI,QACN,GAAU,CACjB"}
@@ -1,3 +0,0 @@
1
- import { type Context } from '../internal/context.ts';
2
- /** @internal Get the context of the currently rendering component. */
3
- export declare function useContext(): Context;
@@ -1,11 +0,0 @@
1
- import { contextStack as e } from "../internal/context.js";
2
- //#region src/hooks/context.ts
3
- function t() {
4
- let t = e.at(-1);
5
- if (!t) throw Error("hooks must be called inside a render function");
6
- return t;
7
- }
8
- //#endregion
9
- export { t as useContext };
10
-
11
- //# sourceMappingURL=context.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../../src/hooks/context.ts"],"sourcesContent":["import { type Context, contextStack } from '../internal/context.ts';\n\n/** @internal Get the context of the currently rendering component. */\nexport function useContext(): Context {\n const context = contextStack.at(-1);\n if (!context) throw new Error('hooks must be called inside a render function');\n return context;\n}\n"],"mappings":";;AAGA,SAAgB,IAAsB;CACpC,IAAM,IAAU,EAAa,GAAG,GAAG;AACnC,KAAI,CAAC,EAAS,OAAU,MAAM,gDAAgD;AAC9E,QAAO"}
@@ -1,11 +0,0 @@
1
- import { type ReadonlyRef, type Ref, type RefValues } from './ref.ts';
2
- export interface LoadingValue<TValue> {
3
- readonly value: TValue | undefined;
4
- readonly error: unknown;
5
- readonly isLoading: boolean;
6
- }
7
- export interface LoadingOptions {
8
- readonly debounceMs?: number;
9
- }
10
- /** Use a reference (reactive state) bound to an async loader function. */
11
- export declare function useLoading<const TDeps extends readonly ReadonlyRef<any>[], TValue>(deps: TDeps, callback: (signal: AbortSignal, ...values: RefValues<TDeps>) => Promise<TValue>, { debounceMs }?: LoadingOptions): Ref<LoadingValue<TValue>>;
@@ -1 +0,0 @@
1
- {"version":3,"file":"loading.js","names":[],"sources":["../../src/hooks/loading.ts"],"sourcesContent":["import { useEffect } from './effect.ts';\nimport { type ReadonlyRef, type Ref, type RefValues, useRef } from './ref.ts';\n\nexport interface LoadingValue<TValue> {\n readonly value: TValue | undefined;\n readonly error: unknown;\n readonly isLoading: boolean;\n}\n\nexport interface LoadingOptions {\n readonly debounceMs?: number;\n}\n\n/** Use a reference (reactive state) bound to an async loader function. */\nexport function useLoading<const TDeps extends readonly ReadonlyRef<any>[], TValue>(\n deps: TDeps,\n callback: (signal: AbortSignal, ...values: RefValues<TDeps>) => Promise<TValue>,\n { debounceMs }: LoadingOptions = {},\n): Ref<LoadingValue<TValue>> {\n const ref = useRef<LoadingValue<TValue>>({ value: undefined, error: undefined, isLoading: true });\n let skipDebounce = true;\n\n useEffect(deps, () => (...values) => {\n const ac = new AbortController();\n\n Promise.race(\n skipDebounce\n ? [Promise.resolve()]\n : [\n new Promise((resolve) => setTimeout(resolve, debounceMs)),\n new Promise((resolve) => ac.signal.addEventListener('abort', resolve, { once: true })),\n ],\n )\n .then(() => {\n if (ac.signal.aborted) return;\n return callback(ac.signal, ...(values as any));\n })\n .then((value) => {\n if (ac.signal.aborted) return;\n ref.value = { isLoading: false, value, error: undefined };\n })\n .catch((error: unknown) => {\n if (ac.signal.aborted) return;\n ref.value = { isLoading: false, value: undefined, error };\n });\n\n skipDebounce = false;\n return () => ac.abort();\n });\n\n // Skip the debounce again if the component unmounts.\n useEffect([], () => () => (skipDebounce = true));\n\n return ref;\n}\n"],"mappings":";;;AAcA,SAAgB,EACd,GACA,GACA,EAAE,kBAA+B,EAAE,EACR;CAC3B,IAAM,IAAM,EAA6B;EAAE,OAAO,KAAA;EAAW,OAAO,KAAA;EAAW,WAAW;EAAM,CAAC,EAC7F,IAAe;AAiCnB,QA/BA,EAAU,UAAa,GAAG,MAAW;EACnC,IAAM,IAAK,IAAI,iBAAiB;AAwBhC,SAtBA,QAAQ,KACN,IACI,CAAC,QAAQ,SAAS,CAAC,GACnB,CACE,IAAI,SAAS,MAAY,WAAW,GAAS,EAAW,CAAC,EACzD,IAAI,SAAS,MAAY,EAAG,OAAO,iBAAiB,SAAS,GAAS,EAAE,MAAM,IAAM,CAAC,CAAC,CACvF,CACN,CACE,WAAW;AACN,UAAG,OAAO,QACd,QAAO,EAAS,EAAG,QAAQ,GAAI,EAAe;IAC9C,CACD,MAAM,MAAU;AACX,KAAG,OAAO,YACd,EAAI,QAAQ;IAAE,WAAW;IAAO;IAAO,OAAO,KAAA;IAAW;IACzD,CACD,OAAO,MAAmB;AACrB,KAAG,OAAO,YACd,EAAI,QAAQ;IAAE,WAAW;IAAO,OAAO,KAAA;IAAW;IAAO;IACzD,EAEJ,IAAe,UACF,EAAG,OAAO;GACvB,EAGF,EAAU,EAAE,cAAe,IAAe,GAAM,EAEzC"}
@@ -1,13 +0,0 @@
1
- import { type Callbacks } from './callbacks.ts';
2
- export interface Context {
3
- readonly host: HTMLElement;
4
- readonly onNotify: Callbacks;
5
- readonly onDisconnect: Callbacks;
6
- readonly notify: () => void;
7
- }
8
- export interface ContextController {
9
- readonly connect: (callback: () => void) => void;
10
- readonly disconnect: () => void;
11
- }
12
- export declare const contextStack: Context[];
13
- export declare function createContextController(host: HTMLElement): ContextController;
@@ -1,32 +0,0 @@
1
- import { createCallbacks as e } from "./callbacks.js";
2
- //#region src/internal/context.ts
3
- var t = [];
4
- function n(n) {
5
- let r = !1, i = {
6
- host: n,
7
- onNotify: e(),
8
- onDisconnect: e(),
9
- notify: () => {
10
- r || (r = !0, queueMicrotask(() => {
11
- r = !1, i.onNotify.run();
12
- }));
13
- }
14
- };
15
- return {
16
- connect: (e) => {
17
- try {
18
- t.push(i), e();
19
- } finally {
20
- t.pop();
21
- }
22
- i.onNotify.run();
23
- },
24
- disconnect: () => {
25
- i.onNotify.clear(), i.onDisconnect.runAndClear();
26
- }
27
- };
28
- }
29
- //#endregion
30
- export { t as contextStack, n as createContextController };
31
-
32
- //# sourceMappingURL=context.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../../src/internal/context.ts"],"sourcesContent":["import { type Callbacks, createCallbacks } from './callbacks.ts';\n\nexport interface Context {\n readonly host: HTMLElement;\n readonly onNotify: Callbacks;\n readonly onDisconnect: Callbacks;\n readonly notify: () => void;\n}\n\nexport interface ContextController {\n readonly connect: (callback: () => void) => void;\n readonly disconnect: () => void;\n}\n\nexport const contextStack: Context[] = [];\n\nexport function createContextController(host: HTMLElement): ContextController {\n let notifying = false;\n\n const context: Context = {\n host,\n onNotify: createCallbacks(),\n onDisconnect: createCallbacks(),\n notify: () => {\n if (notifying) return;\n notifying = true;\n\n queueMicrotask(() => {\n notifying = false;\n context.onNotify.run();\n });\n },\n };\n\n return {\n connect: (callback) => {\n try {\n contextStack.push(context);\n callback();\n } finally {\n contextStack.pop();\n }\n\n context.onNotify.run();\n },\n disconnect: () => {\n context.onNotify.clear();\n context.onDisconnect.runAndClear();\n },\n };\n}\n"],"mappings":";;AAcA,IAAa,IAA0B,EAAE;AAEzC,SAAgB,EAAwB,GAAsC;CAC5E,IAAI,IAAY,IAEV,IAAmB;EACvB;EACA,UAAU,GAAiB;EAC3B,cAAc,GAAiB;EAC/B,cAAc;AACR,SACJ,IAAY,IAEZ,qBAAqB;AAEnB,IADA,IAAY,IACZ,EAAQ,SAAS,KAAK;KACtB;;EAEL;AAED,QAAO;EACL,UAAU,MAAa;AACrB,OAAI;AAEF,IADA,EAAa,KAAK,EAAQ,EAC1B,GAAU;aACF;AACR,MAAa,KAAK;;AAGpB,KAAQ,SAAS,KAAK;;EAExB,kBAAkB;AAEhB,GADA,EAAQ,SAAS,OAAO,EACxB,EAAQ,aAAa,aAAa;;EAErC"}
File without changes