forty-cdk 0.2.0 → 0.3.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/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Toolbar
|
|
2
|
+
|
|
3
|
+
Headless implementation of the [WAI-ARIA Toolbar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/). A toolbar is a single Tab stop that contains a set of buttons, links, and toggle groups; arrow keys move focus inside.
|
|
4
|
+
|
|
5
|
+
Composes naturally with `[forToggleGroup]` — toggle items nested inside a toolbar register with the toolbar's roving tabindex automatically, so arrows move fluidly across the whole bar.
|
|
6
|
+
|
|
7
|
+
## Pieces
|
|
8
|
+
|
|
9
|
+
| Class | Selector | Role |
|
|
10
|
+
| --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
11
|
+
| `ForToolbar` | `[forToolbar]` | Root. `role="toolbar"`. Owns roving + nav. |
|
|
12
|
+
| `ForToolbarButton` | `[forToolbarButton]` | Plain push button. Apply on `<button>`. |
|
|
13
|
+
| `ForToolbarLink` | `[forToolbarLink]` | Hyperlink. Apply on `<a>`. |
|
|
14
|
+
| `ForToolbarSeparator` | `[forToolbarSeparator]` | Visual divider. Defaults `orientation` to the toolbar's cross-axis; reflects `role="separator"` + `aria-orientation` + `data-orientation`. |
|
|
15
|
+
|
|
16
|
+
## Inputs (root)
|
|
17
|
+
|
|
18
|
+
| API | Type | Description |
|
|
19
|
+
| ------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `ariaLabel` | `input<string \| null>` | Reactive accessible name, reflected as `aria-label`. Default `null` (and `''`) emits no attribute. Prefer `aria-labelledby` when a visible label element exists. |
|
|
21
|
+
| `orientation` | `input<'horizontal' \| 'vertical'>` | Layout direction. Default `'horizontal'`. |
|
|
22
|
+
| `dir` | `input<WritingDirection>` | Reading direction. RTL swaps ArrowLeft / ArrowRight. |
|
|
23
|
+
| `loop` | `input<boolean>` | Whether arrow nav wraps at the ends. Default `true`. |
|
|
24
|
+
| `disabled` | `input<boolean>` | Disables every item. |
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Component, signal } from '@angular/core';
|
|
30
|
+
import { ForToggleGroup, ForToggleGroupItem } from 'forty-cdk/toggle';
|
|
31
|
+
import {
|
|
32
|
+
ForToolbar,
|
|
33
|
+
ForToolbarButton,
|
|
34
|
+
ForToolbarLink,
|
|
35
|
+
ForToolbarSeparator,
|
|
36
|
+
} from 'forty-cdk/toolbar';
|
|
37
|
+
|
|
38
|
+
@Component({
|
|
39
|
+
selector: 'demo-toolbar',
|
|
40
|
+
imports: [
|
|
41
|
+
ForToolbar,
|
|
42
|
+
ForToolbarButton,
|
|
43
|
+
ForToolbarLink,
|
|
44
|
+
ForToolbarSeparator,
|
|
45
|
+
ForToggleGroup,
|
|
46
|
+
ForToggleGroupItem,
|
|
47
|
+
],
|
|
48
|
+
template: `
|
|
49
|
+
<div forToolbar class="toolbar" [ariaLabel]="'Formatting'">
|
|
50
|
+
<button forToolbarButton class="toolbar-button" (click)="undo()">Undo</button>
|
|
51
|
+
<button forToolbarButton class="toolbar-button" (click)="redo()">Redo</button>
|
|
52
|
+
<span forToolbarSeparator></span>
|
|
53
|
+
<div forToggleGroup multiple [(value)]="formatting">
|
|
54
|
+
<button forToggleGroupItem value="bold">B</button>
|
|
55
|
+
<button forToggleGroupItem value="italic">I</button>
|
|
56
|
+
<button forToggleGroupItem value="underline">U</button>
|
|
57
|
+
</div>
|
|
58
|
+
<span forToolbarSeparator></span>
|
|
59
|
+
<a forToolbarLink class="toolbar-link" href="/help">Help</a>
|
|
60
|
+
</div>
|
|
61
|
+
`,
|
|
62
|
+
})
|
|
63
|
+
export class DemoToolbar {
|
|
64
|
+
readonly formatting = signal<readonly string[]>([]);
|
|
65
|
+
undo() {}
|
|
66
|
+
redo() {}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Styling
|
|
71
|
+
|
|
72
|
+
forty-cdk ships no styles. Add your own class to each piece — the `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes below.
|
|
73
|
+
|
|
74
|
+
### Data attributes
|
|
75
|
+
|
|
76
|
+
| Piece | Attribute | Values |
|
|
77
|
+
| ----------------------- | ------------------ | -------------------------- |
|
|
78
|
+
| `[forToolbar]` | `data-orientation` | `horizontal` \| `vertical` |
|
|
79
|
+
| `[forToolbar]` | `data-disabled` | present \| absent |
|
|
80
|
+
| `[forToolbarButton]` | `data-orientation` | `horizontal` \| `vertical` |
|
|
81
|
+
| `[forToolbarButton]` | `data-disabled` | present \| absent |
|
|
82
|
+
| `[forToolbarLink]` | `data-orientation` | `horizontal` \| `vertical` |
|
|
83
|
+
| `[forToolbarLink]` | `data-disabled` | present \| absent |
|
|
84
|
+
| `[forToolbarSeparator]` | `data-orientation` | `horizontal` \| `vertical` |
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
.toolbar {
|
|
88
|
+
display: flex;
|
|
89
|
+
gap: 0.25rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.toolbar[data-orientation='vertical'] {
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.toolbar-button[data-disabled],
|
|
97
|
+
.toolbar-link[data-disabled] {
|
|
98
|
+
opacity: 0.4;
|
|
99
|
+
pointer-events: none;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Accessibility notes
|
|
104
|
+
|
|
105
|
+
- **Single Tab stop that follows focus.** The toolbar takes one place in the tab order; only the entry-point item carries `tabindex="0"`. Before any interaction the entry point is the first enabled item; once you move focus with the arrows (or Home / End), the tab stop follows the last focused item, so Shift+Tab back into the toolbar restores it (matching APG and the Tabs / Tree primitives). Arrow keys move focus inside, Home / End jump to the first / last enabled item.
|
|
106
|
+
- **Always label the toolbar.** Pass the reactive `[ariaLabel]` input (or a native `aria-labelledby` pointing at a visible label element) so screen-reader users know what the toolbar acts on. Not optional — APG requires it.
|
|
107
|
+
- **Disabled items stay focusable on `<a forToolbarLink>`.** Native `<a>` has no `disabled` attribute; we expose `aria-disabled="true"` and suppress click. Removing the link from the focus order would deviate from APG; users can still hear "disabled".
|
|
108
|
+
- **Toggle groups don't change roles.** Inside a toolbar, `[forToggleGroup]` keeps `role="group"` (semantically a related set of buttons). The toolbar role lives only on the outer container.
|
|
109
|
+
- **Cross-axis separators.** `[forToolbarSeparator]` defaults to the orientation perpendicular to the toolbar so the line is visible. Override by setting `orientation` explicitly.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
> New to overlays in forty-cdk? [Your first overlay](../../../../../docs/your-first-overlay.md) walks a Popover from empty markup to styled-and-animated and explains the `@if` / open-state model and the portal → global CSS rule.
|
|
4
|
+
|
|
5
|
+
Headless implementation of the [WAI-ARIA Tooltip pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/) with hover / focus delays, Escape-to-dismiss, portal rendering, and `@floating-ui/dom`-driven positioning.
|
|
6
|
+
|
|
7
|
+
> APG: tooltips are for **non-interactive** descriptive text. If you need a click-to-open menu / popup with focusable contents, use a Popover primitive (not yet shipped).
|
|
8
|
+
|
|
9
|
+
## Pieces
|
|
10
|
+
|
|
11
|
+
| Class | Selector | Role |
|
|
12
|
+
| ------------------- | --------------------- | ------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `ForTooltip` | `[forTooltip]` | Wrapper. Owns `open`, delays, side / align positioning. Provides the shared context. |
|
|
14
|
+
| `ForTooltipTrigger` | `[forTooltipTrigger]` | Apply on a `<button>` or other focusable element. Emits the hover / focus / Escape signals. |
|
|
15
|
+
| `ForTooltipContent` | `[forTooltipContent]` | The bubble. Portaled to `document.body`, positioned by floating-ui while open. |
|
|
16
|
+
| `ForTooltipArrow` | `[forTooltipArrow]` | Optional. Render inside the content; floating-ui's `arrow` middleware aligns it with the trigger. |
|
|
17
|
+
|
|
18
|
+
## Inputs / models
|
|
19
|
+
|
|
20
|
+
### `ForTooltip`
|
|
21
|
+
|
|
22
|
+
| API | Type | Description |
|
|
23
|
+
| ------------------ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `open` | `model<boolean>` | Two-way bindable visibility. |
|
|
25
|
+
| `side` | `input<FloatingSide \| undefined>` | Anchor side (`'top'` / `'right'` / `'bottom'` / `'left'`). Falls back to `provideForTooltipDefaults` (`'top'`). |
|
|
26
|
+
| `align` | `input<FloatingAlign \| undefined>` | Alignment along `side` (`'start'` / `'center'` / `'end'`). Falls back to `provideForTooltipDefaults` (`'center'`). |
|
|
27
|
+
| `sideOffset` | `input<number \| undefined>` | Gap (px) between trigger and content along the main axis. Falls back to `provideForTooltipDefaults` (`8`). |
|
|
28
|
+
| `alignOffset` | `input<number>` | Gap (px) along the cross axis. Default `0`. |
|
|
29
|
+
| `collisionPadding` | `input<number \| undefined>` | Padding (px) for the `flip` / `shift` / `size` collision middlewares. Falls back to `provideForTooltipDefaults` (`8`). |
|
|
30
|
+
| `openDelay` | `input<number \| undefined>` | ms before showing after hover/focus enters. Falls back to `provideForTooltipDefaults` (`700`). |
|
|
31
|
+
| `closeDelay` | `input<number \| undefined>` | ms before hiding after hover/focus leaves. Escape ignores this. Falls back to `provideForTooltipDefaults` (`300`). |
|
|
32
|
+
| `disabled` | `input<boolean>` | When `true`, all interaction is ignored. |
|
|
33
|
+
| `showOnOverflow` | `input<boolean \| undefined>` | Show only when the trigger's own text is truncated (`scrollWidth > clientWidth`). Falls back to `provideForTooltipDefaults` (`false`). |
|
|
34
|
+
| `hoverableContent` | `input<boolean \| undefined>` | Let the pointer move into the content without dismissing it (drops `pointer-events: none` while open). Falls back to `provideForTooltipDefaults` (`false`). |
|
|
35
|
+
|
|
36
|
+
### `ForTooltipTrigger`, `ForTooltipContent`, `ForTooltipArrow`
|
|
37
|
+
|
|
38
|
+
No inputs of their own — they coordinate via the `ForTooltip` context.
|
|
39
|
+
|
|
40
|
+
## Scoped defaults
|
|
41
|
+
|
|
42
|
+
`provideForTooltipDefaults` configures defaults for an injector subtree — at the application root or in any component's `providers` array. Partial overrides inherit unspecified keys from the parent scope (or the library fallbacks at the root). Each call also establishes a fresh skip-delay coordinator scope: peer tooltips inside the scope share a skip-delay window; tooltips in other scopes don't.
|
|
43
|
+
|
|
44
|
+
| Key | Library fallback | Meaning |
|
|
45
|
+
| ------------------- | ---------------- | ------------------------------------------------------------------------------------------ |
|
|
46
|
+
| `openDelay` | `700` | ms before showing after hover/focus enters. |
|
|
47
|
+
| `closeDelay` | `300` | ms before hiding after hover/focus leaves. |
|
|
48
|
+
| `skipDelayDuration` | `300` | Window (ms) after a peer closes during which the next open is instant. |
|
|
49
|
+
| `side` | `'top'` | Anchor side for tooltips that don't set `side` themselves. |
|
|
50
|
+
| `align` | `'center'` | Alignment along `side` for tooltips that don't set `align` themselves. |
|
|
51
|
+
| `sideOffset` | `8` | Main-axis gap (px) for tooltips that don't set `sideOffset` themselves. |
|
|
52
|
+
| `collisionPadding` | `8` | Collision-middleware padding (px) for tooltips that don't set it themselves. |
|
|
53
|
+
| `showOnOverflow` | `false` | Show only when the trigger's text is truncated, for tooltips that don't set it themselves. |
|
|
54
|
+
| `hoverableContent` | `false` | Allow hovering into the content, for tooltips that don't set it themselves. |
|
|
55
|
+
|
|
56
|
+
Per-instance inputs always win over the scope defaults.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { provideForTooltipDefaults } from 'forty-cdk/tooltip';
|
|
60
|
+
|
|
61
|
+
// Bottom-anchored tooltips app-wide
|
|
62
|
+
bootstrapApplication(App, {
|
|
63
|
+
providers: [provideForTooltipDefaults({ side: 'bottom', sideOffset: 4 })],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// component-level override layers on top, per key
|
|
67
|
+
@Component({
|
|
68
|
+
providers: [provideForTooltipDefaults({ openDelay: 200 })],
|
|
69
|
+
...
|
|
70
|
+
})
|
|
71
|
+
class Toolbar {}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Stand-alone usage
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { Component, signal } from '@angular/core';
|
|
78
|
+
import {
|
|
79
|
+
ForTooltip,
|
|
80
|
+
ForTooltipArrow,
|
|
81
|
+
ForTooltipContent,
|
|
82
|
+
ForTooltipTrigger,
|
|
83
|
+
} from 'forty-cdk/tooltip';
|
|
84
|
+
|
|
85
|
+
@Component({
|
|
86
|
+
selector: 'demo-save',
|
|
87
|
+
imports: [ForTooltip, ForTooltipTrigger, ForTooltipContent, ForTooltipArrow],
|
|
88
|
+
template: `
|
|
89
|
+
<span forTooltip side="top" [openDelay]="400">
|
|
90
|
+
<button type="button" forTooltipTrigger aria-label="Save">💾</button>
|
|
91
|
+
<div forTooltipContent class="my-tooltip">
|
|
92
|
+
Save changes
|
|
93
|
+
<span forTooltipArrow class="my-tooltip-arrow"></span>
|
|
94
|
+
</div>
|
|
95
|
+
</span>
|
|
96
|
+
`,
|
|
97
|
+
styles: `
|
|
98
|
+
.my-tooltip {
|
|
99
|
+
background: #111;
|
|
100
|
+
color: white;
|
|
101
|
+
padding: 4px 8px;
|
|
102
|
+
border-radius: 4px;
|
|
103
|
+
font-size: 12px;
|
|
104
|
+
}
|
|
105
|
+
.my-tooltip-arrow {
|
|
106
|
+
width: 8px;
|
|
107
|
+
height: 8px;
|
|
108
|
+
background: #111;
|
|
109
|
+
transform: rotate(45deg);
|
|
110
|
+
/* How far the arrow pokes out of the bubble. Negative values
|
|
111
|
+
straddle the edge; 0 keeps the arrow flush against the bubble. */
|
|
112
|
+
--for-arrow-offset: -4px;
|
|
113
|
+
}
|
|
114
|
+
`,
|
|
115
|
+
})
|
|
116
|
+
export class DemoSave {}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Triggers stamped from outside-declared templates
|
|
120
|
+
|
|
121
|
+
Angular resolves `ng-template` DI at the template's **declaration** site, not where it is stamped. A `[forTooltipTrigger]` declared in a template outside the root throws the orphan error even when the template is rendered inside the root via `ngTemplateOutlet`. For that case the selector attribute accepts the root reference as a value, `routerLink`-style — grab it with `#root="forTooltip"` and pass it through the outlet context. The bare valueless attribute keeps resolving via DI.
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<span forTooltip #root="forTooltip">
|
|
125
|
+
<ng-container *ngTemplateOutlet="trig; context: { root }" />
|
|
126
|
+
<div forTooltipContent>Save changes</div>
|
|
127
|
+
</span>
|
|
128
|
+
|
|
129
|
+
<ng-template #trig let-root="root">
|
|
130
|
+
<button type="button" [forTooltipTrigger]="root" aria-label="Save">💾</button>
|
|
131
|
+
</ng-template>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Styling
|
|
135
|
+
|
|
136
|
+
forty-cdk ships no styles. Add your own class to each piece — the `for*` selectors are the behavior API, not a styling contract (see [Styling forty-cdk](../../../../../docs/styling.md)). Key your CSS off the reflected `data-*` attributes below.
|
|
137
|
+
|
|
138
|
+
### Data attributes
|
|
139
|
+
|
|
140
|
+
| Piece | Attribute | Values |
|
|
141
|
+
| --------------------- | --------------------- | ------------------ |
|
|
142
|
+
| `[forTooltip]` | `data-state` | `open` \| `closed` |
|
|
143
|
+
| `[forTooltip]` | `data-disabled` | present \| absent |
|
|
144
|
+
| `[forTooltip]` | `data-reduced-motion` | present \| absent |
|
|
145
|
+
| `[forTooltipTrigger]` | `data-state` | `open` \| `closed` |
|
|
146
|
+
| `[forTooltipContent]` | `data-state` | `open` \| `closed` |
|
|
147
|
+
| `[forTooltipContent]` | `data-reduced-motion` | present \| absent |
|
|
148
|
+
|
|
149
|
+
### CSS custom properties
|
|
150
|
+
|
|
151
|
+
See also: [Styling floating content](../../../../../docs/styling-floating-content.md) — animation rules, standalone `scale`/`opacity`, and the arrow recipe.
|
|
152
|
+
|
|
153
|
+
`[forTooltipContent]` is portaled to `document.body` and gets its position resolved by floating-ui. It exposes that geometry as custom properties on the content host (cleared on close), and `[forTooltipArrow]` reads the consumer-settable `--for-arrow-offset`:
|
|
154
|
+
|
|
155
|
+
| Element | Custom property | Type / range | Direction | Meaning |
|
|
156
|
+
| --------------------- | -------------------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------ |
|
|
157
|
+
| `[forTooltipContent]` | `--for-anchor-width` | px | out | Trigger (reference) width. |
|
|
158
|
+
| `[forTooltipContent]` | `--for-anchor-height` | px | out | Trigger (reference) height. |
|
|
159
|
+
| `[forTooltipContent]` | `--for-available-width` | px | out | Space available along the inline axis (floating-ui `size` middleware) — clamp with `max-width`. |
|
|
160
|
+
| `[forTooltipContent]` | `--for-available-height` | px | out | Space available along the block axis — clamp with `max-height`. |
|
|
161
|
+
| `[forTooltipContent]` | `--for-content-transform-origin` | `<origin>` keywords | out | `transform-origin` matching the resolved side / align, so a `scale` enter animation pivots from the trigger. |
|
|
162
|
+
| `[forTooltipArrow]` | `--for-arrow-offset` | px (default `0px`) | in | Consumer-set. How far the arrow pokes out past the bubble edge — typically a negative `px` (e.g. `-4px`). |
|
|
163
|
+
|
|
164
|
+
> `[forTooltipContent]` is portaled to `document.body`, so styles scoped to the `[forTooltip]` wrapper won't reach it. Style the bubble with a global stylesheet or a class on the content directive itself. See [Styling floating content](../../../../../docs/styling-floating-content.md) for the full positioner custom-property list (`--for-anchor-width` / `-height`, `--for-available-width` / `-height`, `--for-content-transform-origin`) and the animation / arrow recipes.
|
|
165
|
+
|
|
166
|
+
```css
|
|
167
|
+
.my-tooltip {
|
|
168
|
+
opacity: 0;
|
|
169
|
+
transform: scale(0.9);
|
|
170
|
+
transform-origin: var(--for-content-transform-origin);
|
|
171
|
+
transition:
|
|
172
|
+
opacity 120ms,
|
|
173
|
+
transform 120ms;
|
|
174
|
+
}
|
|
175
|
+
.my-tooltip[data-state='open'] {
|
|
176
|
+
opacity: 1;
|
|
177
|
+
transform: scale(1);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Reduced motion
|
|
182
|
+
|
|
183
|
+
`[forTooltip]` and `[forTooltipContent]` reflect `data-reduced-motion` (present / absent) whenever the OS `prefers-reduced-motion: reduce` media query matches, so you can opt your own transitions out without re-deriving the query in CSS or TypeScript. The attribute flips reactively if the preference changes mid-session.
|
|
184
|
+
|
|
185
|
+
```css
|
|
186
|
+
.my-tooltip[data-reduced-motion] {
|
|
187
|
+
transition: none;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The tooltip's open / close delays are hover-intent debouncing rather than motion, so they are deliberately left unchanged under reduced motion — only the visual transitions (which are yours) should opt out.
|
|
192
|
+
|
|
193
|
+
Tooltip content is template-provided and mounts via the consumer's own markup, so the tooltip cannot know the content would be empty before opening — it would happily open an empty bubble on hover/focus. The supported gate is the existing `disabled` input: drive it from whatever signal feeds the content. This is the recipe for design-system wrappers that take the tooltip text as a string input:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { Component, input } from '@angular/core';
|
|
197
|
+
import { ForTooltip, ForTooltipContent, ForTooltipTrigger } from 'forty-cdk/tooltip';
|
|
198
|
+
|
|
199
|
+
@Component({
|
|
200
|
+
selector: 'my-tooltip-button',
|
|
201
|
+
imports: [ForTooltip, ForTooltipTrigger, ForTooltipContent],
|
|
202
|
+
template: `
|
|
203
|
+
<span forTooltip [disabled]="!message()">
|
|
204
|
+
<button type="button" forTooltipTrigger><ng-content /></button>
|
|
205
|
+
<div forTooltipContent class="my-tooltip">{{ message() }}</div>
|
|
206
|
+
</span>
|
|
207
|
+
`,
|
|
208
|
+
})
|
|
209
|
+
export class MyTooltipButton {
|
|
210
|
+
readonly message = input('');
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
While `disabled` is `true`, hover and focus are ignored and an already-open tooltip force-closes — no empty bubble, no stale `aria-describedby`.
|
|
215
|
+
|
|
216
|
+
## Imperative `show()` / `hide()`
|
|
217
|
+
|
|
218
|
+
For programmatic control beyond hover and focus — e.g. a wrapper that drives the tooltip from a text-truncation observer — `ForTooltip` exposes `show()` and `hide()` methods. Grab the root with a template reference (`#tip="forTooltip"`) and call them:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { Component } from '@angular/core';
|
|
222
|
+
import { ForTooltip, ForTooltipContent, ForTooltipTrigger } from 'forty-cdk/tooltip';
|
|
223
|
+
|
|
224
|
+
@Component({
|
|
225
|
+
selector: 'demo-imperative',
|
|
226
|
+
imports: [ForTooltip, ForTooltipTrigger, ForTooltipContent],
|
|
227
|
+
template: `
|
|
228
|
+
<span forTooltip #tip="forTooltip">
|
|
229
|
+
<button type="button" forTooltipTrigger>Save</button>
|
|
230
|
+
<div forTooltipContent class="my-tooltip">Save changes</div>
|
|
231
|
+
</span>
|
|
232
|
+
|
|
233
|
+
<button type="button" (click)="tip.show()">Show</button>
|
|
234
|
+
<button type="button" (click)="tip.hide()">Hide</button>
|
|
235
|
+
`,
|
|
236
|
+
})
|
|
237
|
+
export class DemoImperative {}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Both mirror the hover / focus lifecycle rather than bypassing it:
|
|
241
|
+
|
|
242
|
+
- `show()` schedules the open after the resolved `openDelay` (instant when the delay is `0` or the scope's skip-delay window is active). It is a no-op while `disabled`, and a no-op under `showOnOverflow` when the trigger's own text is not truncated — the same gates a hover / focus open passes.
|
|
243
|
+
- `hide()` schedules the close after the resolved `closeDelay` and disarms the hoverable-content grace bridge.
|
|
244
|
+
|
|
245
|
+
For an **instant, unconditional** open or close that ignores the delays and both gates, write the `[(open)]` model directly (`open.set(true)` / `open.set(false)`) instead. To suppress empty-message tooltips, keep using the `disabled` input shown above rather than gating the `show()` call yourself.
|
|
246
|
+
|
|
247
|
+
## Keyboard
|
|
248
|
+
|
|
249
|
+
- **Tab** to the trigger → opens the tooltip after `openDelay`.
|
|
250
|
+
- **Tab** away → closes after `closeDelay`.
|
|
251
|
+
- **Escape** while open → closes immediately, regardless of `closeDelay`.
|
|
252
|
+
|
|
253
|
+
## Behavior notes
|
|
254
|
+
|
|
255
|
+
- **Portal**: the content element is moved to `document.body` on first render. Any styles you scope to the wrapper won't reach it — style the bubble globally or via a class on the content directive itself.
|
|
256
|
+
- **`pointer-events: none`** is applied by default so hovering the bubble doesn't extend its lifetime and clicks pass through to whatever is behind. Setting `hoverableContent` drops it while open (see below). Override with your own CSS if your design needs a different behavior.
|
|
257
|
+
- **Keep content non-interactive**. Tooltips don't trap focus and won't survive a click into them — APG explicitly forbids interactive children.
|
|
258
|
+
- **`hoverableContent`** lets the pointer move into the bubble without dismissing it — useful for descriptive text the user may want to select. It drops the default `pointer-events: none` while open and bridges the trigger / content gap with a pointer-grace "safe triangle" so a slow diagonal traversal doesn't close the tooltip. The content must still stay non-interactive per APG.
|
|
259
|
+
- **`showOnOverflow`** gates the tooltip on the trigger being truncated (`scrollWidth > clientWidth`) — the common pattern for ellipsized labels, where the tooltip adds nothing once the full text already fits. When the trigger's text fits, hover and focus are ignored.
|
|
260
|
+
- **Touch**: APG flags tooltips as problematic on touch devices (no hover, no separate focus, no obvious dismiss). The trigger filters touch pointers out of both the hover-open and focus-open paths, so a tap does **not** open the tooltip — only mouse hover and keyboard focus do. For touch-first UI where the descriptive content must be reachable on tap, consider a Popover.
|
|
261
|
+
- **Arrow offset**: `[forTooltipArrow]` writes `position: absolute`, the floating-ui-resolved `left` / `top`, and `var(--for-arrow-offset, 0px)` on the side opposite the bubble. Set `--for-arrow-offset` on the arrow (or any ancestor) to control how far the arrow pokes out — typically a negative `px` value such as `-4px`. Defaults to `0px`.
|
|
262
|
+
- **Floating-ui dependency** is declared as an optional peer (`@floating-ui/dom`). Install it only if you actually use the Tooltip primitive:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
npm install @floating-ui/dom
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Accessibility notes
|
|
269
|
+
|
|
270
|
+
- The trigger receives `aria-describedby="<content-id>"` only while the tooltip is open, matching APG.
|
|
271
|
+
- A consumer-set `id` on the trigger element is preserved (and used as the trigger id internally); the generated `for-tooltip-trigger-*` id is only assigned when the element has none. Anchors, `aria-labelledby` references, and `<label for>` associations keep working.
|
|
272
|
+
- The content carries `role="tooltip"` and a stable id wired to the trigger.
|
|
273
|
+
- The optional arrow is `aria-hidden="true"` — it's purely decorative.
|
|
274
|
+
- The tooltip never steals focus.
|