ng-primitives 0.122.0 → 0.123.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.
- package/fesm2022/ng-primitives-combobox.mjs +10 -1
- package/fesm2022/ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/ng-primitives-date-picker.mjs +1 -1
- package/fesm2022/ng-primitives-date-picker.mjs.map +1 -1
- package/fesm2022/ng-primitives-internal.mjs +184 -3
- package/fesm2022/ng-primitives-internal.mjs.map +1 -1
- package/fesm2022/ng-primitives-menu.mjs +131 -19
- package/fesm2022/ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/ng-primitives-portal.mjs +74 -12
- package/fesm2022/ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/ng-primitives-select.mjs +10 -1
- package/fesm2022/ng-primitives-select.mjs.map +1 -1
- package/fesm2022/ng-primitives-tooltip.mjs +19 -118
- package/fesm2022/ng-primitives-tooltip.mjs.map +1 -1
- package/fesm2022/ng-primitives-utils.mjs +40 -23
- package/fesm2022/ng-primitives-utils.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/ng-generate/templates/toggle-group/toggle-group.__fileSuffix@dasherize__.ts.template +2 -2
- package/types/ng-primitives-internal.d.ts +95 -3
- package/types/ng-primitives-menu.d.ts +42 -10
- package/types/ng-primitives-portal.d.ts +31 -1
- package/types/ng-primitives-tooltip.d.ts +6 -9
- package/types/ng-primitives-utils.d.ts +12 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ng-primitives-utils.mjs","sources":["../../../../packages/ng-primitives/utils/src/forms/providers.ts","../../../../packages/ng-primitives/utils/src/observables/take-until-destroyed.ts","../../../../packages/ng-primitives/utils/src/forms/status.ts","../../../../packages/ng-primitives/utils/src/helpers/attributes.ts","../../../../packages/ng-primitives/utils/src/helpers/disposables.ts","../../../../packages/ng-primitives/utils/src/helpers/unique-id.ts","../../../../packages/ng-primitives/utils/src/helpers/validators.ts","../../../../packages/ng-primitives/utils/src/signals/index.ts","../../../../packages/ng-primitives/utils/src/ng-primitives-utils.ts"],"sourcesContent":["import { ExistingProvider, Type } from '@angular/core';\nimport { NG_VALUE_ACCESSOR } from '@angular/forms';\n\n/**\n * A simple helper function to provide a value accessor for a given type.\n * @param type The type to provide the value accessor for\n */\nexport function provideValueAccessor<T>(type: Type<T>): ExistingProvider {\n return { provide: NG_VALUE_ACCESSOR, useExisting: type, multi: true };\n}\n","/* eslint-disable @nx/workspace-take-until-destroyed */\nimport { DestroyRef } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { EMPTY, MonoTypeOperatorFunction, NEVER, pipe } from 'rxjs';\nimport { catchError, defaultIfEmpty, takeUntil } from 'rxjs/operators';\n\n/**\n * The built-in `takeUntilDestroyed` operator does not handle the case when the component is destroyed before the source observable emits.\n * This operator ensures that the source observable completes gracefully without throwing an error.\n * https://github.com/angular/angular/issues/54527#issuecomment-2098254508\n *\n * @internal\n */\nexport function safeTakeUntilDestroyed<T>(destroyRef?: DestroyRef): MonoTypeOperatorFunction<T> {\n return pipe(\n takeUntil(\n NEVER.pipe(\n takeUntilDestroyed(destroyRef),\n catchError(() => EMPTY),\n defaultIfEmpty(null),\n ),\n ),\n );\n}\n","import {\n DestroyRef,\n Injector,\n Signal,\n WritableSignal,\n effect,\n inject,\n signal,\n untracked,\n} from '@angular/core';\nimport { NgControl } from '@angular/forms';\nimport { onMount } from 'ng-primitives/state';\nimport { safeTakeUntilDestroyed } from '../observables/take-until-destroyed';\n\nexport interface NgpControlStatus {\n valid: boolean | null;\n invalid: boolean | null;\n pristine: boolean | null;\n dirty: boolean | null;\n touched: boolean | null;\n pending: boolean | null;\n disabled: boolean | null;\n}\n\n/**\n * Detects an Angular signal-forms interop control without importing any of the new\n * signal-form types. Interop controls expose a `field()` method which returns the\n * underlying FieldState.\n */\nfunction isInteropControl(control: NgControl | null | undefined): boolean {\n return !!control && typeof (control as any).field === 'function';\n}\n\n/**\n * Reads status from a control and updates the status signal.\n * Wrapped in try-catch to handle signal-forms interop controls where\n * the `field` input may not be available yet (throws NG0950).\n */\nfunction updateStatus(control: NgControl, status: WritableSignal<NgpControlStatus>): void {\n try {\n // For interop controls, read directly from the control (which has signal getters).\n // For classic controls, read from the underlying AbstractControl.\n const source = isInteropControl(control) ? control : ((control as any).control ?? control);\n\n const newStatus: NgpControlStatus = {\n valid: source.valid ?? null,\n invalid: source.invalid ?? null,\n pristine: source.pristine ?? null,\n dirty: source.dirty ?? null,\n touched: source.touched ?? null,\n pending: source.pending ?? null,\n disabled: source.disabled ?? null,\n };\n\n untracked(() => status.set(newStatus));\n } catch {\n // NG0950: Required input not available yet. The effect will re-run\n // when the signal input becomes available.\n }\n}\n\n/**\n * A utility function to get the status of an Angular form control as a reactive signal.\n * This function injects the NgControl and returns a signal that reflects the control's status.\n * It supports both classic reactive forms controls and signal-forms interop controls.\n * @internal\n */\n/**\n * Sets up event subscription for a given NgControl.\n * Only sets up the subscription - does not call updateStatus.\n */\nfunction setupEventSubscription(\n ngControl: NgControl,\n status: WritableSignal<NgpControlStatus>,\n destroyRef: DestroyRef,\n): void {\n // For classic controls, also subscribe to the events observable.\n const underlyingControl = (ngControl as any).control;\n if (underlyingControl?.events) {\n underlyingControl.events\n .pipe(safeTakeUntilDestroyed(destroyRef))\n .subscribe(() => updateStatus(ngControl, status));\n }\n}\n\nexport function controlStatus(): Signal<NgpControlStatus> {\n const injector = inject(Injector);\n const destroyRef = inject(DestroyRef);\n\n const status = signal<NgpControlStatus>({\n valid: null,\n invalid: null,\n pristine: null,\n dirty: null,\n touched: null,\n pending: null,\n disabled: null,\n });\n\n const control = signal<NgControl | null>(null);\n\n onMount(() => {\n // Try to inject NgControl immediately for initial state\n control.set(inject(NgControl, { optional: true }));\n\n // If we have a control immediately, update initial status\n if (control()) {\n updateStatus(control()!, status);\n }\n\n // Get the control (either from initial injection or from mount)\n const mountControl = control() || inject(NgControl, { optional: true });\n\n if (!mountControl) {\n return;\n }\n\n // Update control signal if it wasn't set before\n if (!control()) {\n control.set(mountControl);\n }\n\n // Update status to ensure latest values\n updateStatus(mountControl, status);\n\n // Set up event subscription for reactive updates\n setupEventSubscription(mountControl, status, destroyRef);\n });\n\n // Use an effect to reactively track status changes.\n // For signal-forms interop controls, the status properties are signals.\n // For classic controls, this will read the current values and establish\n // no signal dependencies, but we also subscribe to events below.\n effect(\n () => {\n const c = control();\n if (c) {\n updateStatus(c, status);\n }\n },\n { injector },\n );\n\n return status;\n}\n","import { afterRenderEffect, Signal } from '@angular/core';\n\nexport function booleanAttributeBinding(\n element: HTMLElement,\n attribute: string,\n value: Signal<boolean> | undefined,\n): void {\n // eslint-disable-next-line @angular-eslint/no-uncalled-signals -- checking whether the optional signal was provided, not its value\n if (value === undefined) {\n return;\n }\n\n afterRenderEffect({\n write: () =>\n value() ? element.setAttribute(attribute, '') : element.removeAttribute(attribute),\n });\n}\n","import { DestroyRef, inject } from '@angular/core';\n\n/**\n * Disposable functions are a way to manage timers, intervals, and event listeners\n * that should be cleared when a component is destroyed.\n *\n * This is heavily inspired by Headless UI disposables:\n * https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/utils/disposables.ts\n */\nexport function injectDisposables() {\n const destroyRef = inject(DestroyRef);\n let isDestroyed = false;\n\n destroyRef.onDestroy(() => (isDestroyed = true));\n\n return {\n /**\n * Set a timeout that will be cleared when the component is destroyed.\n * @param callback The callback to execute\n * @param delay The delay before the callback is executed\n * @returns A function to clear the timeout\n */\n setTimeout: (callback: () => void, delay: number) => {\n if (isDestroyed) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const id = setTimeout(callback, delay);\n const cleanup = () => clearTimeout(id);\n destroyRef.onDestroy(cleanup);\n return cleanup;\n },\n /**\n * Set an interval that will be cleared when the component is destroyed.\n * @param callback The callback to execute\n * @param delay The delay before the callback is executed\n * @param target\n * @param type\n * @param listener\n * @param options\n * @returns A function to clear the interval\n */\n addEventListener: <K extends keyof HTMLElementEventMap>(\n target: EventTarget,\n type: K,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions,\n ) => {\n if (isDestroyed) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n target.addEventListener(type, listener as EventListenerOrEventListenerObject, options);\n const cleanup = () =>\n target.removeEventListener(type, listener as EventListenerOrEventListenerObject, options);\n destroyRef.onDestroy(cleanup);\n return cleanup;\n },\n /**\n * Set an interval that will be cleared when the component is destroyed.\n * @param callback The callback to execute\n * @param delay The delay before the callback is executed\n * @returns A function to clear the interval\n */\n setInterval: (callback: () => void, delay: number) => {\n if (isDestroyed) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const id = setInterval(callback, delay);\n const cleanup = () => clearInterval(id);\n destroyRef.onDestroy(cleanup);\n return cleanup;\n },\n /**\n * Set a requestAnimationFrame that will be cleared when the component is destroyed.\n * @param callback The callback to execute\n * @returns A function to clear the requestAnimationFrame\n */\n requestAnimationFrame: (callback: FrameRequestCallback) => {\n if (isDestroyed) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const id = requestAnimationFrame(callback);\n const cleanup = () => cancelAnimationFrame(id);\n destroyRef.onDestroy(cleanup);\n return cleanup;\n },\n };\n}\n","/**\n * Store a map of unique ids for elements so that there are no collisions.\n */\nconst uniqueIdMap = new Map<string, number>();\n\n/**\n * Generate a unique id for an element\n * @param prefix - The prefix to use for the id\n * @returns The generated id\n */\nexport function uniqueId(prefix: string): string {\n const id = uniqueIdMap.get(prefix) ?? 0;\n uniqueIdMap.set(prefix, id + 1);\n return `${prefix}-${id}`;\n}\n","/**\n * Type validation utilities\n */\n\n/**\n * Checks if a value is a string\n * @param value - The value to check\n * @returns true if the value is a string, false otherwise\n */\nexport function isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\n/**\n * Checks if a value is a number\n * @param value - The value to check\n * @returns true if the value is a number, false otherwise\n */\nexport function isNumber(value: unknown): value is number {\n return typeof value === 'number';\n}\n\n/**\n * Checks if a value is a boolean\n * @param value - The value to check\n * @returns true if the value is a boolean, false otherwise\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === 'boolean';\n}\n\n/**\n * Checks if a value is a function\n * @param value - The value to check\n * @returns true if the value is a function, false otherwise\n */\nexport function isFunction(value: unknown): value is CallableFunction {\n return typeof value === 'function';\n}\n\n/**\n * Checks if a value is a plain object (but not null or array)\n * @param value - The value to check\n * @returns true if the value is a plain object, false otherwise\n */\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Checks if a value is undefined\n * @param value - The value to check\n * @returns true if the value is undefined, false otherwise\n */\nexport function isUndefined(value: unknown): value is undefined {\n return typeof value === 'undefined';\n}\n\n/**\n * Checks if a value is null or undefined\n * @param value - The value to check\n * @returns true if the value is null or undefined, false otherwise\n */\nexport function isNil(value: unknown): value is null | undefined {\n return isUndefined(value) || value === null;\n}\n\n/**\n * Checks if a value is not null and not undefined\n * @param value - The value to check\n * @returns true if the value is not null and not undefined, false otherwise\n */\nexport function notNil<T>(value: T | null | undefined): value is T {\n return !isNil(value);\n}\n","import { effect, Injector, Signal, signal, untracked } from '@angular/core';\n\n/**\n * Listen for changes to a signal and call a function when the signal changes.\n * @param source\n * @param fn\n * @param options\n * @param options.injector\n * @internal\n */\nexport function onChange<T>(\n source: Signal<T>,\n fn: (value: T, previousValue: T | null | undefined) => void,\n options?: { injector: Injector },\n): void {\n const previousValue = signal(source());\n\n effect(\n () => {\n const value = source();\n if (value !== previousValue()) {\n untracked(() => fn(value, previousValue()));\n previousValue.set(value);\n }\n },\n { injector: options?.injector },\n );\n\n // call the fn with the initial value\n fn(source(), null);\n}\n\n/**\n * Listen for changes to a boolean signal and call one of two functions when the signal changes.\n * @param source\n * @param onTrue\n * @param onFalse\n * @param options\n */\nexport function onBooleanChange(\n source: Signal<boolean>,\n onTrue?: () => void,\n onFalse?: () => void,\n options?: { injector: Injector },\n): void {\n onChange(source, value => (value ? onTrue?.() : onFalse?.()), options);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ng-primitives-utils.mjs","sources":["../../../../packages/ng-primitives/utils/src/forms/providers.ts","../../../../packages/ng-primitives/utils/src/observables/take-until-destroyed.ts","../../../../packages/ng-primitives/utils/src/forms/status.ts","../../../../packages/ng-primitives/utils/src/helpers/attributes.ts","../../../../packages/ng-primitives/utils/src/helpers/disposables.ts","../../../../packages/ng-primitives/utils/src/helpers/unique-id.ts","../../../../packages/ng-primitives/utils/src/helpers/validators.ts","../../../../packages/ng-primitives/utils/src/signals/index.ts","../../../../packages/ng-primitives/utils/src/ng-primitives-utils.ts"],"sourcesContent":["import { ExistingProvider, Type } from '@angular/core';\nimport { NG_VALUE_ACCESSOR } from '@angular/forms';\n\n/**\n * A simple helper function to provide a value accessor for a given type.\n * @param type The type to provide the value accessor for\n */\nexport function provideValueAccessor<T>(type: Type<T>): ExistingProvider {\n return { provide: NG_VALUE_ACCESSOR, useExisting: type, multi: true };\n}\n","/* eslint-disable @nx/workspace-take-until-destroyed */\nimport { DestroyRef } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { EMPTY, MonoTypeOperatorFunction, NEVER, pipe } from 'rxjs';\nimport { catchError, defaultIfEmpty, takeUntil } from 'rxjs/operators';\n\n/**\n * The built-in `takeUntilDestroyed` operator does not handle the case when the component is destroyed before the source observable emits.\n * This operator ensures that the source observable completes gracefully without throwing an error.\n * https://github.com/angular/angular/issues/54527#issuecomment-2098254508\n *\n * @internal\n */\nexport function safeTakeUntilDestroyed<T>(destroyRef?: DestroyRef): MonoTypeOperatorFunction<T> {\n return pipe(\n takeUntil(\n NEVER.pipe(\n takeUntilDestroyed(destroyRef),\n catchError(() => EMPTY),\n defaultIfEmpty(null),\n ),\n ),\n );\n}\n","import {\n DestroyRef,\n Injector,\n Signal,\n WritableSignal,\n effect,\n inject,\n signal,\n untracked,\n} from '@angular/core';\nimport { NgControl } from '@angular/forms';\nimport { onMount } from 'ng-primitives/state';\nimport { safeTakeUntilDestroyed } from '../observables/take-until-destroyed';\n\nexport interface NgpControlStatus {\n valid: boolean | null;\n invalid: boolean | null;\n pristine: boolean | null;\n dirty: boolean | null;\n touched: boolean | null;\n pending: boolean | null;\n disabled: boolean | null;\n}\n\n/**\n * Detects an Angular signal-forms interop control without importing any of the new\n * signal-form types. Interop controls expose a `field()` method which returns the\n * underlying FieldState.\n */\nfunction isInteropControl(control: NgControl | null | undefined): boolean {\n return !!control && typeof (control as any).field === 'function';\n}\n\n/**\n * Reads status from a control and updates the status signal.\n * Wrapped in try-catch to handle signal-forms interop controls where\n * the `field` input may not be available yet (throws NG0950).\n */\nfunction updateStatus(control: NgControl, status: WritableSignal<NgpControlStatus>): void {\n try {\n // For interop controls, read directly from the control (which has signal getters).\n // For classic controls, read from the underlying AbstractControl.\n const source = isInteropControl(control) ? control : ((control as any).control ?? control);\n\n const newStatus: NgpControlStatus = {\n valid: source.valid ?? null,\n invalid: source.invalid ?? null,\n pristine: source.pristine ?? null,\n dirty: source.dirty ?? null,\n touched: source.touched ?? null,\n pending: source.pending ?? null,\n disabled: source.disabled ?? null,\n };\n\n untracked(() => status.set(newStatus));\n } catch {\n // NG0950: Required input not available yet. The effect will re-run\n // when the signal input becomes available.\n }\n}\n\n/**\n * A utility function to get the status of an Angular form control as a reactive signal.\n * This function injects the NgControl and returns a signal that reflects the control's status.\n * It supports both classic reactive forms controls and signal-forms interop controls.\n * @internal\n */\n/**\n * Sets up event subscription for a given NgControl.\n * Only sets up the subscription - does not call updateStatus.\n */\nfunction setupEventSubscription(\n ngControl: NgControl,\n status: WritableSignal<NgpControlStatus>,\n destroyRef: DestroyRef,\n): void {\n // For classic controls, also subscribe to the events observable.\n const underlyingControl = (ngControl as any).control;\n if (underlyingControl?.events) {\n underlyingControl.events\n .pipe(safeTakeUntilDestroyed(destroyRef))\n .subscribe(() => updateStatus(ngControl, status));\n }\n}\n\nexport function controlStatus(): Signal<NgpControlStatus> {\n const injector = inject(Injector);\n const destroyRef = inject(DestroyRef);\n\n const status = signal<NgpControlStatus>({\n valid: null,\n invalid: null,\n pristine: null,\n dirty: null,\n touched: null,\n pending: null,\n disabled: null,\n });\n\n const control = signal<NgControl | null>(null);\n\n onMount(() => {\n // Try to inject NgControl immediately for initial state\n control.set(inject(NgControl, { optional: true }));\n\n // If we have a control immediately, update initial status\n if (control()) {\n updateStatus(control()!, status);\n }\n\n // Get the control (either from initial injection or from mount)\n const mountControl = control() || inject(NgControl, { optional: true });\n\n if (!mountControl) {\n return;\n }\n\n // Update control signal if it wasn't set before\n if (!control()) {\n control.set(mountControl);\n }\n\n // Update status to ensure latest values\n updateStatus(mountControl, status);\n\n // Set up event subscription for reactive updates\n setupEventSubscription(mountControl, status, destroyRef);\n });\n\n // Use an effect to reactively track status changes.\n // For signal-forms interop controls, the status properties are signals.\n // For classic controls, this will read the current values and establish\n // no signal dependencies, but we also subscribe to events below.\n effect(\n () => {\n const c = control();\n if (c) {\n updateStatus(c, status);\n }\n },\n { injector },\n );\n\n return status;\n}\n","import { afterRenderEffect, Signal } from '@angular/core';\n\nexport function booleanAttributeBinding(\n element: HTMLElement,\n attribute: string,\n value: Signal<boolean> | undefined,\n): void {\n // eslint-disable-next-line @angular-eslint/no-uncalled-signals -- checking whether the optional signal was provided, not its value\n if (value === undefined) {\n return;\n }\n\n afterRenderEffect({\n write: () =>\n value() ? element.setAttribute(attribute, '') : element.removeAttribute(attribute),\n });\n}\n","import { DestroyRef, inject } from '@angular/core';\n\n/**\n * Disposable functions are a way to manage timers, intervals, and event listeners\n * that should be cleared when a component is destroyed.\n *\n * Each disposable releases its DestroyRef registration as soon as the resource\n * is gone (the timer fires or the returned cleanup runs), so repeated calls -\n * e.g. rescheduling a timeout on every pointermove - don't accumulate destroy\n * callbacks for the lifetime of the host.\n *\n * This is heavily inspired by Headless UI disposables:\n * https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/utils/disposables.ts\n */\nexport function injectDisposables() {\n const destroyRef = inject(DestroyRef);\n let isDestroyed = false;\n\n destroyRef.onDestroy(() => (isDestroyed = true));\n\n return {\n /**\n * Set a timeout that will be cleared when the component is destroyed.\n * @param callback The callback to execute\n * @param delay The delay before the callback is executed\n * @returns A function to clear the timeout\n */\n setTimeout: (callback: () => void, delay: number) => {\n if (isDestroyed) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const id = setTimeout(() => {\n unregister();\n callback();\n }, delay);\n const unregister = destroyRef.onDestroy(() => clearTimeout(id));\n return () => {\n clearTimeout(id);\n unregister();\n };\n },\n /**\n * Add an event listener that will be removed when the component is destroyed.\n * @param target The event target\n * @param type The event type\n * @param listener The event listener\n * @param options The event listener options\n * @returns A function to remove the event listener\n */\n addEventListener: <K extends keyof HTMLElementEventMap>(\n target: EventTarget,\n type: K,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions,\n ) => {\n if (isDestroyed) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n target.addEventListener(type, listener as EventListenerOrEventListenerObject, options);\n const unregister = destroyRef.onDestroy(() =>\n target.removeEventListener(type, listener as EventListenerOrEventListenerObject, options),\n );\n return () => {\n target.removeEventListener(type, listener as EventListenerOrEventListenerObject, options);\n unregister();\n };\n },\n /**\n * Set an interval that will be cleared when the component is destroyed.\n * @param callback The callback to execute\n * @param delay The delay between executions\n * @returns A function to clear the interval\n */\n setInterval: (callback: () => void, delay: number) => {\n if (isDestroyed) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const id = setInterval(callback, delay);\n const unregister = destroyRef.onDestroy(() => clearInterval(id));\n return () => {\n clearInterval(id);\n unregister();\n };\n },\n /**\n * Set a requestAnimationFrame that will be cleared when the component is destroyed.\n * @param callback The callback to execute\n * @returns A function to clear the requestAnimationFrame\n */\n requestAnimationFrame: (callback: FrameRequestCallback) => {\n if (isDestroyed) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const id = requestAnimationFrame(time => {\n unregister();\n callback(time);\n });\n const unregister = destroyRef.onDestroy(() => cancelAnimationFrame(id));\n return () => {\n cancelAnimationFrame(id);\n unregister();\n };\n },\n };\n}\n","/**\n * Store a map of unique ids for elements so that there are no collisions.\n */\nconst uniqueIdMap = new Map<string, number>();\n\n/**\n * Generate a unique id for an element\n * @param prefix - The prefix to use for the id\n * @returns The generated id\n */\nexport function uniqueId(prefix: string): string {\n const id = uniqueIdMap.get(prefix) ?? 0;\n uniqueIdMap.set(prefix, id + 1);\n return `${prefix}-${id}`;\n}\n","/**\n * Type validation utilities\n */\n\n/**\n * Checks if a value is a string\n * @param value - The value to check\n * @returns true if the value is a string, false otherwise\n */\nexport function isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\n/**\n * Checks if a value is a number\n * @param value - The value to check\n * @returns true if the value is a number, false otherwise\n */\nexport function isNumber(value: unknown): value is number {\n return typeof value === 'number';\n}\n\n/**\n * Checks if a value is a boolean\n * @param value - The value to check\n * @returns true if the value is a boolean, false otherwise\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === 'boolean';\n}\n\n/**\n * Checks if a value is a function\n * @param value - The value to check\n * @returns true if the value is a function, false otherwise\n */\nexport function isFunction(value: unknown): value is CallableFunction {\n return typeof value === 'function';\n}\n\n/**\n * Checks if a value is a plain object (but not null or array)\n * @param value - The value to check\n * @returns true if the value is a plain object, false otherwise\n */\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Checks if a value is undefined\n * @param value - The value to check\n * @returns true if the value is undefined, false otherwise\n */\nexport function isUndefined(value: unknown): value is undefined {\n return typeof value === 'undefined';\n}\n\n/**\n * Checks if a value is null or undefined\n * @param value - The value to check\n * @returns true if the value is null or undefined, false otherwise\n */\nexport function isNil(value: unknown): value is null | undefined {\n return isUndefined(value) || value === null;\n}\n\n/**\n * Checks if a value is not null and not undefined\n * @param value - The value to check\n * @returns true if the value is not null and not undefined, false otherwise\n */\nexport function notNil<T>(value: T | null | undefined): value is T {\n return !isNil(value);\n}\n","import { effect, Injector, Signal, signal, untracked } from '@angular/core';\n\n/**\n * Listen for changes to a signal and call a function when the signal changes.\n * @param source\n * @param fn\n * @param options\n * @param options.injector\n * @internal\n */\nexport function onChange<T>(\n source: Signal<T>,\n fn: (value: T, previousValue: T | null | undefined) => void,\n options?: { injector: Injector },\n): void {\n const previousValue = signal(source());\n\n effect(\n () => {\n const value = source();\n if (value !== previousValue()) {\n untracked(() => fn(value, previousValue()));\n previousValue.set(value);\n }\n },\n { injector: options?.injector },\n );\n\n // call the fn with the initial value\n fn(source(), null);\n}\n\n/**\n * Listen for changes to a boolean signal and call one of two functions when the signal changes.\n * @param source\n * @param onTrue\n * @param onFalse\n * @param options\n */\nexport function onBooleanChange(\n source: Signal<boolean>,\n onTrue?: () => void,\n onFalse?: () => void,\n options?: { injector: Injector },\n): void {\n onChange(source, value => (value ? onTrue?.() : onFalse?.()), options);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;AAGA;;;AAGG;AACG,SAAU,oBAAoB,CAAI,IAAa,EAAA;AACnD,IAAA,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;AACvE;;ACHA;;;;;;AAMG;AACG,SAAU,sBAAsB,CAAI,UAAuB,EAAA;AAC/D,IAAA,OAAO,IAAI,CACT,SAAS,CACP,KAAK,CAAC,IAAI,CACR,kBAAkB,CAAC,UAAU,CAAC,EAC9B,UAAU,CAAC,MAAM,KAAK,CAAC,EACvB,cAAc,CAAC,IAAI,CAAC,CACrB,CACF,CACF;AACH;;ACCA;;;;AAIG;AACH,SAAS,gBAAgB,CAAC,OAAqC,EAAA;IAC7D,OAAO,CAAC,CAAC,OAAO,IAAI,OAAQ,OAAe,CAAC,KAAK,KAAK,UAAU;AAClE;AAEA;;;;AAIG;AACH,SAAS,YAAY,CAAC,OAAkB,EAAE,MAAwC,EAAA;AAChF,IAAA,IAAI;;;QAGF,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,OAAO,IAAK,OAAe,CAAC,OAAO,IAAI,OAAO,CAAC;AAE1F,QAAA,MAAM,SAAS,GAAqB;AAClC,YAAA,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;AAC3B,YAAA,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;AAC/B,YAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;AACjC,YAAA,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;AAC3B,YAAA,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;AAC/B,YAAA,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;AAC/B,YAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;SAClC;QAED,SAAS,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACxC;AAAE,IAAA,MAAM;;;IAGR;AACF;AAEA;;;;;AAKG;AACH;;;AAGG;AACH,SAAS,sBAAsB,CAC7B,SAAoB,EACpB,MAAwC,EACxC,UAAsB,EAAA;;AAGtB,IAAA,MAAM,iBAAiB,GAAI,SAAiB,CAAC,OAAO;AACpD,IAAA,IAAI,iBAAiB,EAAE,MAAM,EAAE;AAC7B,QAAA,iBAAiB,CAAC;AACf,aAAA,IAAI,CAAC,sBAAsB,CAAC,UAAU,CAAC;aACvC,SAAS,CAAC,MAAM,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACrD;AACF;SAEgB,aAAa,GAAA;AAC3B,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;AACjC,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IAErC,MAAM,MAAM,GAAG,MAAM,CAAmB;AACtC,QAAA,KAAK,EAAE,IAAI;AACX,QAAA,OAAO,EAAE,IAAI;AACb,QAAA,QAAQ,EAAE,IAAI;AACd,QAAA,KAAK,EAAE,IAAI;AACX,QAAA,OAAO,EAAE,IAAI;AACb,QAAA,OAAO,EAAE,IAAI;AACb,QAAA,QAAQ,EAAE,IAAI;AACf,KAAA,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,QAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;AAEF,IAAA,MAAM,OAAO,GAAG,MAAM,CAAmB,IAAI,8EAAC;IAE9C,OAAO,CAAC,MAAK;;AAEX,QAAA,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;;QAGlD,IAAI,OAAO,EAAE,EAAE;AACb,YAAA,YAAY,CAAC,OAAO,EAAG,EAAE,MAAM,CAAC;QAClC;;AAGA,QAAA,MAAM,YAAY,GAAG,OAAO,EAAE,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAEvE,IAAI,CAAC,YAAY,EAAE;YACjB;QACF;;AAGA,QAAA,IAAI,CAAC,OAAO,EAAE,EAAE;AACd,YAAA,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QAC3B;;AAGA,QAAA,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC;;AAGlC,QAAA,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,CAAC;AAC1D,IAAA,CAAC,CAAC;;;;;IAMF,MAAM,CACJ,MAAK;AACH,QAAA,MAAM,CAAC,GAAG,OAAO,EAAE;QACnB,IAAI,CAAC,EAAE;AACL,YAAA,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC;QACzB;AACF,IAAA,CAAC,EACD,EAAE,QAAQ,EAAE,CACb;AAED,IAAA,OAAO,MAAM;AACf;;SC9IgB,uBAAuB,CACrC,OAAoB,EACpB,SAAiB,EACjB,KAAkC,EAAA;;AAGlC,IAAA,IAAI,KAAK,KAAK,SAAS,EAAE;QACvB;IACF;AAEA,IAAA,iBAAiB,CAAC;QAChB,KAAK,EAAE,MACL,KAAK,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC;AACrF,KAAA,CAAC;AACJ;;ACdA;;;;;;;;;;;AAWG;SACa,iBAAiB,GAAA;AAC/B,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,IAAI,WAAW,GAAG,KAAK;AAEvB,IAAA,UAAU,CAAC,SAAS,CAAC,OAAO,WAAW,GAAG,IAAI,CAAC,CAAC;IAEhD,OAAO;AACL;;;;;AAKG;AACH,QAAA,UAAU,EAAE,CAAC,QAAoB,EAAE,KAAa,KAAI;YAClD,IAAI,WAAW,EAAE;;AAEf,gBAAA,OAAO,MAAK,EAAE,CAAC;YACjB;AAEA,YAAA,MAAM,EAAE,GAAG,UAAU,CAAC,MAAK;AACzB,gBAAA,UAAU,EAAE;AACZ,gBAAA,QAAQ,EAAE;YACZ,CAAC,EAAE,KAAK,CAAC;AACT,YAAA,MAAM,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,YAAY,CAAC,EAAE,CAAC,CAAC;AAC/D,YAAA,OAAO,MAAK;gBACV,YAAY,CAAC,EAAE,CAAC;AAChB,gBAAA,UAAU,EAAE;AACd,YAAA,CAAC;QACH,CAAC;AACD;;;;;;;AAOG;AACH,QAAA,gBAAgB,EAAE,CAChB,MAAmB,EACnB,IAAO;;QAEP,QAAgE,EAChE,OAA2C,KACzC;YACF,IAAI,WAAW,EAAE;;AAEf,gBAAA,OAAO,MAAK,EAAE,CAAC;YACjB;YAEA,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,QAA8C,EAAE,OAAO,CAAC;YACtF,MAAM,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,MACtC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAA8C,EAAE,OAAO,CAAC,CAC1F;AACD,YAAA,OAAO,MAAK;gBACV,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAA8C,EAAE,OAAO,CAAC;AACzF,gBAAA,UAAU,EAAE;AACd,YAAA,CAAC;QACH,CAAC;AACD;;;;;AAKG;AACH,QAAA,WAAW,EAAE,CAAC,QAAoB,EAAE,KAAa,KAAI;YACnD,IAAI,WAAW,EAAE;;AAEf,gBAAA,OAAO,MAAK,EAAE,CAAC;YACjB;YAEA,MAAM,EAAE,GAAG,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC;AACvC,YAAA,MAAM,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC;AAChE,YAAA,OAAO,MAAK;gBACV,aAAa,CAAC,EAAE,CAAC;AACjB,gBAAA,UAAU,EAAE;AACd,YAAA,CAAC;QACH,CAAC;AACD;;;;AAIG;AACH,QAAA,qBAAqB,EAAE,CAAC,QAA8B,KAAI;YACxD,IAAI,WAAW,EAAE;;AAEf,gBAAA,OAAO,MAAK,EAAE,CAAC;YACjB;AAEA,YAAA,MAAM,EAAE,GAAG,qBAAqB,CAAC,IAAI,IAAG;AACtC,gBAAA,UAAU,EAAE;gBACZ,QAAQ,CAAC,IAAI,CAAC;AAChB,YAAA,CAAC,CAAC;AACF,YAAA,MAAM,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,oBAAoB,CAAC,EAAE,CAAC,CAAC;AACvE,YAAA,OAAO,MAAK;gBACV,oBAAoB,CAAC,EAAE,CAAC;AACxB,gBAAA,UAAU,EAAE;AACd,YAAA,CAAC;QACH,CAAC;KACF;AACH;;ACjHA;;AAEG;AACH,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB;AAE7C;;;;AAIG;AACG,SAAU,QAAQ,CAAC,MAAc,EAAA;IACrC,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IACvC,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC;AAC/B,IAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,EAAE,EAAE;AAC1B;;ACdA;;AAEG;AAEH;;;;AAIG;AACG,SAAU,QAAQ,CAAC,KAAc,EAAA;AACrC,IAAA,OAAO,OAAO,KAAK,KAAK,QAAQ;AAClC;AAEA;;;;AAIG;AACG,SAAU,QAAQ,CAAC,KAAc,EAAA;AACrC,IAAA,OAAO,OAAO,KAAK,KAAK,QAAQ;AAClC;AAEA;;;;AAIG;AACG,SAAU,SAAS,CAAC,KAAc,EAAA;AACtC,IAAA,OAAO,OAAO,KAAK,KAAK,SAAS;AACnC;AAEA;;;;AAIG;AACG,SAAU,UAAU,CAAC,KAAc,EAAA;AACvC,IAAA,OAAO,OAAO,KAAK,KAAK,UAAU;AACpC;AAEA;;;;AAIG;AACG,SAAU,QAAQ,CAAC,KAAc,EAAA;AACrC,IAAA,OAAO,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AACtE;AAEA;;;;AAIG;AACG,SAAU,WAAW,CAAC,KAAc,EAAA;AACxC,IAAA,OAAO,OAAO,KAAK,KAAK,WAAW;AACrC;AAEA;;;;AAIG;AACG,SAAU,KAAK,CAAC,KAAc,EAAA;IAClC,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,IAAI;AAC7C;AAEA;;;;AAIG;AACG,SAAU,MAAM,CAAI,KAA2B,EAAA;AACnD,IAAA,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;AACtB;;ACxEA;;;;;;;AAOG;SACa,QAAQ,CACtB,MAAiB,EACjB,EAA2D,EAC3D,OAAgC,EAAA;AAEhC,IAAA,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,oFAAC;IAEtC,MAAM,CACJ,MAAK;AACH,QAAA,MAAM,KAAK,GAAG,MAAM,EAAE;AACtB,QAAA,IAAI,KAAK,KAAK,aAAa,EAAE,EAAE;AAC7B,YAAA,SAAS,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;AAC3C,YAAA,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC;QAC1B;IACF,CAAC,EACD,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAChC;;AAGD,IAAA,EAAE,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC;AACpB;AAEA;;;;;;AAMG;AACG,SAAU,eAAe,CAC7B,MAAuB,EACvB,MAAmB,EACnB,OAAoB,EACpB,OAAgC,EAAA;IAEhC,QAAQ,CAAC,MAAM,EAAE,KAAK,KAAK,KAAK,GAAG,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,EAAE,OAAO,CAAC;AACxE;;AC9CA;;AAEG;;;;"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "ng-primitives",
|
|
3
3
|
"description": "Angular Primitives is a low-level headless UI component library with a focus on accessibility, customization, and developer experience. ",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.123.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"angular",
|
|
8
8
|
"primitives",
|
|
@@ -57,8 +57,8 @@ export class ToggleGroup<%= componentSuffix %> implements ControlValueAccessor {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** Write a new value to the toggle group. */
|
|
60
|
-
writeValue(value: string[]): void {
|
|
61
|
-
this.toggleGroup().setValue(value, { emit: false });
|
|
60
|
+
writeValue(value: string[] | null): void {
|
|
61
|
+
this.toggleGroup().setValue(value ?? [], { emit: false });
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** Register a callback function to be called when the value changes. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { OnDestroy, ClassProvider, EffectCleanupRegisterFn, CreateEffectOptions, EffectRef, ElementRef,
|
|
2
|
+
import { OnDestroy, ClassProvider, Signal, EffectCleanupRegisterFn, CreateEffectOptions, EffectRef, ElementRef, Injector } from '@angular/core';
|
|
3
3
|
import { Observable } from 'rxjs';
|
|
4
4
|
|
|
5
5
|
declare class NgpExitAnimation implements OnDestroy {
|
|
@@ -45,6 +45,98 @@ declare class NgpExitAnimationManager {
|
|
|
45
45
|
declare function provideExitAnimationManager(): ClassProvider;
|
|
46
46
|
declare function injectExitAnimationManager(): NgpExitAnimationManager;
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Fallback timeout (ms) after which an idle hover bridge closes the overlay.
|
|
50
|
+
* Shared so tooltip, menu and submenu hover-intent behaviour stay in sync.
|
|
51
|
+
*/
|
|
52
|
+
declare const HOVER_BRIDGE_TIMEOUT_MS = 150;
|
|
53
|
+
/**
|
|
54
|
+
* Pointer movement below this magnitude (px) on the intent axis is treated as
|
|
55
|
+
* jitter, not a reversal, so a tiny backward twitch doesn't collapse the bridge.
|
|
56
|
+
*/
|
|
57
|
+
declare const HOVER_BRIDGE_DIRECTION_TOLERANCE_PX = 2;
|
|
58
|
+
interface HoverBridgePoint {
|
|
59
|
+
x: number;
|
|
60
|
+
y: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The dominant axis and sign pointing from the trigger toward the target. Used
|
|
64
|
+
* to reject pointer movement heading away from the target while inside the corridor.
|
|
65
|
+
*/
|
|
66
|
+
interface HoverBridgeDirection {
|
|
67
|
+
axis: 'x' | 'y';
|
|
68
|
+
sign: 1 | -1;
|
|
69
|
+
}
|
|
70
|
+
interface CreateHoverBridgePolygonOptions {
|
|
71
|
+
triggerRect: DOMRect | null;
|
|
72
|
+
targetRect: DOMRect | null;
|
|
73
|
+
exitPoint: HoverBridgePoint;
|
|
74
|
+
corridorHalfSize?: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Computes the dominant axis and sign from the trigger toward the target - i.e.
|
|
78
|
+
* which way the pointer must travel to reach the panel. Returns null if either
|
|
79
|
+
* rect is missing.
|
|
80
|
+
*/
|
|
81
|
+
declare function getHoverBridgeDirection(triggerRect: DOMRect | null, targetRect: DOMRect | null): HoverBridgeDirection | null;
|
|
82
|
+
/**
|
|
83
|
+
* Builds a pointer grace polygon between the trigger exit point and the target overlay.
|
|
84
|
+
* The polygon is intentionally directional so moving away from the target exits quickly.
|
|
85
|
+
*/
|
|
86
|
+
declare function createHoverBridgePolygon({ triggerRect, targetRect, exitPoint, corridorHalfSize, }: CreateHoverBridgePolygonOptions): HoverBridgePoint[] | null;
|
|
87
|
+
/**
|
|
88
|
+
* Returns true when the point lies inside the provided polygon.
|
|
89
|
+
*/
|
|
90
|
+
declare function isPointInHoverBridgePolygon(point: HoverBridgePoint, polygon: HoverBridgePoint[]): boolean;
|
|
91
|
+
|
|
92
|
+
interface HoverBridgeOptions {
|
|
93
|
+
/** Whether the pointer is currently over the trigger or the panel (the "safe" area). */
|
|
94
|
+
isPointerInAnchor: () => boolean;
|
|
95
|
+
/** Close the overlay - called when the pointer leaves the corridor or the idle timer fires. */
|
|
96
|
+
close: () => void;
|
|
97
|
+
/**
|
|
98
|
+
* Require the pointer to keep heading toward the panel. When true, reversing
|
|
99
|
+
* away along the corridor's dominant axis closes the overlay (menu/submenu).
|
|
100
|
+
* Tooltips leave this off. Defaults to false.
|
|
101
|
+
*/
|
|
102
|
+
requireForwardMovement?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Reset the idle-fallback timer on valid in-corridor movement, so it only
|
|
105
|
+
* fires after the pointer genuinely stops. When false the timer is a fixed cap
|
|
106
|
+
* from the moment the corridor is built (tooltip's original semantics).
|
|
107
|
+
* Defaults to true.
|
|
108
|
+
*/
|
|
109
|
+
resetFallbackOnMove?: boolean;
|
|
110
|
+
/** Idle-fallback timeout in ms. Defaults to HOVER_BRIDGE_TIMEOUT_MS. */
|
|
111
|
+
timeoutMs?: number;
|
|
112
|
+
}
|
|
113
|
+
interface HoverBridgeTrackOptions {
|
|
114
|
+
triggerRect: DOMRect | null;
|
|
115
|
+
targetRect: DOMRect | null;
|
|
116
|
+
exitPoint: HoverBridgePoint;
|
|
117
|
+
}
|
|
118
|
+
interface HoverBridgeController {
|
|
119
|
+
/** The active corridor polygon, or null when no bridge is in progress. */
|
|
120
|
+
readonly polygon: Signal<HoverBridgePoint[] | null>;
|
|
121
|
+
/** Whether a corridor is currently active. */
|
|
122
|
+
isActive(): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Build a corridor from the exit point toward the panel and start tracking the
|
|
125
|
+
* pointer. Returns false (and does nothing) when a polygon can't be built, so
|
|
126
|
+
* the caller can apply its own fallback.
|
|
127
|
+
*/
|
|
128
|
+
track(options: HoverBridgeTrackOptions): boolean;
|
|
129
|
+
/** Tear down the corridor and its global listener/timer. */
|
|
130
|
+
clear(): void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Shared safe-polygon hover-intent state machine used by the menu, submenu and
|
|
134
|
+
* tooltip triggers. While the pointer travels inside the corridor toward the
|
|
135
|
+
* panel the overlay stays open; it closes when the pointer leaves the corridor,
|
|
136
|
+
* reverses away (when requireForwardMovement is set) or idles past the timeout.
|
|
137
|
+
*/
|
|
138
|
+
declare function createHoverBridge({ isPointerInAnchor, close, requireForwardMovement, resetFallbackOnMove, timeoutMs, }: HoverBridgeOptions): HoverBridgeController;
|
|
139
|
+
|
|
48
140
|
/**
|
|
49
141
|
* This implementation is heavily inspired by the great work on ngextension!
|
|
50
142
|
* https://github.com/ngxtension/ngxtension-platform/blob/main/libs/ngxtension/explicit-effect/src/explicit-effect.ts
|
|
@@ -210,5 +302,5 @@ declare function injectDimensions(): Signal<Dimensions>;
|
|
|
210
302
|
|
|
211
303
|
declare function scrollIntoViewIfNeeded(element: HTMLElement): void;
|
|
212
304
|
|
|
213
|
-
export { NgpExitAnimation, NgpExitAnimationManager, StyleInjector, domSort, explicitEffect, fromMutationObserver, fromResizeEvent, injectDimensions, injectElementRef, injectExitAnimationManager, injectStyleInjector, observeResize, onDomRemoval, provideExitAnimationManager, scrollIntoViewIfNeeded, setupExitAnimation, setupOverflowListener };
|
|
214
|
-
export type { Dimensions, NgpExitAnimationRef };
|
|
305
|
+
export { HOVER_BRIDGE_DIRECTION_TOLERANCE_PX, HOVER_BRIDGE_TIMEOUT_MS, NgpExitAnimation, NgpExitAnimationManager, StyleInjector, createHoverBridge, createHoverBridgePolygon, domSort, explicitEffect, fromMutationObserver, fromResizeEvent, getHoverBridgeDirection, injectDimensions, injectElementRef, injectExitAnimationManager, injectStyleInjector, isPointInHoverBridgePolygon, observeResize, onDomRemoval, provideExitAnimationManager, scrollIntoViewIfNeeded, setupExitAnimation, setupOverflowListener };
|
|
306
|
+
export type { Dimensions, HoverBridgeController, HoverBridgeDirection, HoverBridgeOptions, HoverBridgePoint, HoverBridgeTrackOptions, NgpExitAnimationRef };
|
|
@@ -653,6 +653,11 @@ declare class NgpSubmenuTrigger<T = unknown> {
|
|
|
653
653
|
* @default true
|
|
654
654
|
*/
|
|
655
655
|
readonly flip: _angular_core.InputSignalWithTransform<NgpFlip, NgpFlipInput>;
|
|
656
|
+
/**
|
|
657
|
+
* Define the container in which the menu should be attached.
|
|
658
|
+
* @default document.body
|
|
659
|
+
*/
|
|
660
|
+
readonly container: _angular_core.InputSignal<string | HTMLElement | null>;
|
|
656
661
|
/**
|
|
657
662
|
* Access the menu trigger state.
|
|
658
663
|
*/
|
|
@@ -676,7 +681,7 @@ declare class NgpSubmenuTrigger<T = unknown> {
|
|
|
676
681
|
*/
|
|
677
682
|
focus(origin?: FocusOrigin): void;
|
|
678
683
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgpSubmenuTrigger<any>, never>;
|
|
679
|
-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NgpSubmenuTrigger<any>, "[ngpSubmenuTrigger]", ["ngpSubmenuTrigger"], { "menu": { "alias": "ngpSubmenuTrigger"; "required": false; "isSignal": true; }; "disabled": { "alias": "ngpSubmenuTriggerDisabled"; "required": false; "isSignal": true; }; "placement": { "alias": "ngpSubmenuTriggerPlacement"; "required": false; "isSignal": true; }; "offset": { "alias": "ngpSubmenuTriggerOffset"; "required": false; "isSignal": true; }; "flip": { "alias": "ngpSubmenuTriggerFlip"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
684
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NgpSubmenuTrigger<any>, "[ngpSubmenuTrigger]", ["ngpSubmenuTrigger"], { "menu": { "alias": "ngpSubmenuTrigger"; "required": false; "isSignal": true; }; "disabled": { "alias": "ngpSubmenuTriggerDisabled"; "required": false; "isSignal": true; }; "placement": { "alias": "ngpSubmenuTriggerPlacement"; "required": false; "isSignal": true; }; "offset": { "alias": "ngpSubmenuTriggerOffset"; "required": false; "isSignal": true; }; "flip": { "alias": "ngpSubmenuTriggerFlip"; "required": false; "isSignal": true; }; "container": { "alias": "ngpSubmenuTriggerContainer"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
680
685
|
}
|
|
681
686
|
|
|
682
687
|
interface NgpSubmenuTriggerState {
|
|
@@ -704,6 +709,11 @@ interface NgpSubmenuTriggerState {
|
|
|
704
709
|
* Whether the menu should flip when there is not enough space.
|
|
705
710
|
*/
|
|
706
711
|
readonly flip: WritableSignal<NgpFlip>;
|
|
712
|
+
/**
|
|
713
|
+
* The container in which the menu should be attached.
|
|
714
|
+
* @default document.body
|
|
715
|
+
*/
|
|
716
|
+
readonly container: WritableSignal<HTMLElement | string | null>;
|
|
707
717
|
/**
|
|
708
718
|
* The focus origin used to open the submenu.
|
|
709
719
|
* Used by the submenu's focus trap for :focus-visible styling.
|
|
@@ -750,14 +760,20 @@ interface NgpSubmenuTriggerState {
|
|
|
750
760
|
* @param shouldFlip - Whether the menu should flip
|
|
751
761
|
*/
|
|
752
762
|
setFlip(shouldFlip: NgpFlip): void;
|
|
763
|
+
/**
|
|
764
|
+
* Set the container in which the menu should be attached. Takes effect the
|
|
765
|
+
* next time the menu is opened; it does not move a menu that is already open.
|
|
766
|
+
* @param container - The new container
|
|
767
|
+
*/
|
|
768
|
+
setContainer(container: HTMLElement | string | null): void;
|
|
753
769
|
/**
|
|
754
770
|
* Focus the trigger element.
|
|
755
771
|
* @param origin - The focus origin
|
|
756
772
|
*/
|
|
757
773
|
focus(origin: FocusOrigin): void;
|
|
758
774
|
/**
|
|
759
|
-
* Set whether the pointer is over the
|
|
760
|
-
*
|
|
775
|
+
* Set whether the pointer is over the submenu content. Entering the submenu
|
|
776
|
+
* tears down the hover bridge (the pointer arrived safely).
|
|
761
777
|
* @param isOver - Whether the pointer is over the content
|
|
762
778
|
* @internal
|
|
763
779
|
*/
|
|
@@ -784,6 +800,10 @@ interface NgpSubmenuTriggerProps<T = unknown> {
|
|
|
784
800
|
* Whether the menu should flip when there is not enough space.
|
|
785
801
|
*/
|
|
786
802
|
readonly flip?: Signal<NgpFlip>;
|
|
803
|
+
/**
|
|
804
|
+
* The container in which the menu should be attached.
|
|
805
|
+
*/
|
|
806
|
+
readonly container?: Signal<HTMLElement | string | null>;
|
|
787
807
|
}
|
|
788
808
|
declare const NgpSubmenuTriggerStateToken: _angular_core.InjectionToken<WritableSignal<{
|
|
789
809
|
placement: WritableSignal<NgpMenuPlacement>;
|
|
@@ -801,10 +821,12 @@ declare const NgpSubmenuTriggerStateToken: _angular_core.InjectionToken<Writable
|
|
|
801
821
|
setFlip: (shouldFlip: NgpFlip) => void;
|
|
802
822
|
setPlacement: (newPlacement: NgpMenuPlacement) => void;
|
|
803
823
|
setOffset: (newOffset: NgpOffset) => void;
|
|
824
|
+
setContainer: (newContainer: HTMLElement | string | null) => void;
|
|
804
825
|
focus: (origin: FocusOrigin) => void;
|
|
805
|
-
setPointerOverContent: (
|
|
826
|
+
setPointerOverContent: (isOver: boolean) => void;
|
|
827
|
+
container: WritableSignal<string | HTMLElement | null>;
|
|
806
828
|
}>>;
|
|
807
|
-
declare const ngpSubmenuTrigger: <T>({ disabled: _disabled, menu: _menu, placement: _placement, offset: _offset, flip: _flip, }: NgpSubmenuTriggerProps<T>) => {
|
|
829
|
+
declare const ngpSubmenuTrigger: <T>({ disabled: _disabled, menu: _menu, placement: _placement, offset: _offset, flip: _flip, container: _container, }: NgpSubmenuTriggerProps<T>) => {
|
|
808
830
|
placement: WritableSignal<NgpMenuPlacement>;
|
|
809
831
|
offset: WritableSignal<NgpOffset>;
|
|
810
832
|
disabled: WritableSignal<boolean>;
|
|
@@ -820,8 +842,10 @@ declare const ngpSubmenuTrigger: <T>({ disabled: _disabled, menu: _menu, placeme
|
|
|
820
842
|
setFlip: (shouldFlip: NgpFlip) => void;
|
|
821
843
|
setPlacement: (newPlacement: NgpMenuPlacement) => void;
|
|
822
844
|
setOffset: (newOffset: NgpOffset) => void;
|
|
845
|
+
setContainer: (newContainer: HTMLElement | string | null) => void;
|
|
823
846
|
focus: (origin: FocusOrigin) => void;
|
|
824
|
-
setPointerOverContent: (
|
|
847
|
+
setPointerOverContent: (isOver: boolean) => void;
|
|
848
|
+
container: WritableSignal<string | HTMLElement | null>;
|
|
825
849
|
};
|
|
826
850
|
declare const injectSubmenuTriggerState: {
|
|
827
851
|
(): Signal<{
|
|
@@ -840,8 +864,10 @@ declare const injectSubmenuTriggerState: {
|
|
|
840
864
|
setFlip: (shouldFlip: NgpFlip) => void;
|
|
841
865
|
setPlacement: (newPlacement: NgpMenuPlacement) => void;
|
|
842
866
|
setOffset: (newOffset: NgpOffset) => void;
|
|
867
|
+
setContainer: (newContainer: HTMLElement | string | null) => void;
|
|
843
868
|
focus: (origin: FocusOrigin) => void;
|
|
844
|
-
setPointerOverContent: (
|
|
869
|
+
setPointerOverContent: (isOver: boolean) => void;
|
|
870
|
+
container: WritableSignal<string | HTMLElement | null>;
|
|
845
871
|
}>;
|
|
846
872
|
(options: ng_primitives_state.StateInjectionOptions): Signal<{
|
|
847
873
|
placement: WritableSignal<NgpMenuPlacement>;
|
|
@@ -859,8 +885,10 @@ declare const injectSubmenuTriggerState: {
|
|
|
859
885
|
setFlip: (shouldFlip: NgpFlip) => void;
|
|
860
886
|
setPlacement: (newPlacement: NgpMenuPlacement) => void;
|
|
861
887
|
setOffset: (newOffset: NgpOffset) => void;
|
|
888
|
+
setContainer: (newContainer: HTMLElement | string | null) => void;
|
|
862
889
|
focus: (origin: FocusOrigin) => void;
|
|
863
|
-
setPointerOverContent: (
|
|
890
|
+
setPointerOverContent: (isOver: boolean) => void;
|
|
891
|
+
container: WritableSignal<string | HTMLElement | null>;
|
|
864
892
|
} | null>;
|
|
865
893
|
(options?: ng_primitives_state.StateInjectionOptions): Signal<{
|
|
866
894
|
placement: WritableSignal<NgpMenuPlacement>;
|
|
@@ -878,8 +906,10 @@ declare const injectSubmenuTriggerState: {
|
|
|
878
906
|
setFlip: (shouldFlip: NgpFlip) => void;
|
|
879
907
|
setPlacement: (newPlacement: NgpMenuPlacement) => void;
|
|
880
908
|
setOffset: (newOffset: NgpOffset) => void;
|
|
909
|
+
setContainer: (newContainer: HTMLElement | string | null) => void;
|
|
881
910
|
focus: (origin: FocusOrigin) => void;
|
|
882
|
-
setPointerOverContent: (
|
|
911
|
+
setPointerOverContent: (isOver: boolean) => void;
|
|
912
|
+
container: WritableSignal<string | HTMLElement | null>;
|
|
883
913
|
}> | Signal<{
|
|
884
914
|
placement: WritableSignal<NgpMenuPlacement>;
|
|
885
915
|
offset: WritableSignal<NgpOffset>;
|
|
@@ -896,8 +926,10 @@ declare const injectSubmenuTriggerState: {
|
|
|
896
926
|
setFlip: (shouldFlip: NgpFlip) => void;
|
|
897
927
|
setPlacement: (newPlacement: NgpMenuPlacement) => void;
|
|
898
928
|
setOffset: (newOffset: NgpOffset) => void;
|
|
929
|
+
setContainer: (newContainer: HTMLElement | string | null) => void;
|
|
899
930
|
focus: (origin: FocusOrigin) => void;
|
|
900
|
-
setPointerOverContent: (
|
|
931
|
+
setPointerOverContent: (isOver: boolean) => void;
|
|
932
|
+
container: WritableSignal<string | HTMLElement | null>;
|
|
901
933
|
} | null>;
|
|
902
934
|
};
|
|
903
935
|
declare const provideSubmenuTriggerState: (opts?: {
|
|
@@ -102,6 +102,12 @@ interface CooldownOverlay {
|
|
|
102
102
|
hideImmediate(): void;
|
|
103
103
|
/** Optional signal to mark the transition as instant due to cooldown */
|
|
104
104
|
instantTransition?: WritableSignal<boolean>;
|
|
105
|
+
/**
|
|
106
|
+
* Optional check for whether this overlay is a descendant of the given overlay
|
|
107
|
+
* (i.e. its trigger is rendered within the other overlay's content). Used to
|
|
108
|
+
* keep an ancestor open when a nested overlay of the same type is activated.
|
|
109
|
+
*/
|
|
110
|
+
isDescendantOf?(other: CooldownOverlay): boolean;
|
|
105
111
|
}
|
|
106
112
|
/**
|
|
107
113
|
* Singleton service that tracks close timestamps and active overlays per overlay type.
|
|
@@ -112,6 +118,13 @@ interface CooldownOverlay {
|
|
|
112
118
|
*/
|
|
113
119
|
declare class NgpOverlayCooldownManager {
|
|
114
120
|
private readonly lastCloseTimestamps;
|
|
121
|
+
/**
|
|
122
|
+
* Active overlays per type, stored as a stack ordered oldest-first / topmost-last.
|
|
123
|
+
* Most types only ever hold a single overlay, but when an overlay is nested inside
|
|
124
|
+
* another of the same type (e.g. a popover whose trigger lives inside another
|
|
125
|
+
* popover) the child is pushed on top of its ancestor instead of evicting it.
|
|
126
|
+
* Closing the child then restores the ancestor as the active overlay.
|
|
127
|
+
*/
|
|
115
128
|
private readonly activeOverlays;
|
|
116
129
|
/**
|
|
117
130
|
* Record the close timestamp for an overlay type.
|
|
@@ -127,7 +140,14 @@ declare class NgpOverlayCooldownManager {
|
|
|
127
140
|
isWithinCooldown(overlayType: string, duration: number): boolean;
|
|
128
141
|
/**
|
|
129
142
|
* Register an overlay as active for its type.
|
|
130
|
-
*
|
|
143
|
+
*
|
|
144
|
+
* Any existing overlay of the same type is closed immediately so that only one
|
|
145
|
+
* overlay of each type is open at a time - *unless* it is an ancestor of the
|
|
146
|
+
* overlay being registered. A nested overlay (its trigger rendered inside an
|
|
147
|
+
* ancestor overlay's content) is stacked on top of its ancestor instead of
|
|
148
|
+
* evicting it, allowing legitimate nesting to coexist while sibling overlays
|
|
149
|
+
* still replace one another.
|
|
150
|
+
*
|
|
131
151
|
* @param overlayType The type identifier for the overlay group
|
|
132
152
|
* @param overlay The overlay instance
|
|
133
153
|
* @param cooldown The cooldown duration - if > 0, enables instant transitions
|
|
@@ -629,6 +649,16 @@ declare class NgpOverlay<T = unknown> implements CooldownOverlay {
|
|
|
629
649
|
* @internal
|
|
630
650
|
*/
|
|
631
651
|
unregisterChildOverlay(child: NgpOverlay): void;
|
|
652
|
+
/**
|
|
653
|
+
* Determine whether this overlay is a descendant of the given overlay - i.e.
|
|
654
|
+
* its trigger is rendered within the other overlay's content. Walks the parent
|
|
655
|
+
* overlay chain established through dependency injection.
|
|
656
|
+
*
|
|
657
|
+
* Used by the cooldown manager to avoid evicting an ancestor overlay when a
|
|
658
|
+
* nested overlay of the same type is activated.
|
|
659
|
+
* @internal
|
|
660
|
+
*/
|
|
661
|
+
isDescendantOf(other: CooldownOverlay): boolean;
|
|
632
662
|
/**
|
|
633
663
|
* Check if the event path includes any child overlay elements (recursively).
|
|
634
664
|
* @internal
|
|
@@ -7,6 +7,8 @@ import { Placement } from '@floating-ui/dom';
|
|
|
7
7
|
import { NumberInput, BooleanInput } from '@angular/cdk/coercion';
|
|
8
8
|
import * as ng_primitives_state from 'ng-primitives/state';
|
|
9
9
|
import { StateInjectionOptions } from 'ng-primitives/state';
|
|
10
|
+
import * as ng_primitives_internal from 'ng-primitives/internal';
|
|
11
|
+
import { HoverBridgePoint } from 'ng-primitives/internal';
|
|
10
12
|
|
|
11
13
|
interface NgpTooltipConfig {
|
|
12
14
|
/**
|
|
@@ -115,11 +117,6 @@ declare const provideTooltipArrowState: (opts?: {
|
|
|
115
117
|
inherit?: boolean;
|
|
116
118
|
}) => _angular_core.FactoryProvider;
|
|
117
119
|
|
|
118
|
-
interface TooltipHoverBridgePoint {
|
|
119
|
-
x: number;
|
|
120
|
-
y: number;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
120
|
interface NgpTooltipTriggerState<T> {
|
|
124
121
|
/** Access the tooltip template ref. */
|
|
125
122
|
readonly tooltip: WritableSignal<NgpOverlayContent<T> | string | null>;
|
|
@@ -240,7 +237,7 @@ interface NgpTooltipTriggerState<T> {
|
|
|
240
237
|
/**
|
|
241
238
|
* Current pointer grace polygon used while crossing trigger -> tooltip.
|
|
242
239
|
*/
|
|
243
|
-
readonly hoverBridgePolygon: Signal<
|
|
240
|
+
readonly hoverBridgePolygon: Signal<HoverBridgePoint[] | null>;
|
|
244
241
|
/**
|
|
245
242
|
* Show the tooltip programmatically (skips cooldown so multiple tooltips can coexist).
|
|
246
243
|
*/
|
|
@@ -392,7 +389,7 @@ declare const NgpTooltipTriggerStateToken: _angular_core.InjectionToken<Writable
|
|
|
392
389
|
open: Signal<boolean>;
|
|
393
390
|
hasOverflow: Signal<boolean>;
|
|
394
391
|
contentHovered: WritableSignal<boolean>;
|
|
395
|
-
hoverBridgePolygon:
|
|
392
|
+
hoverBridgePolygon: Signal<HoverBridgePoint[] | null>;
|
|
396
393
|
show: () => void;
|
|
397
394
|
hide: () => void;
|
|
398
395
|
setTooltipId: (id: string) => void;
|
|
@@ -425,7 +422,7 @@ declare const ngpTooltipTrigger: <T>({ tooltip: _tooltip, disabled, placement, o
|
|
|
425
422
|
open: Signal<boolean>;
|
|
426
423
|
hasOverflow: Signal<boolean>;
|
|
427
424
|
contentHovered: WritableSignal<boolean>;
|
|
428
|
-
hoverBridgePolygon:
|
|
425
|
+
hoverBridgePolygon: Signal<HoverBridgePoint[] | null>;
|
|
429
426
|
show: () => void;
|
|
430
427
|
hide: () => void;
|
|
431
428
|
setTooltipId: (id: string) => void;
|
|
@@ -569,7 +566,7 @@ declare class NgpTooltipTrigger<T = null> implements OnDestroy {
|
|
|
569
566
|
open: _angular_core.Signal<boolean>;
|
|
570
567
|
hasOverflow: _angular_core.Signal<boolean>;
|
|
571
568
|
contentHovered: _angular_core.WritableSignal<boolean>;
|
|
572
|
-
hoverBridgePolygon: _angular_core.
|
|
569
|
+
hoverBridgePolygon: _angular_core.Signal<ng_primitives_internal.HoverBridgePoint[] | null>;
|
|
573
570
|
show: () => void;
|
|
574
571
|
hide: () => void;
|
|
575
572
|
setTooltipId: (id: string) => void;
|
|
@@ -33,6 +33,11 @@ declare function booleanAttributeBinding(element: HTMLElement, attribute: string
|
|
|
33
33
|
* Disposable functions are a way to manage timers, intervals, and event listeners
|
|
34
34
|
* that should be cleared when a component is destroyed.
|
|
35
35
|
*
|
|
36
|
+
* Each disposable releases its DestroyRef registration as soon as the resource
|
|
37
|
+
* is gone (the timer fires or the returned cleanup runs), so repeated calls -
|
|
38
|
+
* e.g. rescheduling a timeout on every pointermove - don't accumulate destroy
|
|
39
|
+
* callbacks for the lifetime of the host.
|
|
40
|
+
*
|
|
36
41
|
* This is heavily inspired by Headless UI disposables:
|
|
37
42
|
* https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/utils/disposables.ts
|
|
38
43
|
*/
|
|
@@ -45,20 +50,18 @@ declare function injectDisposables(): {
|
|
|
45
50
|
*/
|
|
46
51
|
setTimeout: (callback: () => void, delay: number) => () => void;
|
|
47
52
|
/**
|
|
48
|
-
*
|
|
49
|
-
* @param
|
|
50
|
-
* @param
|
|
51
|
-
* @param
|
|
52
|
-
* @param
|
|
53
|
-
* @
|
|
54
|
-
* @param options
|
|
55
|
-
* @returns A function to clear the interval
|
|
53
|
+
* Add an event listener that will be removed when the component is destroyed.
|
|
54
|
+
* @param target The event target
|
|
55
|
+
* @param type The event type
|
|
56
|
+
* @param listener The event listener
|
|
57
|
+
* @param options The event listener options
|
|
58
|
+
* @returns A function to remove the event listener
|
|
56
59
|
*/
|
|
57
60
|
addEventListener: <K extends keyof HTMLElementEventMap>(target: EventTarget, type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions) => () => void;
|
|
58
61
|
/**
|
|
59
62
|
* Set an interval that will be cleared when the component is destroyed.
|
|
60
63
|
* @param callback The callback to execute
|
|
61
|
-
* @param delay The delay
|
|
64
|
+
* @param delay The delay between executions
|
|
62
65
|
* @returns A function to clear the interval
|
|
63
66
|
*/
|
|
64
67
|
setInterval: (callback: () => void, delay: number) => () => void;
|