forty-cdk 0.32.0 → 0.33.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/breakpoints/README.md +6 -2
- package/carousel/README.md +4 -3
- package/date-field/README.md +2 -2
- package/date-picker/README.md +6 -5
- package/drag-drop/README.md +8 -3
- package/drawer/README.md +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +6 -5
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -1
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -1
- package/fesm2022/forty-cdk-carousel.mjs +32 -25
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -1
- package/fesm2022/forty-cdk-core-overlay.mjs +49 -2
- package/fesm2022/forty-cdk-core-overlay.mjs.map +1 -1
- package/fesm2022/forty-cdk-core.mjs +226 -82
- package/fesm2022/forty-cdk-core.mjs.map +1 -1
- package/fesm2022/forty-cdk-date-picker.mjs +6 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -1
- package/fesm2022/forty-cdk-defaults.mjs +33 -9
- package/fesm2022/forty-cdk-defaults.mjs.map +1 -1
- package/fesm2022/forty-cdk-drag-drop.mjs +4 -2
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/forty-cdk-drawer.mjs +22 -5
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -1
- package/fesm2022/forty-cdk-file-upload.mjs +23 -11
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -1
- package/fesm2022/forty-cdk-meter.mjs +2 -8
- package/fesm2022/forty-cdk-meter.mjs.map +1 -1
- package/fesm2022/forty-cdk-navigation-menu.mjs +58 -45
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -1
- package/fesm2022/forty-cdk-pagination.mjs +28 -10
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -1
- package/fesm2022/forty-cdk-pane-resizer.mjs +21 -5
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -1
- package/fesm2022/forty-cdk-popover.mjs +7 -1
- package/fesm2022/forty-cdk-popover.mjs.map +1 -1
- package/fesm2022/forty-cdk-radio-group.mjs +41 -16
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -1
- package/fesm2022/forty-cdk-scroll-area.mjs +15 -6
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -1
- package/fesm2022/forty-cdk-slider.mjs +14 -3
- package/fesm2022/forty-cdk-slider.mjs.map +1 -1
- package/fesm2022/forty-cdk-table.mjs +62 -17
- package/fesm2022/forty-cdk-table.mjs.map +1 -1
- package/fesm2022/forty-cdk-testing.mjs +7 -1
- package/fesm2022/forty-cdk-testing.mjs.map +1 -1
- package/fesm2022/forty-cdk-toast.mjs +11 -2
- package/fesm2022/forty-cdk-toast.mjs.map +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs +11 -2
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/field/README.md +1 -1
- package/fieldset/README.md +1 -1
- package/file-upload/README.md +1 -1
- package/meter/README.md +1 -1
- package/navigation-menu/README.md +2 -2
- package/package.json +1 -1
- package/pagination/README.md +4 -5
- package/pane-resizer/README.md +2 -2
- package/popover/README.md +1 -1
- package/radio-group/README.md +16 -16
- package/scroll-area/README.md +4 -4
- package/shared/README.md +2 -1
- package/slider/README.md +3 -3
- package/table/README.md +26 -21
- package/time-field/README.md +2 -2
- package/toast/README.md +2 -0
- package/types/forty-cdk-breakpoints.d.ts +1 -1
- package/types/forty-cdk-carousel.d.ts +11 -6
- package/types/forty-cdk-core-overlay.d.ts +15 -0
- package/types/forty-cdk-core.d.ts +104 -23
- package/types/forty-cdk-date-picker.d.ts +3 -1
- package/types/forty-cdk-defaults.d.ts +28 -20
- package/types/forty-cdk-drag-drop.d.ts +4 -2
- package/types/forty-cdk-drawer.d.ts +8 -0
- package/types/forty-cdk-file-upload.d.ts +17 -3
- package/types/forty-cdk-meter.d.ts +1 -1
- package/types/forty-cdk-navigation-menu.d.ts +14 -21
- package/types/forty-cdk-pagination.d.ts +16 -6
- package/types/forty-cdk-pane-resizer.d.ts +16 -2
- package/types/forty-cdk-popover.d.ts +17 -15
- package/types/forty-cdk-radio-group.d.ts +13 -5
- package/types/forty-cdk-scroll-area.d.ts +11 -5
- package/types/forty-cdk-slider.d.ts +6 -5
- package/types/forty-cdk-table.d.ts +20 -3
- package/types/forty-cdk-testing.d.ts +4 -1
- package/types/forty-cdk-toast.d.ts +11 -4
- package/types/forty-cdk-virtualization.d.ts +10 -6
- package/virtualization/README.md +8 -4
package/breakpoints/README.md
CHANGED
|
@@ -109,6 +109,8 @@ The default map gives you fully-typed names out of the box (`up('md')` autocompl
|
|
|
109
109
|
|
|
110
110
|
```ts
|
|
111
111
|
// breakpoints.ts
|
|
112
|
+
import { injectBreakpoints } from 'forty-cdk/breakpoints';
|
|
113
|
+
|
|
112
114
|
export const appBreakpoints = {
|
|
113
115
|
mobile: 0,
|
|
114
116
|
tablet: 640,
|
|
@@ -116,12 +118,14 @@ export const appBreakpoints = {
|
|
|
116
118
|
desktop: 1280,
|
|
117
119
|
} as const;
|
|
118
120
|
|
|
119
|
-
declare module 'forty-cdk' {
|
|
121
|
+
declare module 'forty-cdk/breakpoints' {
|
|
120
122
|
interface BreakpointRegistry extends Record<keyof typeof appBreakpoints, true> {}
|
|
121
123
|
}
|
|
124
|
+
|
|
125
|
+
export const injectIsDesktop = () => injectBreakpoints().up('desktop');
|
|
122
126
|
```
|
|
123
127
|
|
|
124
|
-
Now `injectBreakpoints()` autocompletes `'mobile' | 'tablet' | 'laptop' | 'desktop'` across the whole app.
|
|
128
|
+
Now `injectBreakpoints()` autocompletes `'mobile' | 'tablet' | 'laptop' | 'desktop'` across the whole app, and `up('desktop')` compiles without a cast. Augment `'forty-cdk/breakpoints'`, the entry point that declares the registry: the bare `'forty-cdk'` package exports nothing, so a block naming it declares an unrelated interface and the names stay the Tailwind scale.
|
|
125
129
|
|
|
126
130
|
## API
|
|
127
131
|
|
package/carousel/README.md
CHANGED
|
@@ -483,8 +483,9 @@ Implements the [WAI-ARIA Carousel pattern](https://www.w3.org/WAI/ARIA/apg/patte
|
|
|
483
483
|
or localize the default format and both role descriptions app-wide with
|
|
484
484
|
`provideForCarouselDefaults` (see
|
|
485
485
|
[Localizing the default labels](#localizing-the-default-labels)).
|
|
486
|
-
-
|
|
487
|
-
accessibility tree and focus order.
|
|
486
|
+
- Slides outside the viewport receive `aria-hidden="true"` and `inert` to remove them from
|
|
487
|
+
the accessibility tree and focus order. The visible set follows the laid-out offset, so
|
|
488
|
+
`align` and `containScroll` decide which slides stay reachable.
|
|
488
489
|
- The indicator group should be labelled (e.g. `ariaLabel="Choose slide to display"`).
|
|
489
490
|
- The current indicator is marked with `aria-current="true"`.
|
|
490
491
|
- Prev/next buttons never use the native `disabled` attribute. At a boundary without `loop`
|
|
@@ -532,7 +533,7 @@ children, unless noted otherwise:
|
|
|
532
533
|
| Property | Host | Value | Notes |
|
|
533
534
|
| --------------------------------- | ----------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
534
535
|
| `--for-carousel-offset` | `[forCarousel]` | e.g. `-100%` | Pure arithmetic from `activeIndex`, `slidesPerView`, `align`. |
|
|
535
|
-
| `--for-carousel-active-index` | `[forCarousel]` | integer | Current `activeIndex
|
|
536
|
+
| `--for-carousel-active-index` | `[forCarousel]` | integer | Current `activeIndex`, clamped to the registered slides. |
|
|
536
537
|
| `--for-carousel-slide-count` | `[forCarousel]` | integer | Total registered slides. |
|
|
537
538
|
| `--for-carousel-slides-per-view` | `[forCarousel]` | integer | From the `slidesPerView` input. |
|
|
538
539
|
| `--for-carousel-viewport-width` | `[forCarousel]` | e.g. `640px` | Measured via `ResizeObserver`. Absent on the server and before first measurement. |
|
package/date-field/README.md
CHANGED
|
@@ -220,7 +220,7 @@ providers: [
|
|
|
220
220
|
|
|
221
221
|
`segmentLabels` supplies each segment's default `aria-label`, keyed by part type. Unset keys keep the library default (the part name, and `'AM/PM'` for the `dayPeriod` segment), so overriding a single key never wipes the rest. A segment's own `[ariaLabel]` still wins over the scope default.
|
|
222
222
|
|
|
223
|
-
`placeholder` is the text an empty segment shows, keyed the same way. A field's own `[placeholder]` wins for the parts it names and only those, and a part neither names keeps the letter-repeat default. `provideForDateRangeFieldDefaults` takes the same keys for `[forDateRangeField]`.
|
|
223
|
+
`placeholder` is the text an empty segment shows, keyed the same way. A field's own `[placeholder]` wins for the parts it names and only those, and a part neither names keeps the letter-repeat default. A nested `provideForDateFieldDefaults` merges both records entry by entry with its parent scope, so a feature overriding only `dayPeriod` keeps the labels and placeholders the application root localized. `provideForDateRangeFieldDefaults` takes the same keys for `[forDateRangeField]`.
|
|
224
224
|
|
|
225
225
|
`hourCycle` sets the 12- or 24-hour cycle of every field that doesn't bind `[hourCycle]`, so a product on a 24-hour clock sets it once instead of on each field. Its fallback `null` derives the cycle from the locale.
|
|
226
226
|
|
|
@@ -313,7 +313,7 @@ Key behavior applies per segment. Horizontal arrows mirror under `dir="rtl"`.
|
|
|
313
313
|
| **Backspace** | Delete the last entered digit; the value becomes `null` when the last digit is removed. |
|
|
314
314
|
| **Delete** | Clear the whole segment (the value becomes `null` until refilled). |
|
|
315
315
|
|
|
316
|
-
The day clamps to the current month's length (e.g. 31 → 28 in February), and a composed value is clamped into `[minDate, maxDate]`.
|
|
316
|
+
The day clamps to the current month's length (e.g. 31 → 28 in February), and a composed value is clamped into `[minDate, maxDate]`. While focus stays in the field its segments keep what was entered, so editing one part never rewrites the others; they show the clamped value once focus leaves. Stepping the hour skips a wall-clock hour the day does not have, such as a spring-forward DST gap.
|
|
317
317
|
|
|
318
318
|
## Accessibility
|
|
319
319
|
|
package/date-picker/README.md
CHANGED
|
@@ -439,11 +439,12 @@ Defaults are configured with `provideForDateRangePickerDefaults` (`side` / `alig
|
|
|
439
439
|
|
|
440
440
|
## Keyboard
|
|
441
441
|
|
|
442
|
-
| Key | Behavior
|
|
443
|
-
| -------------------------------------------------- |
|
|
444
|
-
| **Enter / Space** on trigger | Open the surface (native button activation).
|
|
445
|
-
| **ArrowDown / Alt+ArrowDown / ArrowUp** on trigger | Open the surface and move focus to the calendar.
|
|
446
|
-
| **Escape** | Dismiss the surface and return focus to the trigger (when `dismissible`).
|
|
442
|
+
| Key | Behavior |
|
|
443
|
+
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
444
|
+
| **Enter / Space** on trigger | Open the surface (native button activation). |
|
|
445
|
+
| **ArrowDown / Alt+ArrowDown / ArrowUp** on trigger | Open the surface and move focus to the calendar. |
|
|
446
|
+
| **Escape** | Dismiss the surface and return focus to the trigger (when `dismissible`). |
|
|
447
|
+
| **Tab / Shift+Tab** past either end of the surface | Non-modal: Tab moves on to the element after the trigger and Shift+Tab lands on the trigger, closing the surface when `dismissible`. Modal: focus stays trapped. |
|
|
447
448
|
|
|
448
449
|
Inside the surface, the projected `ForCalendar` owns the full grid keyboard map (arrows / `Home` / `End` / `PageUp` / `PageDown` / `Enter` / `Space`). On open, focus lands on the calendar's focused cell (`value ?? today`, with today clamped into the calendar's `[min, max]`) in non-modal mode, or the first focusable element in modal mode.
|
|
449
450
|
|
package/drag-drop/README.md
CHANGED
|
@@ -446,11 +446,16 @@ scope. `[animateReorder]` (FLIP) reflows by DOM order and needs no change in mix
|
|
|
446
446
|
## Free drag
|
|
447
447
|
|
|
448
448
|
`[forFreeDrag]` repositions its host element (or a resolved `rootElement`) by pointer drag, with
|
|
449
|
-
**no `[forDropList]` required**. It never commits a reorder; it just moves the element around via
|
|
450
|
-
CSS `
|
|
449
|
+
**no `[forDropList]` required**. It never commits a reorder; it just moves the element around via
|
|
450
|
+
the CSS `translate` property. It is the standalone counterpart to a sortable list item: a
|
|
451
451
|
pointer-driven way to move an element freely, with optional root-element retargeting, a confinement
|
|
452
452
|
boundary, axis locking, and a controllable position (see the inputs below).
|
|
453
453
|
|
|
454
|
+
The offset composes with any `transform` the moved element already has, so a dialog centred with
|
|
455
|
+
`transform: translate(-50%, -50%)` stays centred and moves from there. It replaces an inline
|
|
456
|
+
`translate` property of the element's own, so position the element with `transform` or layout
|
|
457
|
+
properties rather than `translate`.
|
|
458
|
+
|
|
454
459
|
```html
|
|
455
460
|
<!-- move the whole dialog by its header -->
|
|
456
461
|
<div class="dialog">
|
|
@@ -467,7 +472,7 @@ boundary, axis locking, and a controllable position (see the inputs below).
|
|
|
467
472
|
|
|
468
473
|
| Input | Type | Default | Meaning |
|
|
469
474
|
| ------------- | ------------------------------- | ------------- | -------------------------------------------------------------------------------------------------- |
|
|
470
|
-
| `disabled` | `boolean` | `false` | When true, the element can't be dragged (it stays focusable;
|
|
475
|
+
| `disabled` | `boolean` | `false` | When true, the element can't be dragged (it stays focusable; its position doesn't change). |
|
|
471
476
|
| `rootElement` | `HTMLElement \| string \| null` | `null` | The element actually moved. A `closest()` selector resolves an ancestor. `null` moves the host. |
|
|
472
477
|
| `boundary` | `HTMLElement \| string \| null` | `null` | Confine the moved element fully inside this element (or `closest()` selector). `null` = unbounded. |
|
|
473
478
|
| `lockAxis` | `'x' \| 'y' \| null` | `null` | Constrain movement to one axis. `'x'` pins lift-time `y`, `'y'` pins lift-time `x`. |
|
package/drawer/README.md
CHANGED
|
@@ -517,6 +517,7 @@ For a seamless release, transition **both** `translate` and your snap-position p
|
|
|
517
517
|
- Dismissal and dragging are separate. With `swipeToDismiss` or `dismissible` set to `false`, a drawer with `snapPoints` still drags between them and stops at the lowest snap, and `(swipeEnd)` reports `willClose: false`; a drawer without `snapPoints` arms no drag at all.
|
|
518
518
|
- `closeThreshold` (default `0.25`) is the fraction past which a release from the lowest snap dismisses. It is measured against that snap's own extent (not the full dimension), so a small "peek" snap stays dismissible without dragging it off-screen.
|
|
519
519
|
- `handleOnly: true` confines the gesture to a registered `[forDrawerHandle]`, leaving the rest of the surface free for content scroll.
|
|
520
|
+
- On touch the browser would otherwise claim the swipe as a scroll. The handle always carries `touch-action: none`, and while the whole surface arms the drag (`handleOnly` off, and a drag the drawer can arm) the surface carries an inline `touch-action` that leaves the browser only the other axis: `pan-x` for a `top` / `bottom` drawer, `pan-y` for `left` / `right`. A scroller inside the surface keeps its own native scrolling, so your CSS needs no `touch-action` rule of its own.
|
|
520
521
|
- Gestures starting inside a scrollable element that hasn't reached its edge are NOT treated as swipes (the helper defers to inner scroll).
|
|
521
522
|
- **`prefers-reduced-motion: reduce`** disables the drag entirely, snap dragging included. Escape, backdrop, outside-pointer, and close button continue to work.
|
|
522
523
|
|
|
@@ -199,17 +199,18 @@ class ForAvatarImage {
|
|
|
199
199
|
const parent = this.#parent;
|
|
200
200
|
const host = this.#host;
|
|
201
201
|
let destroyed = false;
|
|
202
|
-
|
|
202
|
+
const destroyRef = inject(DestroyRef);
|
|
203
|
+
destroyRef.onDestroy(() => (destroyed = true));
|
|
203
204
|
afterNextRender(() => {
|
|
204
205
|
this.#syncFromAttr();
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
if (destroyed || typeof MutationObserver === 'undefined')
|
|
207
|
+
return;
|
|
207
208
|
const observer = new MutationObserver(() => {
|
|
208
209
|
this.#syncFromAttr();
|
|
209
210
|
});
|
|
210
211
|
observer.observe(host, { attributes: true, attributeFilter: ['src'] });
|
|
211
|
-
|
|
212
|
-
}
|
|
212
|
+
destroyRef.onDestroy(() => observer.disconnect());
|
|
213
|
+
});
|
|
213
214
|
|
|
214
215
|
|
|
215
216
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forty-cdk-avatar.mjs","sources":["../../../projects/forty-cdk/avatar/src/avatar-context.ts","../../../projects/forty-cdk/avatar/src/avatar.ts","../../../projects/forty-cdk/avatar/src/avatar-image.ts","../../../projects/forty-cdk/avatar/src/avatar-fallback.ts","../../../projects/forty-cdk/avatar/src/forty-cdk-avatar.ts"],"sourcesContent":["import { inject, InjectionToken, type Signal } from '@angular/core';\n\nimport { assertRootContext, orphanContextError } from 'forty-cdk/core';\n\n/**\n * Lifecycle stages of the avatar's image:\n * - `idle`: no `src` set yet.\n * - `loading`: `src` set, waiting for the network.\n * - `loaded`: image rendered successfully.\n * - `error`: image failed to load.\n */\nexport type ForAvatarStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\n/** Read surface `[forAvatar]` publishes through {@link FOR_AVATAR_CONTEXT}. */\nexport interface ForAvatarContext {\n /** Current status of the avatar's image. */\n readonly status: Signal<ForAvatarStatus>;\n /**\n * `true` when the consumer should render the fallback. Drives `@if` in\n * the consumer's template. Honors `fallbackDelayMs` for `idle` / `loading`\n * states; `error` flips it to `true` immediately.\n */\n readonly shouldShowFallback: Signal<boolean>;\n}\n\n/**\n * The avatar's piece-coordination surface: the one call `[forAvatarImage]`\n * makes so the root can transition its state machine.\n *\n * **Not** part of {@link ForAvatarContext} and never exported from\n * `public-api.ts` — a consumer drives the status by binding `[src]`, never by\n * reporting a transition into the root.\n */\nexport interface AvatarPieceContext {\n /** Reported by `[forAvatarImage]` so the root can transition its state machine. */\n reportStatus(status: ForAvatarStatus): void;\n}\n\n/**\n * The avatar's internal coordination surface: everything\n * {@link ForAvatarContext} publishes plus the {@link AvatarPieceContext} call.\n *\n * Never exported from `public-api.ts`. It is the type the pieces read\n * {@link FOR_AVATAR_CONTEXT} at, so a consumer who injects that token gets the\n * read surface while `[forAvatarImage]` gets the transition channel. `ForAvatar`\n * declares `reportStatus` TS-`private`, which keeps it out of the emitted\n * `.d.ts` while `useExisting` still satisfies this contract at runtime.\n */\nexport interface AvatarContext extends ForAvatarContext, AvatarPieceContext {}\n\n/**\n * DI token for the avatar's coordination surface, provided by `[forAvatar]`.\n *\n * Publicly typed as the read surface {@link ForAvatarContext}, which is the whole of\n * what the token promises a consumer. `[forAvatarImage]` reads the same token at an\n * internal type that adds the status-report channel, so a wrapper re-providing it must\n * alias it to the root: `{ provide: FOR_AVATAR_CONTEXT, useExisting: MyAvatar }`, where\n * `MyAvatar` extends `ForAvatar`. A value that merely satisfies the declared type\n * resolves too, and is rejected in dev mode by the first piece to reach the channel.\n */\nexport const FOR_AVATAR_CONTEXT = new InjectionToken<ForAvatarContext>('FOR_AVATAR_CONTEXT');\n\n/**\n * The constant half of both resolvers' {@link assertRootContext} calls, so the\n * fallback and the image state the same requirement.\n */\nconst ROOT_ASSERTION = {\n entryPoint: 'avatar',\n token: 'FOR_AVATAR_CONTEXT',\n root: '[forAvatar]',\n};\n\n/**\n * Injects the nearest {@link ForAvatarContext}, throwing a descriptive error\n * when used outside a `[forAvatar]` element.\n *\n * @param piece Name of the calling directive, used in the error message.\n */\nexport function injectAvatarContext(piece: string): AvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw orphanContextError({\n code: 'FORCDK-AVATAR-001',\n piece,\n root: '[forAvatar]',\n token: 'FOR_AVATAR_CONTEXT',\n });\n }\n const widened = ctx as AvatarContext;\n assertRootContext({\n ...ROOT_ASSERTION,\n piece,\n probe: () => widened.reportStatus,\n });\n return widened;\n}\n\n/**\n * `[forAvatarImage]`'s own resolver. It reports a distinct failure from the rest\n * of the anatomy — the image is the piece that drives the state machine, so an\n * orphaned one silently never transitions — and therefore carries its own code\n * rather than routing through {@link injectAvatarContext}.\n */\nexport function injectAvatarImageContext(): AvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw orphanContextError({\n code: 'FORCDK-AVATAR-002',\n piece: 'ForAvatarImage',\n root: '[forAvatar]',\n token: 'FOR_AVATAR_CONTEXT',\n });\n }\n const widened = ctx as AvatarContext;\n assertRootContext({\n ...ROOT_ASSERTION,\n piece: 'ForAvatarImage',\n probe: () => widened.reportStatus,\n });\n return widened;\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n Directive,\n effect,\n inject,\n input,\n linkedSignal,\n numberAttribute,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\n\nimport { FOR_AVATAR_CONTEXT, type ForAvatarContext, type ForAvatarStatus } from './avatar-context';\nimport { FOR_AVATAR_DEFAULTS } from 'forty-cdk/defaults';\n\n/**\n * Headless avatar root. Tracks the load lifecycle of an inner\n * `[forAvatarImage]` and exposes `status` + `shouldShowFallback` so the\n * consumer can swap the image for fallback content (initials, generic icon)\n * via `@if`.\n *\n * No ARIA role is imposed — avatars are typically presentational. If the\n * surrounding component conveys identity (a person's name in the header,\n * a chat bubble), the consumer should provide an `aria-label` on the\n * relevant ancestor or pair the avatar with visible text.\n *\n * @example\n * ```html\n * <span forAvatar #a=\"forAvatar\" fallbackDelayMs=\"500\">\n * <img forAvatarImage [src]=\"user.avatarUrl\" [alt]=\"user.name\" />\n * @if (a.shouldShowFallback()) {\n * <span forAvatarFallback>{{ user.initials }}</span>\n * }\n * </span>\n * ```\n */\n@Directive({\n selector: '[forAvatar]',\n exportAs: 'forAvatar',\n host: {\n '[attr.data-status]': 'status()',\n },\n providers: [{ provide: FOR_AVATAR_CONTEXT, useExisting: ForAvatar }],\n})\nexport class ForAvatar implements ForAvatarContext {\n readonly #defaults = inject(FOR_AVATAR_DEFAULTS);\n\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` (default) to show immediately\n * during `idle` / `loading`, or to e.g. `500` to skip rendering during\n * quick cached loads. `error` always shows the fallback immediately.\n * The default is read from `provideForAvatarDefaults` for the\n * surrounding scope.\n */\n readonly fallbackDelayMs = input(this.#defaults.fallbackDelayMs, {\n transform: (value: unknown) => {\n const parsed = numberAttribute(value);\n return Number.isFinite(parsed) ? parsed : this.#defaults.fallbackDelayMs;\n },\n });\n\n readonly #status = signal<ForAvatarStatus>('idle');\n readonly status = this.#status.asReadonly();\n\n // Whether the deferred-fallback timer has fired for the current status/delay.\n // `linkedSignal` resets it to `false` whenever the source changes (the\n // canonical replacement for resetting state inside an effect); the timer\n // callback in the constructor flips it to `true`.\n readonly #timerElapsed = linkedSignal<{ status: ForAvatarStatus; delay: number }, boolean>({\n source: () => ({ status: this.#status(), delay: this.fallbackDelayMs() }),\n computation: () => false,\n });\n #timer: ReturnType<typeof setTimeout> | null = null;\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n readonly shouldShowFallback = computed(() => {\n const status = this.#status();\n if (status === 'loaded') return false;\n if (status === 'error') return true;\n // idle | loading: an immediate (delay <= 0) gate shows the fallback right\n // away; a deferred gate waits for the timer armed in the effect below.\n if (this.fallbackDelayMs() <= 0) return true;\n return this.#timerElapsed();\n });\n\n private reportStatus(status: ForAvatarStatus): void {\n this.#status.set(status);\n }\n\n constructor() {\n // Imperative timer integration: the effect's only job is to (re)arm a\n // setTimeout for the deferred-fallback case (idle | loading with a positive\n // delay). The synchronous branches (loaded / error / delay <= 0) are pure\n // derivations and live in `shouldShowFallback`. The signal write happens\n // inside the timer callback, which runs outside the effect's reactive\n // scope, so this does NOT create a self-cycle in the reactive graph.\n effect(() => {\n const status = this.#status();\n const delay = this.fallbackDelayMs();\n this.#cancelTimer();\n if (this.#isBrowser && (status === 'idle' || status === 'loading') && delay > 0) {\n this.#timer = setTimeout(() => {\n this.#timer = null;\n this.#timerElapsed.set(true);\n }, delay);\n }\n });\n\n inject(DestroyRef).onDestroy(() => this.#cancelTimer());\n }\n\n #cancelTimer(): void {\n if (this.#timer !== null) {\n clearTimeout(this.#timer);\n this.#timer = null;\n }\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n DestroyRef,\n Directive,\n ElementRef,\n inject,\n output,\n PLATFORM_ID,\n} from '@angular/core';\n\nimport { type ForAvatarStatus, injectAvatarImageContext } from './avatar-context';\n\n/**\n * Wraps an `<img>` and reports its load lifecycle to the surrounding\n * `[forAvatar]`. The native `src` / `alt` bindings are owned by the\n * consumer — this directive only observes.\n *\n * Reflects `data-status` on the image element so the consumer can hide it\n * via CSS while loading or after an error (e.g. `img[data-status=\"error\"]\n * { display: none }`).\n */\n@Directive({\n selector: 'img[forAvatarImage]',\n exportAs: 'forAvatarImage',\n host: {\n '[attr.data-status]': 'status()',\n '(load)': 'onLoad()',\n '(error)': 'onError()',\n },\n})\nexport class ForAvatarImage {\n readonly #host = inject<ElementRef<HTMLImageElement>>(ElementRef).nativeElement;\n readonly #parent = injectAvatarImageContext();\n\n /**\n * Emits whenever the image lifecycle transitions to a new state. Useful\n * if the consumer wants to log analytics, swap to a different src on\n * error, etc.\n */\n readonly loadStatusChange = output<ForAvatarStatus>();\n\n constructor() {\n const parent = this.#parent;\n const host = this.#host;\n\n let destroyed = false;\n inject(DestroyRef).onDestroy(() => (destroyed = true));\n\n afterNextRender(() => {\n this.#syncFromAttr();\n });\n\n if (isPlatformBrowser(inject(PLATFORM_ID)) && typeof MutationObserver !== 'undefined') {\n const observer = new MutationObserver(() => {\n this.#syncFromAttr();\n });\n observer.observe(host, { attributes: true, attributeFilter: ['src'] });\n inject(DestroyRef).onDestroy(() => observer.disconnect());\n }\n\n // Re-emit upstream whenever the parent's status changes due to our writes.\n // De-dupe on the (request, status) pair rather than status alone: a new\n // `src` opens a fresh request token, so an identical status (e.g. a second\n // image that also `loaded`) still re-reports for the new request, while a\n // repeated status within the same request is suppressed.\n let last: ForAvatarStatus | null = null;\n let lastReportedToken = -1;\n const emit = (status: ForAvatarStatus): void => {\n if (destroyed) return;\n if (last === status && lastReportedToken === this.#requestToken) return;\n last = status;\n lastReportedToken = this.#requestToken;\n parent.reportStatus(status);\n this.loadStatusChange.emit(status);\n };\n this.#emit = emit;\n }\n\n protected status(): ForAvatarStatus {\n return this.#parent.status();\n }\n\n protected onLoad(): void {\n // Ignore a `load` that fires for a request the host has already moved past\n // (the native event can arrive after `src` was reassigned).\n if (!this.#isCurrentRequest()) return;\n if (!this.#host.complete) return;\n this.#emit('loaded');\n }\n\n protected onError(): void {\n if (!this.#isCurrentRequest()) return;\n if (!this.#host.complete) return;\n this.#emit('error');\n }\n\n /**\n * Cheap string-identity fast-path: true when the `src` the host currently\n * carries is the one the latest `#syncFromAttr` observed, so a native\n * load/error event has not drifted away from the in-flight request. This\n * proves src-attribute stability only; request *completeness* (a late event\n * from a superseded resource that happens to share the current src) is\n * verified separately in onLoad/onError via `this.#host.complete`.\n */\n #isCurrentRequest(): boolean {\n return this.#host.getAttribute('src') === this.#requestSrc;\n }\n\n #syncFromAttr(): void {\n const src = this.#host.getAttribute('src');\n this.#requestToken++;\n this.#requestSrc = src;\n if (!src) {\n this.#emit('idle');\n return;\n }\n if (this.#host.complete) {\n // Cached image: load/error events may not fire — derive from naturalWidth.\n if (this.#host.naturalWidth > 0) {\n this.#emit('loaded');\n return;\n }\n // A complete image with zero intrinsic width is ambiguous: it may be a\n // broken image OR a valid SVG with no intrinsic dimensions. Don't\n // pessimistically flag `error` and hide a valid image — verify with\n // `decode()`, which resolves for a decodable (even zero-size) image and\n // rejects for a broken one. Stay `loading` until it settles.\n this.#emit('loading');\n this.#verifyCachedZeroSize(src);\n return;\n }\n this.#emit('loading');\n }\n\n #verifyCachedZeroSize(src: string): void {\n const decode = this.#host.decode?.bind(this.#host);\n if (!decode) {\n // No `decode()` (older engines / jsdom): keep the legacy heuristic.\n this.#emit('error');\n return;\n }\n decode().then(\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('loaded');\n }\n },\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('error');\n }\n },\n );\n }\n\n // Initialized in constructor.\n #emit!: (status: ForAvatarStatus) => void;\n\n /** Monotonic id bumped on every `src` change so stale events are ignored. */\n #requestToken = 0;\n /** The `src` the latest `#syncFromAttr` observed, used to match native events. */\n #requestSrc: string | null = null;\n}\n","import { Directive } from '@angular/core';\n\nimport { injectAvatarContext } from './avatar-context';\n\n/**\n * Marker for the avatar's fallback content (initials, generic icon, …).\n * Reflects `data-status` from the parent `[forAvatar]` so the consumer can\n * style for the `error` state separately from `idle` / `loading` if needed.\n *\n * The decision of *whether* to render the fallback lives on the root —\n * drive `@if` with `forAvatar.shouldShowFallback()`.\n */\n@Directive({\n selector: '[forAvatarFallback]',\n exportAs: 'forAvatarFallback',\n host: {\n '[attr.data-status]': 'parent.status()',\n },\n})\nexport class ForAvatarFallback {\n protected readonly parent = injectAvatarContext('ForAvatarFallback');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"forty-cdk-avatar.mjs","sources":["../../../projects/forty-cdk/avatar/src/avatar-context.ts","../../../projects/forty-cdk/avatar/src/avatar.ts","../../../projects/forty-cdk/avatar/src/avatar-image.ts","../../../projects/forty-cdk/avatar/src/avatar-fallback.ts","../../../projects/forty-cdk/avatar/src/forty-cdk-avatar.ts"],"sourcesContent":["import { inject, InjectionToken, type Signal } from '@angular/core';\n\nimport { assertRootContext, orphanContextError } from 'forty-cdk/core';\n\n/**\n * Lifecycle stages of the avatar's image:\n * - `idle`: no `src` set yet.\n * - `loading`: `src` set, waiting for the network.\n * - `loaded`: image rendered successfully.\n * - `error`: image failed to load.\n */\nexport type ForAvatarStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\n/** Read surface `[forAvatar]` publishes through {@link FOR_AVATAR_CONTEXT}. */\nexport interface ForAvatarContext {\n /** Current status of the avatar's image. */\n readonly status: Signal<ForAvatarStatus>;\n /**\n * `true` when the consumer should render the fallback. Drives `@if` in\n * the consumer's template. Honors `fallbackDelayMs` for `idle` / `loading`\n * states; `error` flips it to `true` immediately.\n */\n readonly shouldShowFallback: Signal<boolean>;\n}\n\n/**\n * The avatar's piece-coordination surface: the one call `[forAvatarImage]`\n * makes so the root can transition its state machine.\n *\n * **Not** part of {@link ForAvatarContext} and never exported from\n * `public-api.ts` — a consumer drives the status by binding `[src]`, never by\n * reporting a transition into the root.\n */\nexport interface AvatarPieceContext {\n /** Reported by `[forAvatarImage]` so the root can transition its state machine. */\n reportStatus(status: ForAvatarStatus): void;\n}\n\n/**\n * The avatar's internal coordination surface: everything\n * {@link ForAvatarContext} publishes plus the {@link AvatarPieceContext} call.\n *\n * Never exported from `public-api.ts`. It is the type the pieces read\n * {@link FOR_AVATAR_CONTEXT} at, so a consumer who injects that token gets the\n * read surface while `[forAvatarImage]` gets the transition channel. `ForAvatar`\n * declares `reportStatus` TS-`private`, which keeps it out of the emitted\n * `.d.ts` while `useExisting` still satisfies this contract at runtime.\n */\nexport interface AvatarContext extends ForAvatarContext, AvatarPieceContext {}\n\n/**\n * DI token for the avatar's coordination surface, provided by `[forAvatar]`.\n *\n * Publicly typed as the read surface {@link ForAvatarContext}, which is the whole of\n * what the token promises a consumer. `[forAvatarImage]` reads the same token at an\n * internal type that adds the status-report channel, so a wrapper re-providing it must\n * alias it to the root: `{ provide: FOR_AVATAR_CONTEXT, useExisting: MyAvatar }`, where\n * `MyAvatar` extends `ForAvatar`. A value that merely satisfies the declared type\n * resolves too, and is rejected in dev mode by the first piece to reach the channel.\n */\nexport const FOR_AVATAR_CONTEXT = new InjectionToken<ForAvatarContext>('FOR_AVATAR_CONTEXT');\n\n/**\n * The constant half of both resolvers' {@link assertRootContext} calls, so the\n * fallback and the image state the same requirement.\n */\nconst ROOT_ASSERTION = {\n entryPoint: 'avatar',\n token: 'FOR_AVATAR_CONTEXT',\n root: '[forAvatar]',\n};\n\n/**\n * Injects the nearest {@link ForAvatarContext}, throwing a descriptive error\n * when used outside a `[forAvatar]` element.\n *\n * @param piece Name of the calling directive, used in the error message.\n */\nexport function injectAvatarContext(piece: string): AvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw orphanContextError({\n code: 'FORCDK-AVATAR-001',\n piece,\n root: '[forAvatar]',\n token: 'FOR_AVATAR_CONTEXT',\n });\n }\n const widened = ctx as AvatarContext;\n assertRootContext({\n ...ROOT_ASSERTION,\n piece,\n probe: () => widened.reportStatus,\n });\n return widened;\n}\n\n/**\n * `[forAvatarImage]`'s own resolver. It reports a distinct failure from the rest\n * of the anatomy — the image is the piece that drives the state machine, so an\n * orphaned one silently never transitions — and therefore carries its own code\n * rather than routing through {@link injectAvatarContext}.\n */\nexport function injectAvatarImageContext(): AvatarContext {\n const ctx = inject(FOR_AVATAR_CONTEXT, { optional: true });\n if (!ctx) {\n throw orphanContextError({\n code: 'FORCDK-AVATAR-002',\n piece: 'ForAvatarImage',\n root: '[forAvatar]',\n token: 'FOR_AVATAR_CONTEXT',\n });\n }\n const widened = ctx as AvatarContext;\n assertRootContext({\n ...ROOT_ASSERTION,\n piece: 'ForAvatarImage',\n probe: () => widened.reportStatus,\n });\n return widened;\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n Directive,\n effect,\n inject,\n input,\n linkedSignal,\n numberAttribute,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\n\nimport { FOR_AVATAR_CONTEXT, type ForAvatarContext, type ForAvatarStatus } from './avatar-context';\nimport { FOR_AVATAR_DEFAULTS } from 'forty-cdk/defaults';\n\n/**\n * Headless avatar root. Tracks the load lifecycle of an inner\n * `[forAvatarImage]` and exposes `status` + `shouldShowFallback` so the\n * consumer can swap the image for fallback content (initials, generic icon)\n * via `@if`.\n *\n * No ARIA role is imposed — avatars are typically presentational. If the\n * surrounding component conveys identity (a person's name in the header,\n * a chat bubble), the consumer should provide an `aria-label` on the\n * relevant ancestor or pair the avatar with visible text.\n *\n * @example\n * ```html\n * <span forAvatar #a=\"forAvatar\" fallbackDelayMs=\"500\">\n * <img forAvatarImage [src]=\"user.avatarUrl\" [alt]=\"user.name\" />\n * @if (a.shouldShowFallback()) {\n * <span forAvatarFallback>{{ user.initials }}</span>\n * }\n * </span>\n * ```\n */\n@Directive({\n selector: '[forAvatar]',\n exportAs: 'forAvatar',\n host: {\n '[attr.data-status]': 'status()',\n },\n providers: [{ provide: FOR_AVATAR_CONTEXT, useExisting: ForAvatar }],\n})\nexport class ForAvatar implements ForAvatarContext {\n readonly #defaults = inject(FOR_AVATAR_DEFAULTS);\n\n /**\n * Milliseconds to defer the fallback for fast loads, avoiding a brief\n * \"initials → image\" flicker. Set to `0` (default) to show immediately\n * during `idle` / `loading`, or to e.g. `500` to skip rendering during\n * quick cached loads. `error` always shows the fallback immediately.\n * The default is read from `provideForAvatarDefaults` for the\n * surrounding scope.\n */\n readonly fallbackDelayMs = input(this.#defaults.fallbackDelayMs, {\n transform: (value: unknown) => {\n const parsed = numberAttribute(value);\n return Number.isFinite(parsed) ? parsed : this.#defaults.fallbackDelayMs;\n },\n });\n\n readonly #status = signal<ForAvatarStatus>('idle');\n readonly status = this.#status.asReadonly();\n\n // Whether the deferred-fallback timer has fired for the current status/delay.\n // `linkedSignal` resets it to `false` whenever the source changes (the\n // canonical replacement for resetting state inside an effect); the timer\n // callback in the constructor flips it to `true`.\n readonly #timerElapsed = linkedSignal<{ status: ForAvatarStatus; delay: number }, boolean>({\n source: () => ({ status: this.#status(), delay: this.fallbackDelayMs() }),\n computation: () => false,\n });\n #timer: ReturnType<typeof setTimeout> | null = null;\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n readonly shouldShowFallback = computed(() => {\n const status = this.#status();\n if (status === 'loaded') return false;\n if (status === 'error') return true;\n // idle | loading: an immediate (delay <= 0) gate shows the fallback right\n // away; a deferred gate waits for the timer armed in the effect below.\n if (this.fallbackDelayMs() <= 0) return true;\n return this.#timerElapsed();\n });\n\n private reportStatus(status: ForAvatarStatus): void {\n this.#status.set(status);\n }\n\n constructor() {\n // Imperative timer integration: the effect's only job is to (re)arm a\n // setTimeout for the deferred-fallback case (idle | loading with a positive\n // delay). The synchronous branches (loaded / error / delay <= 0) are pure\n // derivations and live in `shouldShowFallback`. The signal write happens\n // inside the timer callback, which runs outside the effect's reactive\n // scope, so this does NOT create a self-cycle in the reactive graph.\n effect(() => {\n const status = this.#status();\n const delay = this.fallbackDelayMs();\n this.#cancelTimer();\n if (this.#isBrowser && (status === 'idle' || status === 'loading') && delay > 0) {\n this.#timer = setTimeout(() => {\n this.#timer = null;\n this.#timerElapsed.set(true);\n }, delay);\n }\n });\n\n inject(DestroyRef).onDestroy(() => this.#cancelTimer());\n }\n\n #cancelTimer(): void {\n if (this.#timer !== null) {\n clearTimeout(this.#timer);\n this.#timer = null;\n }\n }\n}\n","import { afterNextRender, DestroyRef, Directive, ElementRef, inject, output } from '@angular/core';\n\nimport { type ForAvatarStatus, injectAvatarImageContext } from './avatar-context';\n\n/**\n * Wraps an `<img>` and reports its load lifecycle to the surrounding\n * `[forAvatar]`. The native `src` / `alt` bindings are owned by the\n * consumer — this directive only observes.\n *\n * Reflects `data-status` on the image element so the consumer can hide it\n * via CSS while loading or after an error (e.g. `img[data-status=\"error\"]\n * { display: none }`).\n */\n@Directive({\n selector: 'img[forAvatarImage]',\n exportAs: 'forAvatarImage',\n host: {\n '[attr.data-status]': 'status()',\n '(load)': 'onLoad()',\n '(error)': 'onError()',\n },\n})\nexport class ForAvatarImage {\n readonly #host = inject<ElementRef<HTMLImageElement>>(ElementRef).nativeElement;\n readonly #parent = injectAvatarImageContext();\n\n /**\n * Emits whenever the image lifecycle transitions to a new state. Useful\n * if the consumer wants to log analytics, swap to a different src on\n * error, etc.\n */\n readonly loadStatusChange = output<ForAvatarStatus>();\n\n constructor() {\n const parent = this.#parent;\n const host = this.#host;\n\n let destroyed = false;\n const destroyRef = inject(DestroyRef);\n destroyRef.onDestroy(() => (destroyed = true));\n\n afterNextRender(() => {\n this.#syncFromAttr();\n if (destroyed || typeof MutationObserver === 'undefined') return;\n const observer = new MutationObserver(() => {\n this.#syncFromAttr();\n });\n observer.observe(host, { attributes: true, attributeFilter: ['src'] });\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n // Re-emit upstream whenever the parent's status changes due to our writes.\n // De-dupe on the (request, status) pair rather than status alone: a new\n // `src` opens a fresh request token, so an identical status (e.g. a second\n // image that also `loaded`) still re-reports for the new request, while a\n // repeated status within the same request is suppressed.\n let last: ForAvatarStatus | null = null;\n let lastReportedToken = -1;\n const emit = (status: ForAvatarStatus): void => {\n if (destroyed) return;\n if (last === status && lastReportedToken === this.#requestToken) return;\n last = status;\n lastReportedToken = this.#requestToken;\n parent.reportStatus(status);\n this.loadStatusChange.emit(status);\n };\n this.#emit = emit;\n }\n\n protected status(): ForAvatarStatus {\n return this.#parent.status();\n }\n\n protected onLoad(): void {\n // Ignore a `load` that fires for a request the host has already moved past\n // (the native event can arrive after `src` was reassigned).\n if (!this.#isCurrentRequest()) return;\n if (!this.#host.complete) return;\n this.#emit('loaded');\n }\n\n protected onError(): void {\n if (!this.#isCurrentRequest()) return;\n if (!this.#host.complete) return;\n this.#emit('error');\n }\n\n /**\n * Cheap string-identity fast-path: true when the `src` the host currently\n * carries is the one the latest `#syncFromAttr` observed, so a native\n * load/error event has not drifted away from the in-flight request. This\n * proves src-attribute stability only; request *completeness* (a late event\n * from a superseded resource that happens to share the current src) is\n * verified separately in onLoad/onError via `this.#host.complete`.\n */\n #isCurrentRequest(): boolean {\n return this.#host.getAttribute('src') === this.#requestSrc;\n }\n\n #syncFromAttr(): void {\n const src = this.#host.getAttribute('src');\n this.#requestToken++;\n this.#requestSrc = src;\n if (!src) {\n this.#emit('idle');\n return;\n }\n if (this.#host.complete) {\n // Cached image: load/error events may not fire — derive from naturalWidth.\n if (this.#host.naturalWidth > 0) {\n this.#emit('loaded');\n return;\n }\n // A complete image with zero intrinsic width is ambiguous: it may be a\n // broken image OR a valid SVG with no intrinsic dimensions. Don't\n // pessimistically flag `error` and hide a valid image — verify with\n // `decode()`, which resolves for a decodable (even zero-size) image and\n // rejects for a broken one. Stay `loading` until it settles.\n this.#emit('loading');\n this.#verifyCachedZeroSize(src);\n return;\n }\n this.#emit('loading');\n }\n\n #verifyCachedZeroSize(src: string): void {\n const decode = this.#host.decode?.bind(this.#host);\n if (!decode) {\n // No `decode()` (older engines / jsdom): keep the legacy heuristic.\n this.#emit('error');\n return;\n }\n decode().then(\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('loaded');\n }\n },\n () => {\n if (this.#host.getAttribute('src') === src) {\n this.#emit('error');\n }\n },\n );\n }\n\n // Initialized in constructor.\n #emit!: (status: ForAvatarStatus) => void;\n\n /** Monotonic id bumped on every `src` change so stale events are ignored. */\n #requestToken = 0;\n /** The `src` the latest `#syncFromAttr` observed, used to match native events. */\n #requestSrc: string | null = null;\n}\n","import { Directive } from '@angular/core';\n\nimport { injectAvatarContext } from './avatar-context';\n\n/**\n * Marker for the avatar's fallback content (initials, generic icon, …).\n * Reflects `data-status` from the parent `[forAvatar]` so the consumer can\n * style for the `error` state separately from `idle` / `loading` if needed.\n *\n * The decision of *whether* to render the fallback lives on the root —\n * drive `@if` with `forAvatar.shouldShowFallback()`.\n */\n@Directive({\n selector: '[forAvatarFallback]',\n exportAs: 'forAvatarFallback',\n host: {\n '[attr.data-status]': 'parent.status()',\n },\n})\nexport class ForAvatarFallback {\n protected readonly parent = injectAvatarContext('ForAvatarFallback');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forty-cdk-breakpoints.mjs","sources":["../../../projects/forty-cdk/breakpoints/src/breakpoints.ts","../../../projects/forty-cdk/breakpoints/src/forty-cdk-breakpoints.ts"],"sourcesContent":["import { computed, inject, type Signal } from '@angular/core';\n\nimport { MediaQueryRegistry, fortyError } from 'forty-cdk/core';\nimport { FOR_BREAKPOINTS_DEFAULTS, type TailwindBreakpointName } from 'forty-cdk/defaults';\n\n/**\n * Extension point for typing custom breakpoint names. Augment it via module\n * declaration merging so `injectBreakpoints` autocompletes your own names\n * instead of the default Tailwind scale:\n *\n * ```ts\n * import type { appBreakpoints } from './breakpoints';\n *\n * declare module 'forty-cdk' {\n * interface BreakpointRegistry extends Record<keyof typeof appBreakpoints, true> {}\n * }\n * ```\n *\n * When left empty (no augmentation), {@link BreakpointName} falls back to the\n * keys of {@link forBreakpointsTailwind}.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface BreakpointRegistry {}\n\n/**\n * The set of valid breakpoint names. Resolves to the augmented\n * {@link BreakpointRegistry} keys when present, otherwise the Tailwind scale\n * (`'sm' | 'md' | 'lg' | 'xl' | '2xl'`).\n */\nexport type BreakpointName = [keyof BreakpointRegistry] extends [never]\n ? TailwindBreakpointName\n : keyof BreakpointRegistry & string;\n\n/** Reactive handle returned by {@link injectBreakpoints}. */\nexport interface ForBreakpoints<K extends string = BreakpointName> {\n /**\n * Matches the named breakpoint and wider — `(min-width: <threshold>px)`.\n * @param name A breakpoint declared in the active map.\n */\n up(name: K): Signal<boolean>;\n /**\n * Matches narrower than the named breakpoint —\n * `(max-width: <threshold - 0.02>px)`. The 0.02px back-off keeps `up(name)`\n * and `down(name)` from both matching at the exact threshold.\n * @param name A breakpoint declared in the active map.\n */\n down(name: K): Signal<boolean>;\n /**\n * Matches from `min` (inclusive) up to but not including `max`.\n * @param min Lower breakpoint, inclusive.\n * @param max Upper breakpoint, exclusive.\n */\n between(min: K, max: K): Signal<boolean>;\n /**\n * Matches only the named breakpoint's own band — from its threshold up to\n * but not including the next-larger breakpoint (open-ended for the largest).\n * @param name A breakpoint declared in the active map.\n */\n only(name: K): Signal<boolean>;\n /**\n * The largest breakpoint whose `min-width` currently matches, or `null` when\n * the viewport is narrower than the smallest breakpoint (and on the server).\n */\n readonly active: Signal<K | null>;\n /**\n * Escape hatch for an arbitrary media query string (orientation, pointer,\n * `prefers-color-scheme`, …) that the named helpers don't cover.\n * @param query Any valid media query.\n */\n matches(query: string): Signal<boolean>;\n}\n\n/**\n * A signal-first, zoneless, SSR-safe viewport breakpoint observer. Reads the\n * breakpoint map from the ambient {@link FOR_BREAKPOINTS_DEFAULTS} token\n * (configured with `provideForBreakpointsDefaults`, or the Tailwind fallback),\n * so call sites never repeat the breakpoint set:\n *\n * ```ts\n * private bp = injectBreakpoints();\n * protected isDesktop = this.bp.up('desktop');\n * protected active = this.bp.active;\n * ```\n *\n * Each query method returns a `Signal<boolean>` backed by a `MediaQueryList`\n * shared across the application: any number of calls reading the same query\n * open it once, and its listener lives as long as the application. The\n * methods need no injection context, so they can be invoked lazily from\n * `computed()` or a template — not only during construction. `active`'s\n * per-breakpoint queries are materialized eagerly at call time, so reading\n * `active` never attaches a listener from inside a reactive computation. On\n * the server (or where `matchMedia` is unavailable) every signal reads\n * `false` and `active` reads `null`.\n *\n * Must be called from an injection context.\n *\n * @returns A {@link ForBreakpoints} handle of reactive query methods.\n */\nexport function injectBreakpoints(): ForBreakpoints {\n const registry = inject(MediaQueryRegistry);\n const map = inject(FOR_BREAKPOINTS_DEFAULTS).breakpoints;\n const names = Object.keys(map).sort((a, b) => map[a]! - map[b]!);\n\n const observe = (query: string): Signal<boolean> => registry.observe(query);\n\n const thresholdOf = (name: string): number => {\n const value = map[name];\n if (value === undefined) {\n throw fortyError({\n code: 'FORCDK-BREAKPOINTS-001',\n message: `Unknown breakpoint \"${name}\".`,\n cause: `The active breakpoint map defines: ${names.join(', ') || '(none)'}.`,\n fix: 'Use one of the names above, or register yours with provideForBreakpointsDefaults().',\n });\n }\n return value;\n };\n\n const up = (name: string): Signal<boolean> => observe(`(min-width: ${thresholdOf(name)}px)`);\n\n const down = (name: string): Signal<boolean> =>\n observe(`(max-width: ${thresholdOf(name) - 0.02}px)`);\n\n const between = (min: string, max: string): Signal<boolean> =>\n observe(`(min-width: ${thresholdOf(min)}px) and (max-width: ${thresholdOf(max) - 0.02}px)`);\n\n const only = (name: string): Signal<boolean> => {\n const lower = thresholdOf(name);\n const index = names.indexOf(name);\n const next = index < names.length - 1 ? thresholdOf(names[index + 1]!) : null;\n return next === null\n ? observe(`(min-width: ${lower}px)`)\n : observe(`(min-width: ${lower}px) and (max-width: ${next - 0.02}px)`);\n };\n\n const activeCandidates = names.map((name) => ({\n name,\n matches: observe(`(min-width: ${map[name]}px)`),\n }));\n\n const active = computed<BreakpointName | null>(() => {\n let current: string | null = null;\n for (const candidate of activeCandidates) {\n if (candidate.matches()) {\n current = candidate.name;\n }\n }\n return current as BreakpointName | null;\n });\n\n return { up, down, between, only, active, matches: (query) => observe(query) };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;AAwEA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;SACa,iBAAiB,GAAA;AAC/B,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,kBAAkB,CAAC;IAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,wBAAwB,CAAC,CAAC,WAAW;AACxD,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAE,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC;AAEhE,IAAA,MAAM,OAAO,GAAG,CAAC,KAAa,KAAsB,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC;AAE3E,IAAA,MAAM,WAAW,GAAG,CAAC,IAAY,KAAY;AAC3C,QAAA,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC;AACvB,QAAA,IAAI,KAAK,KAAK,SAAS,EAAE;AACvB,YAAA,MAAM,UAAU,CAAC;AACf,gBAAA,IAAI,EAAE,wBAAwB;gBAC9B,OAAO,EAAE,CAAA,oBAAA,EAAuB,IAAI,CAAA,EAAA,CAAI;gBACxC,KAAK,EAAE,CAAA,mCAAA,EAAsC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAA,CAAA,CAAG;AAC5E,gBAAA,GAAG,EAAE,qFAAqF;AAC3F,aAAA,CAAC;QACJ;AACA,QAAA,OAAO,KAAK;AACd,IAAA,CAAC;AAED,IAAA,MAAM,EAAE,GAAG,CAAC,IAAY,KAAsB,OAAO,CAAC,CAAA,YAAA,EAAe,WAAW,CAAC,IAAI,CAAC,CAAA,GAAA,CAAK,CAAC;AAE5F,IAAA,MAAM,IAAI,GAAG,CAAC,IAAY,KACxB,OAAO,CAAC,CAAA,YAAA,EAAe,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA,GAAA,CAAK,CAAC;IAEvD,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,GAAW,KACvC,OAAO,CAAC,CAAA,YAAA,EAAe,WAAW,CAAC,GAAG,CAAC,CAAA,oBAAA,EAAuB,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA,GAAA,CAAK,CAAC;AAE7F,IAAA,MAAM,IAAI,GAAG,CAAC,IAAY,KAAqB;AAC7C,QAAA,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC;QAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QACjC,MAAM,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAE,CAAC,GAAG,IAAI;QAC7E,OAAO,IAAI,KAAK;AACd,cAAE,OAAO,CAAC,CAAA,YAAA,EAAe,KAAK,KAAK;cACjC,OAAO,CAAC,CAAA,YAAA,EAAe,KAAK,CAAA,oBAAA,EAAuB,IAAI,GAAG,IAAI,CAAA,GAAA,CAAK,CAAC;AAC1E,IAAA,CAAC;IAED,MAAM,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM;QAC5C,IAAI;QACJ,OAAO,EAAE,OAAO,CAAC,CAAA,YAAA,EAAe,GAAG,CAAC,IAAI,CAAC,CAAA,GAAA,CAAK,CAAC;AAChD,KAAA,CAAC,CAAC;AAEH,IAAA,MAAM,MAAM,GAAG,QAAQ,CAAwB,MAAK;QAClD,IAAI,OAAO,GAAkB,IAAI;AACjC,QAAA,KAAK,MAAM,SAAS,IAAI,gBAAgB,EAAE;AACxC,YAAA,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE;AACvB,gBAAA,OAAO,GAAG,SAAS,CAAC,IAAI;YAC1B;QACF;AACA,QAAA,OAAO,OAAgC;IACzC,CAAC;+EAAC;IAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,EAAE;AAChF;;ACvJA;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"forty-cdk-breakpoints.mjs","sources":["../../../projects/forty-cdk/breakpoints/src/breakpoints.ts","../../../projects/forty-cdk/breakpoints/src/forty-cdk-breakpoints.ts"],"sourcesContent":["import { computed, inject, type Signal } from '@angular/core';\n\nimport { MediaQueryRegistry, fortyError } from 'forty-cdk/core';\nimport { FOR_BREAKPOINTS_DEFAULTS, type TailwindBreakpointName } from 'forty-cdk/defaults';\n\n/**\n * Extension point for typing custom breakpoint names. Augment it via module\n * declaration merging so `injectBreakpoints` autocompletes your own names\n * instead of the default Tailwind scale:\n *\n * ```ts\n * import type { appBreakpoints } from './breakpoints';\n *\n * declare module 'forty-cdk/breakpoints' {\n * interface BreakpointRegistry extends Record<keyof typeof appBreakpoints, true> {}\n * }\n * ```\n *\n * When left empty (no augmentation), {@link BreakpointName} falls back to the\n * keys of {@link forBreakpointsTailwind}.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface BreakpointRegistry {}\n\n/**\n * The set of valid breakpoint names. Resolves to the augmented\n * {@link BreakpointRegistry} keys when present, otherwise the Tailwind scale\n * (`'sm' | 'md' | 'lg' | 'xl' | '2xl'`).\n */\nexport type BreakpointName = [keyof BreakpointRegistry] extends [never]\n ? TailwindBreakpointName\n : keyof BreakpointRegistry & string;\n\n/** Reactive handle returned by {@link injectBreakpoints}. */\nexport interface ForBreakpoints<K extends string = BreakpointName> {\n /**\n * Matches the named breakpoint and wider — `(min-width: <threshold>px)`.\n * @param name A breakpoint declared in the active map.\n */\n up(name: K): Signal<boolean>;\n /**\n * Matches narrower than the named breakpoint —\n * `(max-width: <threshold - 0.02>px)`. The 0.02px back-off keeps `up(name)`\n * and `down(name)` from both matching at the exact threshold.\n * @param name A breakpoint declared in the active map.\n */\n down(name: K): Signal<boolean>;\n /**\n * Matches from `min` (inclusive) up to but not including `max`.\n * @param min Lower breakpoint, inclusive.\n * @param max Upper breakpoint, exclusive.\n */\n between(min: K, max: K): Signal<boolean>;\n /**\n * Matches only the named breakpoint's own band — from its threshold up to\n * but not including the next-larger breakpoint (open-ended for the largest).\n * @param name A breakpoint declared in the active map.\n */\n only(name: K): Signal<boolean>;\n /**\n * The largest breakpoint whose `min-width` currently matches, or `null` when\n * the viewport is narrower than the smallest breakpoint (and on the server).\n */\n readonly active: Signal<K | null>;\n /**\n * Escape hatch for an arbitrary media query string (orientation, pointer,\n * `prefers-color-scheme`, …) that the named helpers don't cover.\n * @param query Any valid media query.\n */\n matches(query: string): Signal<boolean>;\n}\n\n/**\n * A signal-first, zoneless, SSR-safe viewport breakpoint observer. Reads the\n * breakpoint map from the ambient {@link FOR_BREAKPOINTS_DEFAULTS} token\n * (configured with `provideForBreakpointsDefaults`, or the Tailwind fallback),\n * so call sites never repeat the breakpoint set:\n *\n * ```ts\n * private bp = injectBreakpoints();\n * protected isDesktop = this.bp.up('desktop');\n * protected active = this.bp.active;\n * ```\n *\n * Each query method returns a `Signal<boolean>` backed by a `MediaQueryList`\n * shared across the application: any number of calls reading the same query\n * open it once, and its listener lives as long as the application. The\n * methods need no injection context, so they can be invoked lazily from\n * `computed()` or a template — not only during construction. `active`'s\n * per-breakpoint queries are materialized eagerly at call time, so reading\n * `active` never attaches a listener from inside a reactive computation. On\n * the server (or where `matchMedia` is unavailable) every signal reads\n * `false` and `active` reads `null`.\n *\n * Must be called from an injection context.\n *\n * @returns A {@link ForBreakpoints} handle of reactive query methods.\n */\nexport function injectBreakpoints(): ForBreakpoints {\n const registry = inject(MediaQueryRegistry);\n const map = inject(FOR_BREAKPOINTS_DEFAULTS).breakpoints;\n const names = Object.keys(map).sort((a, b) => map[a]! - map[b]!);\n\n const observe = (query: string): Signal<boolean> => registry.observe(query);\n\n const thresholdOf = (name: string): number => {\n const value = map[name];\n if (value === undefined) {\n throw fortyError({\n code: 'FORCDK-BREAKPOINTS-001',\n message: `Unknown breakpoint \"${name}\".`,\n cause: `The active breakpoint map defines: ${names.join(', ') || '(none)'}.`,\n fix: 'Use one of the names above, or register yours with provideForBreakpointsDefaults().',\n });\n }\n return value;\n };\n\n const up = (name: string): Signal<boolean> => observe(`(min-width: ${thresholdOf(name)}px)`);\n\n const down = (name: string): Signal<boolean> =>\n observe(`(max-width: ${thresholdOf(name) - 0.02}px)`);\n\n const between = (min: string, max: string): Signal<boolean> =>\n observe(`(min-width: ${thresholdOf(min)}px) and (max-width: ${thresholdOf(max) - 0.02}px)`);\n\n const only = (name: string): Signal<boolean> => {\n const lower = thresholdOf(name);\n const index = names.indexOf(name);\n const next = index < names.length - 1 ? thresholdOf(names[index + 1]!) : null;\n return next === null\n ? observe(`(min-width: ${lower}px)`)\n : observe(`(min-width: ${lower}px) and (max-width: ${next - 0.02}px)`);\n };\n\n const activeCandidates = names.map((name) => ({\n name,\n matches: observe(`(min-width: ${map[name]}px)`),\n }));\n\n const active = computed<BreakpointName | null>(() => {\n let current: string | null = null;\n for (const candidate of activeCandidates) {\n if (candidate.matches()) {\n current = candidate.name;\n }\n }\n return current as BreakpointName | null;\n });\n\n return { up, down, between, only, active, matches: (query) => observe(query) };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, model, input, booleanAttribute, numberAttribute, computed, DestroyRef, PLATFORM_ID, ElementRef, signal, effect, untracked, isDevMode, Directive } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser } from '@angular/common';
|
|
4
|
-
import { orphanContextError, assertRootContext, hostAriaLabel, resolveText, injectTextDirection, RovingTabindex, injectPrefersReducedMotion, injectPauseController, Collection, createSingleSlot, injectElementSize, firstEnabledHost, fortyWarn, moveIndex, hostId, registerHandle, hostButtonType, rovingTabStop, navigateFromRovingItem, resolveTextInput, attachSwipeDismiss, isScrollableAtEdge, FLICK_STALE_VELOCITY_MS, flickVelocity, FLICK_VELOCITY_PX_PER_MS } from 'forty-cdk/core';
|
|
4
|
+
import { orphanContextError, assertRootContext, hostAriaLabel, resolveText, injectTextDirection, RovingTabindex, injectPrefersReducedMotion, injectPauseController, Collection, createSingleSlot, injectElementSize, firstEnabledHost, clamp, fortyWarn, moveIndex, hostId, registerHandle, hostButtonType, rovingTabStop, navigateFromRovingItem, resolveTextInput, attachSwipeDismiss, isScrollableAtEdge, FLICK_STALE_VELOCITY_MS, flickVelocity, FLICK_VELOCITY_PX_PER_MS } from 'forty-cdk/core';
|
|
5
5
|
import { FOR_CAROUSEL_DEFAULTS } from 'forty-cdk/defaults';
|
|
6
6
|
export { FOR_CAROUSEL_DEFAULTS, provideForCarouselDefaults } from 'forty-cdk/defaults';
|
|
7
7
|
|
|
@@ -57,6 +57,8 @@ class ForCarousel {
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
|
|
61
|
+
|
|
60
62
|
activeIndex = model(0, /* @ts-ignore */
|
|
61
63
|
...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
62
64
|
|
|
@@ -157,23 +159,26 @@ class ForCarousel {
|
|
|
157
159
|
...(ngDevMode ? [{ debugName: "slideCount" }] : /* istanbul ignore next */ []));
|
|
158
160
|
#firstEnabledIndicatorHost = computed(() => firstEnabledHost(this.#indicators.items()), /* @ts-ignore */
|
|
159
161
|
...(ngDevMode ? [{ debugName: "#firstEnabledIndicatorHost" }] : /* istanbul ignore next */ []));
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const perView =
|
|
166
|
-
const slideSizePct = 100 / perView;
|
|
167
|
-
const base = -(this.activeIndex() * slideSizePct);
|
|
162
|
+
effectiveIndex = computed(() => clamp(this.activeIndex(), 0, Math.max(0, this.slideCount() - 1)), /* @ts-ignore */
|
|
163
|
+
...(ngDevMode ? [{ debugName: "effectiveIndex" }] : /* istanbul ignore next */ []));
|
|
164
|
+
#perView = computed(() => Math.max(1, this.slidesPerView()), /* @ts-ignore */
|
|
165
|
+
...(ngDevMode ? [{ debugName: "#perView" }] : /* istanbul ignore next */ []));
|
|
166
|
+
#viewStart = computed(() => {
|
|
167
|
+
const perView = this.#perView();
|
|
168
168
|
const align = this.align();
|
|
169
|
-
const
|
|
170
|
-
const
|
|
169
|
+
const lead = align === 'center' ? (perView - 1) / 2 : align === 'end' ? perView - 1 : 0;
|
|
170
|
+
const start = this.effectiveIndex() - lead;
|
|
171
171
|
if (this.containScroll() && !this.loop()) {
|
|
172
|
-
|
|
173
|
-
return `${Math.min(0, Math.max(minOffset, raw))}%`;
|
|
172
|
+
return clamp(start, 0, Math.max(0, this.slideCount() - perView));
|
|
174
173
|
}
|
|
175
|
-
return
|
|
174
|
+
return start;
|
|
176
175
|
}, /* @ts-ignore */
|
|
176
|
+
...(ngDevMode ? [{ debugName: "#viewStart" }] : /* istanbul ignore next */ []));
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
offset = computed(() => `${-(this.#viewStart() * (100 / this.#perView()))}%`, /* @ts-ignore */
|
|
177
182
|
...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
|
|
178
183
|
|
|
179
184
|
viewportWidth = computed(() => {
|
|
@@ -217,11 +222,11 @@ class ForCarousel {
|
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
canScrollPrev() {
|
|
220
|
-
return moveIndex(this.
|
|
225
|
+
return (moveIndex(this.effectiveIndex(), this.slideCount(), 'prev', { loop: this.loop() }) !== null);
|
|
221
226
|
}
|
|
222
227
|
|
|
223
228
|
canScrollNext() {
|
|
224
|
-
return moveIndex(this.
|
|
229
|
+
return (moveIndex(this.effectiveIndex(), this.slideCount(), 'next', { loop: this.loop() }) !== null);
|
|
225
230
|
}
|
|
226
231
|
|
|
227
232
|
scrollPrev() {
|
|
@@ -323,15 +328,15 @@ class ForCarousel {
|
|
|
323
328
|
}
|
|
324
329
|
|
|
325
330
|
isCurrent(index) {
|
|
326
|
-
return index === this.
|
|
331
|
+
return index === this.effectiveIndex();
|
|
327
332
|
}
|
|
328
333
|
|
|
329
334
|
|
|
330
335
|
|
|
331
336
|
|
|
332
337
|
isInView(index) {
|
|
333
|
-
const start = this
|
|
334
|
-
return index >= start && index < start +
|
|
338
|
+
const start = this.#viewStart();
|
|
339
|
+
return index >= 0 && index + 1 > start && index < start + this.#perView();
|
|
335
340
|
}
|
|
336
341
|
|
|
337
342
|
|
|
@@ -350,7 +355,7 @@ class ForCarousel {
|
|
|
350
355
|
|
|
351
356
|
|
|
352
357
|
hasCurrentIndicator() {
|
|
353
|
-
const active = this.
|
|
358
|
+
const active = this.effectiveIndex();
|
|
354
359
|
return this.#indicators.items().some((i, idx) => idx === active && !i.disabled());
|
|
355
360
|
}
|
|
356
361
|
|
|
@@ -387,7 +392,7 @@ class ForCarousel {
|
|
|
387
392
|
#advance() {
|
|
388
393
|
const count = this.slideCount();
|
|
389
394
|
if (count > 0) {
|
|
390
|
-
this.activeIndex.set((this.
|
|
395
|
+
this.activeIndex.set((this.effectiveIndex() + 1) % count);
|
|
391
396
|
}
|
|
392
397
|
}
|
|
393
398
|
#clearTimer() {
|
|
@@ -397,13 +402,15 @@ class ForCarousel {
|
|
|
397
402
|
}
|
|
398
403
|
}
|
|
399
404
|
#move(action) {
|
|
400
|
-
const next = moveIndex(this.
|
|
405
|
+
const next = moveIndex(this.effectiveIndex(), this.slideCount(), action, {
|
|
406
|
+
loop: this.loop(),
|
|
407
|
+
});
|
|
401
408
|
if (next !== null) {
|
|
402
409
|
this.activeIndex.set(next);
|
|
403
410
|
}
|
|
404
411
|
}
|
|
405
412
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForCarousel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
406
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForCarousel, isStandalone: true, selector: "[forCarousel]", inputs: { activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, slidesPerView: { classPropertyName: "slidesPerView", publicName: "slidesPerView", isSignal: true, isRequired: false, transformFunction: null }, containScroll: { classPropertyName: "containScroll", publicName: "containScroll", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: true, isRequired: false, transformFunction: null }, autoplayInterval: { classPropertyName: "autoplayInterval", publicName: "autoplayInterval", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeIndex: "activeIndexChange" }, host: { attributes: { "role": "group" }, listeners: { "pointerenter": "onAutoplayPause(\"hover\")", "pointerleave": "onAutoplayResume(\"hover\")", "focusin": "onAutoplayPause(\"focus\")", "focusout": "onAutoplayFocusOut($event)" }, properties: { "attr.aria-roledescription": "roleDescription()", "attr.aria-label": "resolvedAriaLabel()", "attr.data-orientation": "orientation()", "attr.data-align": "align()", "attr.dir": "dir()", "style.--for-carousel-offset": "offset()", "style.--for-carousel-active-index": "
|
|
413
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForCarousel, isStandalone: true, selector: "[forCarousel]", inputs: { activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, slidesPerView: { classPropertyName: "slidesPerView", publicName: "slidesPerView", isSignal: true, isRequired: false, transformFunction: null }, containScroll: { classPropertyName: "containScroll", publicName: "containScroll", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: true, isRequired: false, transformFunction: null }, autoplayInterval: { classPropertyName: "autoplayInterval", publicName: "autoplayInterval", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeIndex: "activeIndexChange" }, host: { attributes: { "role": "group" }, listeners: { "pointerenter": "onAutoplayPause(\"hover\")", "pointerleave": "onAutoplayResume(\"hover\")", "focusin": "onAutoplayPause(\"focus\")", "focusout": "onAutoplayFocusOut($event)" }, properties: { "attr.aria-roledescription": "roleDescription()", "attr.aria-label": "resolvedAriaLabel()", "attr.data-orientation": "orientation()", "attr.data-align": "align()", "attr.dir": "dir()", "style.--for-carousel-offset": "offset()", "style.--for-carousel-active-index": "effectiveIndex()", "style.--for-carousel-slide-count": "slideCount()", "style.--for-carousel-slides-per-view": "slidesPerView()", "style.--for-carousel-viewport-width": "viewportWidth()", "style.--for-carousel-viewport-height": "viewportHeight()", "attr.data-autoplay": "autoplay() ? \"\" : null", "attr.data-rotating": "rotating() ? \"\" : null" } }, providers: [{ provide: FOR_CAROUSEL_CONTEXT, useExisting: ForCarousel }], exportAs: ["forCarousel"], ngImport: i0 });
|
|
407
414
|
}
|
|
408
415
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForCarousel, decorators: [{
|
|
409
416
|
type: Directive,
|
|
@@ -418,7 +425,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
418
425
|
'[attr.data-align]': 'align()',
|
|
419
426
|
'[attr.dir]': 'dir()',
|
|
420
427
|
'[style.--for-carousel-offset]': 'offset()',
|
|
421
|
-
'[style.--for-carousel-active-index]': '
|
|
428
|
+
'[style.--for-carousel-active-index]': 'effectiveIndex()',
|
|
422
429
|
'[style.--for-carousel-slide-count]': 'slideCount()',
|
|
423
430
|
'[style.--for-carousel-slides-per-view]': 'slidesPerView()',
|
|
424
431
|
'[style.--for-carousel-viewport-width]': 'viewportWidth()',
|
|
@@ -986,7 +993,7 @@ class ForCarouselDrag {
|
|
|
986
993
|
const sign = this.#nextPerPx();
|
|
987
994
|
const slidesDragged = (dragPx * sign) / this.#slideSizePx;
|
|
988
995
|
const velocityTowardNext = flickVelocity(this.#velocity * sign, staleVelocity);
|
|
989
|
-
const target = resolveDragIndex(this.ctx.
|
|
996
|
+
const target = resolveDragIndex(this.ctx.effectiveIndex(), slidesDragged, velocityTowardNext);
|
|
990
997
|
this.ctx.scrollTo(target);
|
|
991
998
|
}
|
|
992
999
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForCarouselDrag, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|