@vizejs/composable 0.302.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.
@@ -0,0 +1,228 @@
1
+ import { ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from "vue";
2
+
3
+ //#region src/async-resource.d.ts
4
+ /** Lifecycle state of an asynchronous resource. */
5
+ type AsyncResourceStatus = "idle" | "pending" | "success" | "error" | "cancelled";
6
+ /** Context supplied to an asynchronous resource loader. */
7
+ interface AsyncResourceContext {
8
+ /** Signal aborted by cancellation, reset, scope disposal, or a newer execution. */
9
+ readonly signal: AbortSignal;
10
+ }
11
+ /** Explicit result of one asynchronous resource execution. */
12
+ type AsyncResourceExecution<Data, Failure> = {
13
+ readonly status: "success";
14
+ readonly data: Data;
15
+ } | {
16
+ readonly status: "error";
17
+ readonly error: Failure;
18
+ } | {
19
+ readonly status: "cancelled";
20
+ readonly reason: unknown;
21
+ } | {
22
+ readonly status: "superseded";
23
+ };
24
+ /** Options for {@link useAsyncResource}. */
25
+ interface UseAsyncResourceOptions<Data> {
26
+ /**
27
+ * Initial data restored by {@link AsyncResource.reset}.
28
+ *
29
+ * @default undefined
30
+ */
31
+ readonly initialData?: Data;
32
+ /**
33
+ * Abort the active execution when a newer execution starts.
34
+ *
35
+ * @default true
36
+ */
37
+ readonly cancelPrevious?: boolean;
38
+ /**
39
+ * Retain the current data while a new execution is pending.
40
+ *
41
+ * @default true
42
+ */
43
+ readonly keepData?: boolean;
44
+ /**
45
+ * Cancel an active execution when the current reactive scope is disposed.
46
+ *
47
+ * @default true
48
+ */
49
+ readonly scope?: boolean;
50
+ }
51
+ /** Reactive state and controls for an asynchronous loader. */
52
+ interface AsyncResource<Data, Arguments extends readonly unknown[], Failure> {
53
+ readonly data: Readonly<ShallowRef<Data | undefined>>;
54
+ readonly error: Readonly<ShallowRef<Failure | undefined>>;
55
+ readonly status: Readonly<Ref<AsyncResourceStatus>>;
56
+ readonly pending: ComputedRef<boolean>;
57
+ readonly execute: (...arguments_: Arguments) => Promise<AsyncResourceExecution<Data, Failure>>;
58
+ readonly cancel: (reason?: unknown) => boolean;
59
+ readonly reset: () => void;
60
+ }
61
+ /**
62
+ * Create a scoped, abortable asynchronous resource with latest-result-wins
63
+ * state. Every execution returns a discriminated result, so cancellation,
64
+ * supersession, loader failure, and successful `undefined` data stay distinct.
65
+ */
66
+ declare function useAsyncResource<Data, Arguments extends readonly unknown[], Failure = unknown>(loader: (context: AsyncResourceContext, ...arguments_: Arguments) => Promise<Data>, options?: UseAsyncResourceOptions<Data>): AsyncResource<Data, Arguments, Failure>;
67
+ //#endregion
68
+ //#region src/event-listener.d.ts
69
+ /** Options for {@link useEventListener}. */
70
+ interface UseEventListenerOptions {
71
+ /**
72
+ * Invoke the listener during the capture phase.
73
+ *
74
+ * @default false
75
+ */
76
+ readonly capture?: boolean;
77
+ /**
78
+ * Stop listening after the first event.
79
+ *
80
+ * @default false
81
+ */
82
+ readonly once?: boolean;
83
+ /**
84
+ * Declare that the listener does not cancel the event's default action.
85
+ *
86
+ * @default false
87
+ */
88
+ readonly passive?: boolean;
89
+ /**
90
+ * Stop listening when this signal is aborted.
91
+ *
92
+ * @default undefined
93
+ */
94
+ readonly signal?: AbortSignal;
95
+ /**
96
+ * Start listening during composable creation.
97
+ *
98
+ * @default true
99
+ */
100
+ readonly immediate?: boolean;
101
+ /**
102
+ * Reactive target update timing.
103
+ *
104
+ * @default "pre"
105
+ */
106
+ readonly flush?: "pre" | "post" | "sync";
107
+ }
108
+ /** Reactive controls returned by {@link useEventListener}. */
109
+ interface EventListenerControls {
110
+ /** Whether a concrete target currently owns the listener. */
111
+ readonly isListening: Readonly<Ref<boolean>>;
112
+ /**
113
+ * Begin listening.
114
+ *
115
+ * @returns Whether a new reactive listener was started.
116
+ */
117
+ readonly start: () => boolean;
118
+ /** Stop listening. Repeated calls are safe. */
119
+ readonly stop: () => void;
120
+ }
121
+ /**
122
+ * Attach an event listener to a reactive target and clean it up with the
123
+ * current reactive scope. Missing targets are valid during server rendering.
124
+ *
125
+ * @param target Reactive event target.
126
+ * @param event Event name.
127
+ * @param listener Typed event listener.
128
+ * @param options Listener lifecycle and scheduling options.
129
+ * @default options {}
130
+ */
131
+ declare function useEventListener<Key extends keyof WindowEventMap>(target: MaybeRefOrGetter<Window | null | undefined>, event: Key, listener: (event: WindowEventMap[Key]) => void, options?: UseEventListenerOptions): EventListenerControls;
132
+ declare function useEventListener<Key extends keyof DocumentEventMap>(target: MaybeRefOrGetter<Document | null | undefined>, event: Key, listener: (event: DocumentEventMap[Key]) => void, options?: UseEventListenerOptions): EventListenerControls;
133
+ declare function useEventListener<Key extends keyof HTMLElementEventMap>(target: MaybeRefOrGetter<HTMLElement | null | undefined>, event: Key, listener: (event: HTMLElementEventMap[Key]) => void, options?: UseEventListenerOptions): EventListenerControls;
134
+ declare function useEventListener(target: MaybeRefOrGetter<EventTarget | null | undefined>, event: string, listener: EventListener, options?: UseEventListenerOptions): EventListenerControls;
135
+ //#endregion
136
+ //#region src/locale.d.ts
137
+ /** Text flow reported by the internationalization runtime. */
138
+ type TextDirection = "ltr" | "rtl";
139
+ /** Locale selection options for {@link useLocale}. */
140
+ interface UseLocaleOptions {
141
+ /**
142
+ * Locale detector used when the reactive source has no value.
143
+ *
144
+ * @default navigator.language when available; otherwise undefined
145
+ */
146
+ readonly detect?: () => Intl.Locale | string | null | undefined;
147
+ /**
148
+ * Locale used when neither the source nor detector provides one.
149
+ *
150
+ * @default "en"
151
+ */
152
+ readonly fallback?: Intl.Locale | string;
153
+ }
154
+ /** Reactive locale metadata and cached formatter factories. */
155
+ interface LocaleControls {
156
+ /** Canonical Unicode locale identifier. */
157
+ readonly locale: ComputedRef<string>;
158
+ /** Parsed locale details supplied by the internationalization runtime. */
159
+ readonly details: ComputedRef<Intl.Locale>;
160
+ /** Native writing direction for the active locale. */
161
+ readonly direction: ComputedRef<TextDirection>;
162
+ /** Return a cached number formatter for the active locale and options. */
163
+ readonly number: (options?: Intl.NumberFormatOptions) => Intl.NumberFormat;
164
+ /** Return a cached date and time formatter for the active locale and options. */
165
+ readonly dateTime: (options?: Intl.DateTimeFormatOptions) => Intl.DateTimeFormat;
166
+ /** Return a cached list formatter for the active locale and options. */
167
+ readonly list: (options?: Intl.ListFormatOptions) => Intl.ListFormat;
168
+ /** Return a cached relative-time formatter for the active locale and options. */
169
+ readonly relativeTime: (options?: Intl.RelativeTimeFormatOptions) => Intl.RelativeTimeFormat;
170
+ }
171
+ /**
172
+ * Create reactive locale metadata and platform-native formatter factories.
173
+ *
174
+ * Equivalent formatter options reuse instances. The bounded cache follows the
175
+ * active locale automatically and prevents repeated constructor overhead in
176
+ * reactive render paths.
177
+ */
178
+ declare function useLocale(source?: MaybeRefOrGetter<Intl.Locale | string | null | undefined>, options?: UseLocaleOptions): LocaleControls;
179
+ //#endregion
180
+ //#region src/media-query.d.ts
181
+ /** Capability required to evaluate media queries. */
182
+ interface MediaQueryHost {
183
+ /** Create an observable result for a media query. */
184
+ readonly matchMedia: (query: string) => MediaQueryList;
185
+ }
186
+ /** Options for {@link useMediaQuery}. */
187
+ interface UseMediaQueryOptions {
188
+ /**
189
+ * Value exposed when no media-query capability is available.
190
+ *
191
+ * @default false
192
+ */
193
+ readonly ssrValue?: boolean;
194
+ /**
195
+ * Reactive media-query capability for alternate runtimes and tests.
196
+ *
197
+ * @default globalThis.window when available
198
+ */
199
+ readonly host?: MaybeRefOrGetter<MediaQueryHost | null | undefined>;
200
+ }
201
+ /**
202
+ * Evaluate a reactive media query without requiring browser globals.
203
+ *
204
+ * @param query Reactive media-query source.
205
+ * @param options Runtime capability and server-rendered fallback.
206
+ * @default options {}
207
+ */
208
+ declare function useMediaQuery(query: MaybeRefOrGetter<string>, options?: UseMediaQueryOptions): Readonly<Ref<boolean>>;
209
+ /** User motion preference exposed by {@link useReducedMotion}. */
210
+ type MotionPreference = "reduce" | "no-preference";
211
+ /**
212
+ * Return the reactive user motion preference.
213
+ *
214
+ * @param options Runtime capability and server-rendered fallback.
215
+ * @default options {}
216
+ */
217
+ declare function useReducedMotion(options?: UseMediaQueryOptions): ComputedRef<MotionPreference>;
218
+ //#endregion
219
+ //#region src/scope.d.ts
220
+ /**
221
+ * Register cleanup in the active reactive scope when one exists.
222
+ *
223
+ * @param cleanup Cleanup invoked exactly once when the scope is disposed.
224
+ * @returns Whether the cleanup was registered.
225
+ */
226
+ declare function tryOnScopeDispose(cleanup: () => void): boolean;
227
+ //#endregion
228
+ export { AsyncResource, AsyncResourceContext, AsyncResourceExecution, AsyncResourceStatus, EventListenerControls, LocaleControls, MediaQueryHost, MotionPreference, TextDirection, UseAsyncResourceOptions, UseEventListenerOptions, UseLocaleOptions, UseMediaQueryOptions, tryOnScopeDispose, useAsyncResource, useEventListener, useLocale, useMediaQuery, useReducedMotion };
package/dist/index.mjs ADDED
@@ -0,0 +1,244 @@
1
+ import { computed, getCurrentScope, onScopeDispose, readonly, ref, shallowRef, toValue, watch, watchEffect } from "vue";
2
+ //#region src/scope.ts
3
+ /**
4
+ * Register cleanup in the active reactive scope when one exists.
5
+ *
6
+ * @param cleanup Cleanup invoked exactly once when the scope is disposed.
7
+ * @returns Whether the cleanup was registered.
8
+ */
9
+ function tryOnScopeDispose(cleanup) {
10
+ if (!getCurrentScope()) return false;
11
+ onScopeDispose(cleanup);
12
+ return true;
13
+ }
14
+ //#endregion
15
+ //#region src/async-resource.ts
16
+ /**
17
+ * Create a scoped, abortable asynchronous resource with latest-result-wins
18
+ * state. Every execution returns a discriminated result, so cancellation,
19
+ * supersession, loader failure, and successful `undefined` data stay distinct.
20
+ */
21
+ function useAsyncResource(loader, options = {}) {
22
+ const data = shallowRef(options.initialData);
23
+ const error = shallowRef(void 0);
24
+ const status = shallowRef("idle");
25
+ const pending = computed(() => status.value === "pending");
26
+ let generation = 0;
27
+ let active;
28
+ const cancel = (reason = createAbortReason("The execution was cancelled.")) => {
29
+ if (active === void 0) return false;
30
+ generation += 1;
31
+ active.controller.abort(reason);
32
+ active = void 0;
33
+ status.value = "cancelled";
34
+ return true;
35
+ };
36
+ const execute = async (...arguments_) => {
37
+ if ((options.cancelPrevious ?? true) && active !== void 0) {
38
+ active.superseded = true;
39
+ active.controller.abort(createAbortReason("A newer execution started."));
40
+ }
41
+ const record = {
42
+ generation: ++generation,
43
+ controller: new AbortController(),
44
+ superseded: false
45
+ };
46
+ active = record;
47
+ error.value = void 0;
48
+ status.value = "pending";
49
+ if (!(options.keepData ?? true)) data.value = void 0;
50
+ try {
51
+ const result = await loader({ signal: record.controller.signal }, ...arguments_);
52
+ if (record.generation !== generation) return executionAfterInvalidation(record);
53
+ data.value = result;
54
+ status.value = "success";
55
+ return {
56
+ status: "success",
57
+ data: result
58
+ };
59
+ } catch (cause) {
60
+ if (record.generation !== generation || record.controller.signal.aborted) return executionAfterInvalidation(record);
61
+ error.value = cause;
62
+ status.value = "error";
63
+ return {
64
+ status: "error",
65
+ error: cause
66
+ };
67
+ } finally {
68
+ if (active === record) active = void 0;
69
+ }
70
+ };
71
+ const reset = () => {
72
+ cancel(createAbortReason("The resource was reset."));
73
+ data.value = options.initialData;
74
+ error.value = void 0;
75
+ status.value = "idle";
76
+ };
77
+ if (options.scope ?? true) tryOnScopeDispose(() => cancel(createAbortReason("The reactive scope was disposed.")));
78
+ return {
79
+ data,
80
+ error,
81
+ status,
82
+ pending,
83
+ execute,
84
+ cancel,
85
+ reset
86
+ };
87
+ }
88
+ function executionAfterInvalidation(execution) {
89
+ if (execution.superseded || !execution.controller.signal.aborted) return { status: "superseded" };
90
+ return {
91
+ status: "cancelled",
92
+ reason: execution.controller.signal.reason
93
+ };
94
+ }
95
+ function createAbortReason(message) {
96
+ return new DOMException(message, "AbortError");
97
+ }
98
+ //#endregion
99
+ //#region src/event-listener.ts
100
+ function useEventListener(target, event, listener, options = {}) {
101
+ const { capture = false, once = false, passive = false, signal, immediate = true, flush = "pre" } = options;
102
+ const isListening = ref(false);
103
+ const eventOptions = {
104
+ capture,
105
+ passive,
106
+ ...signal ? { signal } : {}
107
+ };
108
+ let stopWatch;
109
+ const stop = () => {
110
+ stopWatch?.stop();
111
+ stopWatch = void 0;
112
+ isListening.value = false;
113
+ };
114
+ const start = () => {
115
+ if (stopWatch || signal?.aborted) return false;
116
+ stopWatch = watch(() => toValue(target), (next, _previous, onCleanup) => {
117
+ isListening.value = false;
118
+ if (!next || signal?.aborted) return;
119
+ const invoke = (nativeEvent) => {
120
+ if (once) stop();
121
+ listener(nativeEvent);
122
+ };
123
+ const onAbort = () => stop();
124
+ next.addEventListener(event, invoke, eventOptions);
125
+ signal?.addEventListener("abort", onAbort, { once: true });
126
+ isListening.value = true;
127
+ onCleanup(() => {
128
+ next.removeEventListener(event, invoke, capture);
129
+ signal?.removeEventListener("abort", onAbort);
130
+ isListening.value = false;
131
+ });
132
+ }, {
133
+ flush,
134
+ immediate: true
135
+ });
136
+ return true;
137
+ };
138
+ tryOnScopeDispose(stop);
139
+ if (immediate) start();
140
+ return {
141
+ isListening: readonly(isListening),
142
+ start,
143
+ stop
144
+ };
145
+ }
146
+ //#endregion
147
+ //#region src/locale.ts
148
+ const FORMATTER_CACHE_LIMIT = 32;
149
+ /**
150
+ * Create reactive locale metadata and platform-native formatter factories.
151
+ *
152
+ * Equivalent formatter options reuse instances. The bounded cache follows the
153
+ * active locale automatically and prevents repeated constructor overhead in
154
+ * reactive render paths.
155
+ */
156
+ function useLocale(source, options = {}) {
157
+ const locale = computed(() => {
158
+ const candidate = (source === void 0 ? void 0 : toValue(source)) ?? (options.detect ?? detectBrowserLocale)() ?? options.fallback ?? "en";
159
+ return candidate instanceof Intl.Locale ? candidate.toString() : new Intl.Locale(candidate).toString();
160
+ });
161
+ const details = computed(() => new Intl.Locale(locale.value));
162
+ const direction = computed(() => details.value.getTextInfo().direction);
163
+ const number = createFormatterCache((activeLocale, formatOptions) => new Intl.NumberFormat(activeLocale, formatOptions));
164
+ const dateTime = createFormatterCache((activeLocale, formatOptions) => new Intl.DateTimeFormat(activeLocale, formatOptions));
165
+ const list = createFormatterCache((activeLocale, formatOptions) => new Intl.ListFormat(activeLocale, formatOptions));
166
+ const relativeTime = createFormatterCache((activeLocale, formatOptions) => new Intl.RelativeTimeFormat(activeLocale, formatOptions));
167
+ return {
168
+ locale,
169
+ details,
170
+ direction,
171
+ number: (formatOptions) => number(locale.value, formatOptions),
172
+ dateTime: (formatOptions) => dateTime(locale.value, formatOptions),
173
+ list: (formatOptions) => list(locale.value, formatOptions),
174
+ relativeTime: (formatOptions) => relativeTime(locale.value, formatOptions)
175
+ };
176
+ }
177
+ function detectBrowserLocale() {
178
+ return typeof navigator === "undefined" ? void 0 : navigator.language;
179
+ }
180
+ function createFormatterCache(create) {
181
+ const cache = /* @__PURE__ */ new Map();
182
+ return (locale, options) => {
183
+ const key = `${locale}\u0000${serializeOptions(options)}`;
184
+ const cached = cache.get(key);
185
+ if (cached !== void 0) {
186
+ cache.delete(key);
187
+ cache.set(key, cached);
188
+ return cached;
189
+ }
190
+ const formatter = create(locale, options);
191
+ if (cache.size >= FORMATTER_CACHE_LIMIT) {
192
+ const oldest = cache.keys().next().value;
193
+ if (oldest !== void 0) cache.delete(oldest);
194
+ }
195
+ cache.set(key, formatter);
196
+ return formatter;
197
+ };
198
+ }
199
+ function serializeOptions(options) {
200
+ if (options === void 0) return "";
201
+ return JSON.stringify(Object.entries(options).sort(([left], [right]) => left.localeCompare(right)));
202
+ }
203
+ //#endregion
204
+ //#region src/media-query.ts
205
+ /**
206
+ * Evaluate a reactive media query without requiring browser globals.
207
+ *
208
+ * @param query Reactive media-query source.
209
+ * @param options Runtime capability and server-rendered fallback.
210
+ * @default options {}
211
+ */
212
+ function useMediaQuery(query, options = {}) {
213
+ const matches = ref(options.ssrValue ?? false);
214
+ watchEffect((onCleanup) => {
215
+ const host = options.host === void 0 ? browserMediaQueryHost() : toValue(options.host);
216
+ if (!host) {
217
+ matches.value = options.ssrValue ?? false;
218
+ return;
219
+ }
220
+ const media = host.matchMedia(toValue(query));
221
+ const update = () => {
222
+ matches.value = media.matches;
223
+ };
224
+ update();
225
+ media.addEventListener("change", update);
226
+ onCleanup(() => media.removeEventListener("change", update));
227
+ });
228
+ return readonly(matches);
229
+ }
230
+ /**
231
+ * Return the reactive user motion preference.
232
+ *
233
+ * @param options Runtime capability and server-rendered fallback.
234
+ * @default options {}
235
+ */
236
+ function useReducedMotion(options = {}) {
237
+ const reduced = useMediaQuery("(prefers-reduced-motion: reduce)", options);
238
+ return computed(() => reduced.value ? "reduce" : "no-preference");
239
+ }
240
+ function browserMediaQueryHost() {
241
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window : void 0;
242
+ }
243
+ //#endregion
244
+ export { tryOnScopeDispose, useAsyncResource, useEventListener, useLocale, useMediaQuery, useReducedMotion };
@@ -0,0 +1,90 @@
1
+ import { ComputedRef, MaybeRefOrGetter, Ref } from "vue";
2
+ import { Intl as TemporalIntl, Temporal } from "temporal-polyfill-lite";
3
+
4
+ //#region src/temporal.d.ts
5
+ /**
6
+ * Timer host used by {@link useTemporalNow}.
7
+ *
8
+ * Implement this interface to integrate a deterministic clock, a native
9
+ * runtime timer, or an application-owned scheduler.
10
+ */
11
+ interface TemporalScheduler {
12
+ /** Starts a repeating callback and returns its opaque cancellation handle. */
13
+ readonly setInterval: (callback: () => void, intervalMs: number) => unknown;
14
+ /** Cancels a handle previously returned by {@link TemporalScheduler.setInterval}. */
15
+ readonly clearInterval: (handle: unknown) => void;
16
+ }
17
+ /** Options for {@link useTemporalNow}. */
18
+ interface UseTemporalNowOptions {
19
+ /**
20
+ * Clock update interval in milliseconds.
21
+ *
22
+ * Reactive changes replace the active timer. Values must be finite and
23
+ * greater than zero.
24
+ *
25
+ * @default 1000
26
+ */
27
+ readonly intervalMs?: MaybeRefOrGetter<number>;
28
+ /**
29
+ * Pauses periodic updates while preserving the current instant.
30
+ * Manual calls to {@link TemporalClock.refresh} continue to work.
31
+ *
32
+ * @default false
33
+ */
34
+ readonly paused?: MaybeRefOrGetter<boolean>;
35
+ /**
36
+ * Starts the timer when no browser `window` is available.
37
+ *
38
+ * Keep this disabled during server rendering. Enable it for native,
39
+ * desktop, worker, and test runtimes whose scheduler is lifecycle-bound.
40
+ *
41
+ * @default false
42
+ */
43
+ readonly runOnServer?: MaybeRefOrGetter<boolean>;
44
+ /**
45
+ * Produces the current instant.
46
+ *
47
+ * @default Temporal.Now.instant
48
+ */
49
+ readonly now?: () => Temporal.Instant;
50
+ /**
51
+ * Owns the repeating timer.
52
+ *
53
+ * @default globalThis timer functions
54
+ */
55
+ readonly scheduler?: TemporalScheduler;
56
+ }
57
+ /** Reactive controls returned by {@link useTemporalNow}. */
58
+ interface TemporalClock {
59
+ /** The latest instant. This ref is readonly to consumers. */
60
+ readonly instant: Readonly<Ref<Temporal.Instant>>;
61
+ /** Reads the clock source immediately, stores the value, and returns it. */
62
+ readonly refresh: () => Temporal.Instant;
63
+ }
64
+ /** Options for {@link useTemporalZonedDateTime}. */
65
+ interface UseTemporalZonedDateTimeOptions extends UseTemporalNowOptions {
66
+ /**
67
+ * Time-zone identifier or zoned date-time accepted by Temporal.
68
+ *
69
+ * @default Temporal.Now.timeZoneId()
70
+ */
71
+ readonly timeZone?: MaybeRefOrGetter<Temporal.TimeZoneLike>;
72
+ }
73
+ /**
74
+ * Creates a pauseable Temporal clock whose timer follows the current Vue
75
+ * effect scope.
76
+ *
77
+ * The timer is replaced when reactive options change and is always cancelled
78
+ * when the owning scope stops. During server rendering, no timer starts unless
79
+ * {@link UseTemporalNowOptions.runOnServer} is explicitly enabled.
80
+ */
81
+ declare function useTemporalNow(options?: UseTemporalNowOptions): TemporalClock;
82
+ /**
83
+ * Creates a reactive zoned date-time derived from a scoped Temporal clock.
84
+ *
85
+ * Changes to {@link UseTemporalZonedDateTimeOptions.timeZone} are reflected
86
+ * without replacing the underlying timer.
87
+ */
88
+ declare function useTemporalZonedDateTime(options?: UseTemporalZonedDateTimeOptions): ComputedRef<Temporal.ZonedDateTime>;
89
+ //#endregion
90
+ export { Temporal, TemporalClock, TemporalIntl, TemporalScheduler, UseTemporalNowOptions, UseTemporalZonedDateTimeOptions, useTemporalNow, useTemporalZonedDateTime };
@@ -0,0 +1,61 @@
1
+ import { computed, readonly, shallowRef, toValue, watchEffect } from "vue";
2
+ import { Intl as TemporalIntl, Temporal } from "temporal-polyfill-lite";
3
+ //#region src/temporal.ts
4
+ const defaultScheduler = {
5
+ setInterval: (callback, intervalMs) => globalThis.setInterval(callback, intervalMs),
6
+ clearInterval: (handle) => {
7
+ globalThis.clearInterval(handle);
8
+ }
9
+ };
10
+ function resolveIntervalMs(value) {
11
+ if (!Number.isFinite(value) || value <= 0) throw new RangeError(`[VIZE_COMPOSE_TEMPORAL_INVALID_INTERVAL] intervalMs must be finite and greater than zero; received ${String(value)}`);
12
+ return Math.max(1, Math.trunc(value));
13
+ }
14
+ function shouldSchedule(options) {
15
+ if (toValue(options.paused ?? false)) return false;
16
+ return typeof window !== "undefined" || toValue(options.runOnServer ?? false);
17
+ }
18
+ /**
19
+ * Creates a pauseable Temporal clock whose timer follows the current Vue
20
+ * effect scope.
21
+ *
22
+ * The timer is replaced when reactive options change and is always cancelled
23
+ * when the owning scope stops. During server rendering, no timer starts unless
24
+ * {@link UseTemporalNowOptions.runOnServer} is explicitly enabled.
25
+ */
26
+ function useTemporalNow(options = {}) {
27
+ const readNow = options.now ?? Temporal.Now.instant;
28
+ const instant = shallowRef(readNow());
29
+ if (shouldSchedule(options)) resolveIntervalMs(toValue(options.intervalMs ?? 1e3));
30
+ const refresh = () => {
31
+ const nextInstant = readNow();
32
+ instant.value = nextInstant;
33
+ return nextInstant;
34
+ };
35
+ watchEffect((onCleanup) => {
36
+ if (!shouldSchedule(options)) return;
37
+ const intervalMs = resolveIntervalMs(toValue(options.intervalMs ?? 1e3));
38
+ const scheduler = options.scheduler ?? defaultScheduler;
39
+ const handle = scheduler.setInterval(refresh, intervalMs);
40
+ onCleanup(() => scheduler.clearInterval(handle));
41
+ });
42
+ return {
43
+ instant: readonly(instant),
44
+ refresh
45
+ };
46
+ }
47
+ /**
48
+ * Creates a reactive zoned date-time derived from a scoped Temporal clock.
49
+ *
50
+ * Changes to {@link UseTemporalZonedDateTimeOptions.timeZone} are reflected
51
+ * without replacing the underlying timer.
52
+ */
53
+ function useTemporalZonedDateTime(options = {}) {
54
+ const clock = useTemporalNow(options);
55
+ return computed(() => {
56
+ const timeZone = options.timeZone === void 0 ? Temporal.Now.timeZoneId() : toValue(options.timeZone);
57
+ return clock.instant.value.toZonedDateTimeISO(timeZone);
58
+ });
59
+ }
60
+ //#endregion
61
+ export { Temporal, TemporalIntl, useTemporalNow, useTemporalZonedDateTime };
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@vizejs/composable",
3
+ "version": "0.302.0",
4
+ "description": "Lifecycle-safe composable foundations for Vize applications",
5
+ "keywords": [
6
+ "composables",
7
+ "lifecycle",
8
+ "type-safe",
9
+ "vize",
10
+ "vue"
11
+ ],
12
+ "homepage": "https://github.com/ubugeeei-prod/vize",
13
+ "bugs": {
14
+ "url": "https://github.com/ubugeeei-prod/vize/issues"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/ubugeeei-prod/vize.git",
20
+ "directory": "npm/compose/core"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "main": "./dist/index.mjs",
28
+ "types": "./dist/index.d.mts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.mts",
32
+ "import": "./dist/index.mjs",
33
+ "default": "./dist/index.mjs"
34
+ },
35
+ "./temporal": {
36
+ "types": "./dist/temporal.d.mts",
37
+ "import": "./dist/temporal.mjs",
38
+ "default": "./dist/temporal.mjs"
39
+ }
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "vp pack",
46
+ "dev": "vp pack --watch",
47
+ "pretest": "vp pack && pnpm check:size",
48
+ "test": "vp exec node --test 'src/**/*.test.ts'",
49
+ "check": "vp check src scripts vite.config.ts",
50
+ "check:fix": "vp check --fix src scripts vite.config.ts",
51
+ "check:size": "node scripts/check-size.mjs",
52
+ "fmt": "vp fmt --write src scripts vite.config.ts"
53
+ },
54
+ "dependencies": {
55
+ "temporal-polyfill-lite": "0.4.2"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "catalog:typescript",
59
+ "typescript": "catalog:typescript",
60
+ "vite-plus": "catalog:vite-stack",
61
+ "vue": "catalog:vue-stable"
62
+ },
63
+ "peerDependencies": {
64
+ "vue": "^3.5.0"
65
+ },
66
+ "engines": {
67
+ "node": ">=24"
68
+ }
69
+ }