@pastweb/components-core 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,141 @@
1
+ # TextArea
2
+
3
+ TextArea is a labelled multiline text control with explicit disabled, read-only,
4
+ required, resizable, and loading states. Its rendering contract targets WCAG
5
+ 2.2 Level AA.
6
+
7
+ ## When to use
8
+
9
+ Use TextArea for multi-line values. Use Input for a single line. Prefer a
10
+ visible label; placeholders do not replace labels. If there is a good reason to
11
+ limit length, associate a character-count message through `id` rather than
12
+ relying on `maxLength` alone.
13
+
14
+ ## Props
15
+
16
+ | PropName | Type | Required | Default | Description |
17
+ |---|---|---:|---:|---|
18
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component, part, and state classes. |
19
+ | `ariaLabel` | `string` | no | `"TextArea"` | Names the field when no visible `label` is supplied. Pass an empty string to omit it. |
20
+ | `className` | `string` | no | `""` | Additional root classes. The `is-loading` class is also treated as a loading signal. |
21
+ | `cols` | `number` | no | - | Native column count. |
22
+ | `disabled` | `boolean` | no | - | Makes the field unavailable. |
23
+ | `id` | `string` | no | generated | Native textarea ID. Generated IDs use the `textArea` prefix. |
24
+ | `label` | `string` | no | `""` | Visible accessible label. |
25
+ | `labelPlacement` | `Placement` | no | `'top-start'` | Visual label placement. |
26
+ | `loading` | `boolean` | no | - | Marks the field busy and temporarily unavailable. |
27
+ | `maxLength` | `number` | no | - | Native maximum character length. |
28
+ | `name` | `string` | no | - | Native form name. |
29
+ | `onChange` | `(value: string) => void` | no | noop | Reports edited text. |
30
+ | `placeholder` | `string` | no | `""` | Short input hint. Do not use it as the only label. |
31
+ | `readOnly` | `boolean` | no | - | Prevents editing and applies `is-readonly`. |
32
+ | `required` | `boolean` | no | - | Marks the value as required. |
33
+ | `resizeable` | `boolean` | no | - | Enables two-axis web resizing and `is-resizeable`. |
34
+ | `rows` | `number` | no | - | Native row count. |
35
+ | `tabIndex` | `number` | no | `0` | Sequential focus order. Prefer document order. |
36
+ | `value` | `string` | no | `""` | Controlled value. |
37
+
38
+ The named `loading` slot defaults to “Loading...”.
39
+
40
+ ## Utility classes
41
+
42
+ Apply these classes through `className`.
43
+
44
+ | Class | Description |
45
+ |---|---|
46
+ | `is-rounded` | Uses the rounded control radius token. |
47
+ | `is-full-width` | Expands the field to the available width. |
48
+ | `is-loading` | Compatibility signal for the loading state. Prefer the `loading` prop. |
49
+
50
+ ## Mediator state
51
+
52
+ | Property | Type | Description |
53
+ |---|---|---|
54
+ | `ariaLabel` | `string \| undefined` | Normalized accessible name when no visible `label` is present. Empty labels are omitted. |
55
+ | `inputId` | `string` | Native textarea ID from an explicit `id` or a generated `textArea` prefix. |
56
+ | `isDisabled` | `true \| undefined` | Is `true` when disabled; otherwise omitted. |
57
+ | `isLoading` | `true \| undefined` | Is `true` when busy; otherwise omitted. |
58
+ | `isReadOnly` | `true \| undefined` | Is `true` when read-only; otherwise omitted. |
59
+ | `isResizeable` | `true \| undefined` | Is `true` when resizing is enabled; otherwise omitted. |
60
+
61
+ ## CSS Variables
62
+
63
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
64
+ |---|---|---|---|---|---|---|
65
+ | `--pw_TextArea_root_default_margin` | `root` | `default` | `margin` | shared margins | Component spacing. | Web, Capacitor, NativeScript |
66
+ | `--pw_TextArea_root_disabled_opacity` | `root` | `disabled` | `opacity` | `var(--pw_disabled_opacity)` | Disabled appearance. | Web, Capacitor, NativeScript |
67
+ | `--pw_TextArea_control_default_min-height` | `control` | `default` | `min-height` | `2.75em` | Minimum control height, with a 44px web target. | Web, Capacitor, NativeScript |
68
+ | `--pw_TextArea_root_default_background-color` | `control` | `default` | `background-color` | `var(--pw_control_background-color)` | Control background. | Web, Capacitor, NativeScript |
69
+ | `--pw_TextArea_root_default_color` | `control` | `default` | `color` | `var(--pw_text_color)` | Control foreground. | Web, Capacitor, NativeScript |
70
+ | `--pw_TextArea_root_default_border-color` | `control` | `default` | `border-color` | `var(--pw_border-color)` | Border colour. | Web, Capacitor, NativeScript |
71
+ | `--pw_TextArea_root_default_border-radius` | `control` | `default` | `border-radius` | `var(--pw_border-radius)` | Corner radius. | Web, Capacitor, NativeScript |
72
+ | `--pw_TextArea_label_default_color` | `label` | `default` | `color` | `var(--pw_label_text_color)` | Label colour. | Web, Capacitor, NativeScript |
73
+ | `--pw_TextArea_loading_default_background-color` | `loading` | `default` | `background-color` | control background | Loading overlay. | Web, Capacitor, NativeScript |
74
+
75
+ ```xts
76
+ import { useBeforeUnmount, useMediator, withDefaultProps } from '@pastweb/x';
77
+ import { textAreaMediator } from '@pastweb/components-core/TextArea';
78
+ import { DEFAULT_PROPS } from './constants';
79
+
80
+ export function TextArea(props: TextAreaProps) {
81
+ const p = withDefaultProps<TextAreaDefaultProps>(props, DEFAULT_PROPS);
82
+ const m = useMediator<TextAreaMediator>(textAreaMediator, p);
83
+ const s = m.state;
84
+
85
+ useBeforeUnmount(m.useBeforeUnmount);
86
+
87
+ return (
88
+ <div class={p.cls('TextArea', `label-${p.labelPlacement}`, {
89
+ 'is-disabled': s.isDisabled, 'is-loading': s.isLoading, 'is-readonly': s.isReadOnly, 'is-resizeable': s.isResizeable,
90
+ }, p.className)} aria-busy={s.isLoading}>
91
+ <div class={p.cls('textarea')}>
92
+ <textarea id={s.inputId} class={p.cls('input')} disabled={s.isDisabled || s.isLoading} readonly={s.isReadOnly}
93
+ required={p.required} value={p.value} aria-label={s.ariaLabel} aria-busy={s.isLoading} aria-disabled={s.isDisabled}
94
+ @change={event => m.onChange(event.target.value)} />
95
+ <div #if={s.isLoading} class={p.cls('loading')} role="status" aria-live="polite">
96
+ <slot name="loading">Loading...</slot>
97
+ </div>
98
+ </div>
99
+ <label #if={p.label} class={p.cls('label', p.labelPlacement)} for={s.inputId}>{p.label}</label>
100
+ </div>
101
+ );
102
+ }
103
+ ```
104
+
105
+ ## Keyboard interaction
106
+
107
+ - Printable keys, `Backspace`, and `Delete` edit the value through native
108
+ textarea behavior.
109
+ - `Tab` and `Shift+Tab` move sequential focus.
110
+
111
+ ## Accessibility notes
112
+
113
+ - The native textarea supplies keyboard, form, and validation behavior.
114
+ - Visible `label` text is connected through `for`/`id`. When it is absent,
115
+ `ariaLabel` names the field. The default name is `TextArea`.
116
+ - Disabled fields use `.is-disabled`, native `disabled`, and `aria-disabled`.
117
+ - Loading disables the field, applies `aria-busy`, and displays a polite status
118
+ region.
119
+ - Shared web styles provide at least a 44 by 44 CSS-pixel target, visible
120
+ `:focus-visible` indication, forced-colours treatment, and reduced-motion
121
+ behavior.
122
+
123
+ ## Native renderer mapping
124
+
125
+ Native renderers use a multiline editable text view. Do not copy ARIA attributes
126
+ into native markup.
127
+
128
+ ## Known limitations
129
+
130
+ - Placeholders do not replace a visible or accessible name.
131
+ - Hint, error, and character-count text are not built in. Associate them with
132
+ the native textarea through `id` when the surrounding form provides that
133
+ content.
134
+ - Automated accessibility checks do not by themselves establish WCAG
135
+ conformance.
136
+
137
+ ## Standards
138
+
139
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
140
+ - WAI-ARIA Textbox pattern: https://www.w3.org/WAI/ARIA/apg/patterns/
141
+ - GOV.UK textarea: https://design-system.service.gov.uk/components/textarea/
@@ -0,0 +1,119 @@
1
+ # ToastController
2
+
3
+ ToastController presents a temporary, politely announced status message. Its
4
+ rendering contract targets WCAG 2.2 Level AA.
5
+
6
+ ## When to use
7
+
8
+ Use ToastController for non-urgent confirmation or status that can disappear.
9
+ Use Notification for a persistent in-page message, an alert pattern for urgent
10
+ information, and a dialog when the user must decide before continuing.
11
+
12
+ ## Props
13
+
14
+ | PropName | Type | Required | Default | Description |
15
+ |---|---|---:|---:|---|
16
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and state classes. |
17
+ | `ariaLabel` | `string` | no | `""` | Names the status region. Pass an empty string to omit it. |
18
+ | `portal` | `Portal` | no | - | Portal configuration. |
19
+ | `portalElement` | `HTMLElement` | no | - | Portal container. |
20
+ | `entryId` | `string` | no | - | Portal entry ID. |
21
+ | `placement` | `PLACEMENT` | no | `top-right` | Viewport placement. |
22
+ | `padding` | `number` | no | `15` | Inner padding in pixels. |
23
+ | `delay` | `number` | no | `5` | Auto-dismiss delay in seconds. |
24
+ | `timeUpdate` | `number` | no | `10` | Timer tick interval. |
25
+ | `animation` | `Animation` | no | `slide` | Entry and exit animation. |
26
+ | `closeOnEscKey` | `boolean` | no | - | Dismisses on `Escape`. |
27
+ | `pauseTimerOnOver` | `boolean` | no | - | Pauses the timer on pointer or keyboard focus. |
28
+ | `resetTimerOnPause` | `boolean` | no | - | Resets the timer when pausing. |
29
+ | `onClose` | `() => void` | no | noop | Reports close intent. |
30
+ | `onDestroy` | `() => void` | no | noop | Reports portal removal. |
31
+ | `onKeyDown` | `(event) => void` | no | noop | Observes keyboard input. |
32
+
33
+ ## XTS rendering contract
34
+
35
+ ```xts
36
+ import { setContext, useMediator, useRef, withDefaultProps } from '@pastweb/x';
37
+ import {
38
+ JUSTIFY,
39
+ TOAST_CONTEXT_KEY,
40
+ toastControllerMediator,
41
+ } from '@pastweb/components-core/ToastController';
42
+ import { DEFAULT_PROPS } from './constants';
43
+
44
+ export function ToastController(props: ToastControllerProps) {
45
+ const p = withDefaultProps<ToastControllerDefaultProps>(props, DEFAULT_PROPS);
46
+ const m = useMediator<ToastControllerMediator>(toastControllerMediator, p);
47
+ const s = m.state;
48
+ const c = m.getContext();
49
+ const toastReference = useRef(null);
50
+
51
+ setContext(TOAST_CONTEXT_KEY, c);
52
+
53
+ return (
54
+ <div
55
+ #ref={(element) => {
56
+ toastReference.value = element;
57
+ if (element) m.onMounted(element);
58
+ }}
59
+ class={p.cls('ToastController', p.placement)}
60
+ style={{ [s.y]: s.top || s.bottom, justifyContent: JUSTIFY[s.x] }}
61
+ >
62
+ <div
63
+ class={p.cls('toast', {
64
+ [`${p.animation.name}-in-${s.x}-${s.y}`]: s.isOpen,
65
+ [`${p.animation.name}-out-${s.x}-${s.y}`]: !s.isOpen,
66
+ })}
67
+ role='status'
68
+ aria-live='polite'
69
+ aria-atomic='true'
70
+ aria-label={p.ariaLabel || undefined}
71
+ @keydown={m.onKeyDown}
72
+ @mouseenter={m.onMouseOver}
73
+ @mouseleave={m.onMouseLeave}
74
+ @focus={m.onMouseOver}
75
+ @blur={m.onMouseLeave}
76
+ >
77
+ <slot />
78
+ </div>
79
+ </div>
80
+ );
81
+ }
82
+ ```
83
+
84
+ ToastController is a browser-only portal controller. Create it only while its
85
+ portal element is available; it is not part of initial server rendering.
86
+
87
+ ## Keyboard interaction
88
+
89
+ - `Escape` dismisses the toast when `closeOnEscKey` is enabled.
90
+ - The toast does not enter sequential tab order. Interactive content inside it
91
+ remains operable.
92
+
93
+ ## Accessibility notes
94
+
95
+ - The toast is a polite `status` live region. It does not move focus on open.
96
+ - The auto-dismiss timer pauses while the pointer is over the toast, while it
97
+ has keyboard focus, and while it is not fully visible.
98
+ - Time limits can be paused (`pauseTimerOnOver`) and dismissed early (`Escape`
99
+ or a consumer close control).
100
+ - Shared web styles keep a visible `:focus-visible` indicator and disable
101
+ non-essential motion under `prefers-reduced-motion`.
102
+
103
+ ## Known limitations
104
+
105
+ - A visible close control is not built in. Supply one in the toast content when
106
+ users must dismiss without waiting.
107
+ - Automated accessibility checks do not by themselves establish WCAG
108
+ conformance.
109
+
110
+ ## Native renderer mapping
111
+
112
+ Native renderers map the toast to a native polite announcement and timed overlay.
113
+ Do not copy ARIA attributes into native markup.
114
+
115
+ ## Standards
116
+
117
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
118
+ - WAI-ARIA Status and alert patterns: https://www.w3.org/WAI/ARIA/apg/patterns/
119
+ - GOV.UK notification banner: https://design-system.service.gov.uk/components/notification-banner/
@@ -0,0 +1,109 @@
1
+ # Toggle
2
+
3
+ Toggle represents an immediate binary setting. The web renderer uses a native
4
+ checkbox with `role="switch"`. Its rendering contract follows the WAI-ARIA
5
+ Switch pattern and targets WCAG 2.2 Level AA.
6
+
7
+ ## When to use
8
+
9
+ Use Toggle for an immediate on/off setting. Use Checkbox for a form choice that
10
+ is submitted later. Prefer a pair of radios when both options need visible
11
+ labels, such as “On” and “Off”.
12
+
13
+ ## Props
14
+
15
+ | PropName | Type | Required | Default | Description |
16
+ |---|---|---:|---:|---|
17
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and state classes. |
18
+ | `ariaLabel` | `string` | no | `"Toggle"` | Names the switch when no visible `label` is supplied. Pass an empty string to omit it. |
19
+ | `className` | `string` | no | `""` | Additional root classes. |
20
+ | `disabled` | `boolean` | no | - | Disables the control and applies `is-disabled`. |
21
+ | `id` | `string \| number` | no | generated | Native control ID. Generated IDs use the `toggle` prefix. |
22
+ | `label` | `string` | no | `""` | Visible accessible label. |
23
+ | `labelPlacement` | `Placement` | no | `'top-start'` | Visual label position. |
24
+ | `name` | `string` | no | - | Native form name. |
25
+ | `onChange` | `(checked, event) => void` | no | noop | Reports state changes. |
26
+ | `tabIndex` | `number` | no | `0` | Sequential focus order. |
27
+ | `value` | `boolean` | no | - | Controlled checked state. |
28
+
29
+ ## Mediator state
30
+
31
+ | Property | Type | Description |
32
+ |---|---|---|
33
+ | `ariaLabel` | `string \| undefined` | Normalized accessible name when no visible `label` is present. |
34
+ | `inputId` | `string` | Native input ID from an explicit `id` or a generated `toggle` prefix. |
35
+ | `isDisabled` | `true \| undefined` | Is `true` when the Toggle is disabled; otherwise omitted. |
36
+ | `value` | `boolean` | Checked state used for `.is-checked`, native `checked`, and `aria-checked`. |
37
+
38
+ ## CSS Variables
39
+
40
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
41
+ |---|---|---|---|---|---|---|
42
+ | `--pw_Toggle_root_default_margin` | `root` | `default` | `margin` | shared margins | Component spacing. | Web, Capacitor, NativeScript |
43
+ | `--pw_Toggle_root_disabled_opacity` | `root` | `disabled` | `opacity` | `var(--pw_disabled_opacity)` | Disabled appearance. | Web, Capacitor, NativeScript |
44
+ | `--pw_Toggle_background_default_width` | `track` | `default` | `width` | `4em` | Track width. | Web, Capacitor, NativeScript |
45
+ | `--pw_Toggle_background_default_height` | `track` | `default` | `height` | `2.75em` | Track height, with a 44px web target. | Web, Capacitor, NativeScript |
46
+ | `--pw_Toggle_root_default_background-color` | `track` | `default` | `background-color` | `var(--pw_control_background-color)` | Unchecked track. | Web, Capacitor, NativeScript |
47
+ | `--pw_Toggle_label_default_color` | `label` | `default` | `color` | `var(--pw_label_text_color)` | Label colour. | Web, Capacitor, NativeScript |
48
+
49
+ ```xts
50
+ import { useBeforeUnmount, useMediator, withDefaultProps } from '@pastweb/x';
51
+ import { toggleMediator } from '@pastweb/components-core/Toggle';
52
+ import { DEFAULT_PROPS } from './constants';
53
+
54
+ export function Toggle(props: ToggleProps) {
55
+ const p = withDefaultProps<ToggleDefaultProps>(props, DEFAULT_PROPS);
56
+ const m = useMediator<ToggleMediator>(toggleMediator, p);
57
+ const s = m.state;
58
+
59
+ useBeforeUnmount(m.useBeforeUnmount);
60
+
61
+ return (
62
+ <div class={p.cls('Toggle', `label-${p.labelPlacement}`, { 'is-checked': s.value, 'is-disabled': s.isDisabled }, p.className)}>
63
+ <label class={p.cls('control')}>
64
+ <span class={p.cls('button')}>
65
+ <input type="checkbox" role="switch" id={s.inputId} checked={s.value} disabled={s.isDisabled}
66
+ aria-checked={s.value} aria-disabled={s.isDisabled} aria-label={s.ariaLabel}
67
+ @change={event => m.onChange(event.target.checked, event)} />
68
+ </span>
69
+ <span #if={p.label} class={p.cls('label', p.labelPlacement)}>{p.label}</span>
70
+ </label>
71
+ </div>
72
+ );
73
+ }
74
+ ```
75
+
76
+ ## Keyboard interaction
77
+
78
+ - `Space` toggles the native switch.
79
+ - `Tab` and `Shift+Tab` move sequential focus.
80
+
81
+ ## Accessibility notes
82
+
83
+ - The web renderer uses a native checkbox with `role="switch"` and
84
+ `aria-checked`.
85
+ - One wrapping label supplies the visible accessible name. When visible label
86
+ text is absent, the mediator exposes `ariaLabel`.
87
+ - Disabled switches use native `disabled`, `aria-disabled="true"`, and
88
+ `is-disabled`.
89
+ - Shared web styles provide at least a 44 by 44 CSS-pixel target, visible
90
+ `:focus-visible` indication, forced-colours treatment, and reduced-motion
91
+ behavior.
92
+
93
+ ## Native renderer mapping
94
+
95
+ Native renderers map Toggle to the platform switch control. Do not copy ARIA
96
+ attributes into native markup.
97
+
98
+ ## Known limitations
99
+
100
+ - GOV.UK prefers radios or checkboxes when both options need equal visible
101
+ labels. Use Toggle only for an immediate setting.
102
+ - Automated accessibility checks do not by themselves establish WCAG
103
+ conformance.
104
+
105
+ ## Standards
106
+
107
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
108
+ - WAI-ARIA Switch pattern: https://www.w3.org/WAI/ARIA/apg/patterns/switch/
109
+ - GOV.UK radios: https://design-system.service.gov.uk/components/radios/
@@ -0,0 +1,128 @@
1
+ # Tooltip
2
+
3
+ Tooltip provides a short, non-interactive description for a focused or hovered
4
+ control. Its rendering contract follows the WAI-ARIA Tooltip pattern and
5
+ targets WCAG 2.2 Level AA.
6
+
7
+ ## When to use
8
+
9
+ Use Tooltip to supplement a control’s visible name with extra description. Do
10
+ not put essential instructions, errors, or interactive content in a tooltip.
11
+ Prefer visible hint text, especially in forms. Hover-only UI is unreliable on
12
+ touch devices.
13
+
14
+ ## Props
15
+
16
+ | PropName | Type | Required | Default | Description |
17
+ |---|---|---:|---:|---|
18
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and state classes. |
19
+ | `content` | `string` | no | `""` | Tooltip description. Empty content never opens. |
20
+ | `id` | `string` | no | generated | Stable ID used by the trigger description relationship. Generated IDs use the `tooltip` prefix. |
21
+ | `placement` | `TooltipPlacement` | no | `'top'` | Preferred side and optional start/end alignment. |
22
+ | `offset` | `number` | no | `8` | Renderer positioning gap. |
23
+ | `showDelay` | `number` | no | `300` | Pointer-hover opening delay in milliseconds. Focus opens immediately. |
24
+ | `hideDelay` | `number` | no | `100` | Pointer-leave closing delay in milliseconds. Blur and Escape close immediately. |
25
+ | `disabled` | `boolean` | no | - | Prevents opening and applies `is-disabled`. |
26
+ | `className` | `string` | no | `""` | Additional root classes. |
27
+ | `tooltipClassName` | `string` | no | `""` | Additional tooltip-surface classes. |
28
+
29
+ `TooltipPlacement` supports `top`, `right`, `bottom`, and `left`, each
30
+ optionally followed by `-start` or `-end`. `Placement` remains a deprecated
31
+ alias.
32
+
33
+ ## Mediator state
34
+
35
+ | Property | Type | Description |
36
+ |---|---|---|
37
+ | `isDisabled` | `true \| undefined` | Is `true` when the Tooltip is disabled; otherwise omitted. |
38
+ | `isVisible` | `boolean` | Whether the tooltip is requested open. |
39
+ | `tooltipId` | `string` | Tooltip relationship ID from an explicit `id` or a generated `tooltip` prefix. |
40
+
41
+ ## CSS Variables
42
+
43
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
44
+ |---|---|---|---|---|---|---|
45
+ | `--pw_Tooltip_content_default_background-color` | `content` | `default` | `background-color` | `var(--pw_background-color)` | Tooltip background. | Web, Capacitor |
46
+ | `--pw_Tooltip_content_default_color` | `content` | `default` | `color` | `var(--pw_text_color)` | Tooltip foreground. | Web, Capacitor |
47
+ | `--pw_Tooltip_content_default_border-color` | `content` | `default` | `border-color` | `var(--pw_border-color)` | Border colour. | Web, Capacitor |
48
+ | `--pw_Tooltip_content_default_max-width` | `content` | `default` | `max-width` | `20rem` | Maximum tooltip width. | Web, Capacitor |
49
+ | `--pw_Tooltip_content_default_font-size` | `content` | `default` | `font-size` | shared font size | Tooltip type size. | Web, Capacitor |
50
+ | `--pw_Tooltip_content_default_line-height` | `content` | `default` | `line-height` | `1.5em` | Tooltip line box. With vertical padding this matches the arrow box. | Web, Capacitor |
51
+ | `--pw_Tooltip_content_default_padding` | `content` | `default` | `padding` | `0.25em` plus shared horizontal padding | Inner spacing, in `em` so it tracks font size. | Web, Capacitor |
52
+ | `--pw_Tooltip_content_default_drop-shadow` | `content` | `default` | `filter` | `0 0 0.2em` shared shadow colour | Shadow that follows the tooltip and arrow. | Web, Capacitor |
53
+ | `--pw_Tooltip_arrow_default_size` | `arrow` | `default` | `size` | `1em` | Decorative arrow size, in `em` so it tracks the tooltip font. | Web, Capacitor |
54
+
55
+ ```xts
56
+ import { useBeforeUnmount, useMediator, useMounted, useRef, withDefaultProps } from '@pastweb/x';
57
+ import { tooltipMediator } from '@pastweb/components-core/Tooltip';
58
+ import { DEFAULT_PROPS } from './constants';
59
+
60
+ export function Tooltip(props: TooltipProps) {
61
+ const p = withDefaultProps<TooltipDefaultProps>(props, DEFAULT_PROPS);
62
+ const m = useMediator<TooltipMediator>(tooltipMediator, p);
63
+ const s = m.state;
64
+ const trigger = useRef(null);
65
+ const tooltip = useRef(null);
66
+ const arrow = useRef(null);
67
+
68
+ useMounted(() => m.useMounted(trigger.value, tooltip.value, arrow.value));
69
+ useBeforeUnmount(m.useBeforeUnmount);
70
+
71
+ return (
72
+ <span class={p.cls('Tooltip', { 'is-disabled': s.isDisabled }, p.className)}
73
+ @pointerenter={event => event.pointerType !== 'touch' && m.show()}
74
+ @pointerleave={() => m.hide()} @focusin={() => m.show(true)}
75
+ @focusout={() => m.hide(true)} @keydown={m.onKeyDown}>
76
+ <span #ref={trigger} class={p.cls('trigger')} aria-describedby={s.isDisabled ? undefined : s.tooltipId}>
77
+ <slot />
78
+ </span>
79
+ <div #ref={tooltip} id={s.tooltipId} role="tooltip"
80
+ class={p.cls('tooltipContent', { 'is-visible': s.isVisible }, p.tooltipClassName)}>
81
+ <Render content={p.content} />
82
+ <div #ref={arrow} class={p.cls('arrow')} aria-hidden="true" />
83
+ </div>
84
+ </span>
85
+ );
86
+ }
87
+ ```
88
+
89
+ ## Keyboard interaction
90
+
91
+ - Focus on the trigger opens the tooltip immediately.
92
+ - Blur closes the tooltip immediately.
93
+ - `Escape` dismisses an open tooltip without moving focus.
94
+ - The tooltip itself does not enter the tab order.
95
+
96
+ ## Accessibility notes
97
+
98
+ - The popup uses `role="tooltip"`. The trigger references it with
99
+ `aria-describedby`.
100
+ - A single element trigger receives `aria-describedby` on that element. Plain
101
+ text triggers are wrapped in a focusable span.
102
+ - Pointer hover opens after `showDelay`. Pointer leave closes after
103
+ `hideDelay` so the pointer can move onto the tooltip (WCAG 1.4.13).
104
+ - Touch pointer entry is ignored because hover-only UI is unreliable on touch
105
+ devices.
106
+ - Empty `content` never opens.
107
+ - The decorative arrow is hidden from assistive technology.
108
+ - Shared web styles wrap long text, keep a visible trigger focus indicator,
109
+ support forced colours, and disable non-essential motion under
110
+ `prefers-reduced-motion`.
111
+
112
+ ## Native renderer mapping
113
+
114
+ NativeScript renderers map show/hide intent to a native accessible hint or
115
+ non-interactive popup. Do not copy ARIA attributes into native markup.
116
+
117
+ ## Known limitations
118
+
119
+ - Tooltip content must not be interactive. Use a dialog or disclosure for
120
+ actions or forms.
121
+ - Automated accessibility checks do not by themselves establish WCAG
122
+ conformance.
123
+
124
+ ## Standards
125
+
126
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
127
+ - WAI-ARIA Tooltip pattern: https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
128
+ - GOV.UK components: https://design-system.service.gov.uk/components/