@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.
- package/dist/Accordion/README.md +251 -0
- package/dist/Button/README.md +143 -0
- package/dist/Buttons/README.md +108 -0
- package/dist/Carousel/README.md +204 -0
- package/dist/Checkbox/README.md +140 -0
- package/dist/DateTimePicker/README.md +125 -0
- package/dist/DropDown/README.md +237 -0
- package/dist/FileUploader/README.md +224 -0
- package/dist/Icon/README.md +103 -0
- package/dist/Input/README.md +212 -0
- package/dist/Link/README.md +108 -0
- package/dist/Notification/README.md +141 -0
- package/dist/Pagination/README.md +147 -0
- package/dist/Panel/README.md +158 -0
- package/dist/Placeholder/README.md +124 -0
- package/dist/ProgressBar/README.md +137 -0
- package/dist/Radio/README.md +144 -0
- package/dist/RangeSlider/README.md +120 -0
- package/dist/ScrollArea/README.md +105 -0
- package/dist/Select/README.md +243 -0
- package/dist/SheetController/README.md +143 -0
- package/dist/Spinner/README.md +80 -0
- package/dist/SplitLayout/README.md +132 -0
- package/dist/Stepper/README.md +146 -0
- package/dist/Table/README.md +76 -0
- package/dist/Tabs/README.md +352 -0
- package/dist/Tabs/utils/index.d.cts +2 -1
- package/dist/Tag/README.md +90 -0
- package/dist/TaskBar/README.md +105 -0
- package/dist/TextArea/README.md +141 -0
- package/dist/ToastController/README.md +119 -0
- package/dist/Toggle/README.md +109 -0
- package/dist/Tooltip/README.md +128 -0
- package/dist/Walktour/README.md +188 -0
- package/dist/WindowController/README.md +190 -0
- package/package.json +5 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# RangeSlider
|
|
2
|
+
|
|
3
|
+
RangeSlider selects one numeric value from a bounded range using the native
|
|
4
|
+
range control. Its rendering contract follows the WAI-ARIA Slider pattern and
|
|
5
|
+
targets WCAG 2.2 Level AA.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
Use it when an approximate value is acceptable and the range is meaningful. Use
|
|
10
|
+
a numeric input when users must enter an exact value. Always supply a visible
|
|
11
|
+
`label` or a specific `ariaLabel`.
|
|
12
|
+
|
|
13
|
+
## Props
|
|
14
|
+
|
|
15
|
+
| PropName | Type | Required | Default | Description |
|
|
16
|
+
|---|---|---:|---:|---|
|
|
17
|
+
| `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component, part, and state classes. |
|
|
18
|
+
| `ariaLabel` | `string` | no | `"Range value"` | Names the slider when no visible label is supplied. Pass an empty string to omit the name. |
|
|
19
|
+
| `id` | `string` | no | generated | Stable native input ID. Generated IDs use the `rangeSlider` prefix. Explicit IDs are stored as `_${id}`. |
|
|
20
|
+
| `label` | `string` | no | `""` | Visible native label. |
|
|
21
|
+
| `labelPlacement` | aligned placement | no | `'top-start'` | Label placement. |
|
|
22
|
+
| `min` | `number` | yes | - | Minimum. |
|
|
23
|
+
| `max` | `number` | yes | - | Maximum. |
|
|
24
|
+
| `step` | `number` | no | browser default | Increment. |
|
|
25
|
+
| `value` | `number` | no | range midpoint | Current value. |
|
|
26
|
+
| `disabled` | `boolean` | no | - | Disables the native input and applies `is-disabled`. |
|
|
27
|
+
| `onChange` | `(value, event) => void` | no | noop | Reports each value change. |
|
|
28
|
+
| `onMouseUp` | `(value) => void` | no | noop | Compatibility callback reporting committed pointer or keyboard value. |
|
|
29
|
+
| `className` | `string` | no | `""` | Additional root classes. |
|
|
30
|
+
|
|
31
|
+
## Mediator state
|
|
32
|
+
|
|
33
|
+
| Property | Type | Description |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `ariaLabel` | `string \| undefined` | Normalized accessible name when no visible `label` is present. Empty labels are omitted. |
|
|
36
|
+
| `inputId` | `string` | Native input ID from an explicit `id` or a generated `rangeSlider` prefix. |
|
|
37
|
+
| `isDisabled` | `true \| undefined` | Is `true` when the slider is disabled; otherwise omitted. Renderers use this value directly for `.is-disabled` and native `disabled`. |
|
|
38
|
+
| `value` | `string` | Clamped current value as a string for the native range input. |
|
|
39
|
+
|
|
40
|
+
## Utility classes
|
|
41
|
+
|
|
42
|
+
Apply these classes through `className`.
|
|
43
|
+
|
|
44
|
+
| Class | Description |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `is-full-width` | Expands the slider to the available width. |
|
|
47
|
+
|
|
48
|
+
## CSS Variables
|
|
49
|
+
|
|
50
|
+
| Variable | Part | State | CSS property | Default | Description | Platforms |
|
|
51
|
+
|---|---|---|---|---|---|---|
|
|
52
|
+
| `--pw_RangeSlider_root_default_margin` | `root` | `default` | `margin` | shared margins | Component spacing. | Web, Capacitor, NativeScript |
|
|
53
|
+
| `--pw_RangeSlider_root_disabled_opacity` | `root` | `disabled` | `opacity` | `var(--pw_disabled_opacity)` | Disabled appearance. | Web, Capacitor, NativeScript |
|
|
54
|
+
| `--pw_RangeSlider_track_default_height` | `track` | `default` | `height` | `0.8em` | Track height. | Web, Capacitor, NativeScript |
|
|
55
|
+
| `--pw_RangeSlider_track_default_background-color` | `track` | `default` | `background-color` | `var(--pw_control_background-color)` | Track background. | Web, Capacitor, NativeScript |
|
|
56
|
+
| `--pw_RangeSlider_track_default_border-color` | `track` | `default` | `border-color` | `var(--pw_border-color)` | Track and thumb border. | Web, Capacitor, NativeScript |
|
|
57
|
+
| `--pw_RangeSlider_track_default_border-radius` | `track` | `default` | `border-radius` | `var(--pw_border-radius)` | Track corners. | Web, Capacitor, NativeScript |
|
|
58
|
+
| `--pw_RangeSlider_track_default_border-style` | `track` | `default` | `border-style` | `var(--pw_border-style)` | Track and thumb border style. | Web, Capacitor, NativeScript |
|
|
59
|
+
| `--pw_RangeSlider_track_default_border-width` | `track` | `default` | `border-width` | `var(--pw_border-width)` | Track and thumb border width. | Web, Capacitor, NativeScript |
|
|
60
|
+
| `--pw_RangeSlider_thumb_default_width` | `thumb` | `default` | `width` | `1.562em` | Pointer target width. | Web, Capacitor, NativeScript |
|
|
61
|
+
| `--pw_RangeSlider_thumb_default_height` | `thumb` | `default` | `height` | `1.562em` | Pointer target height. | Web, Capacitor, NativeScript |
|
|
62
|
+
| `--pw_RangeSlider_thumb_default_background-color` | `thumb` | `default` | `background-color` | track background | Thumb background. | Web, Capacitor, NativeScript |
|
|
63
|
+
| `--pw_RangeSlider_thumb_default_border-radius` | `thumb` | `default` | `border-radius` | `50%` | Thumb shape. | Web, Capacitor, NativeScript |
|
|
64
|
+
| `--pw_RangeSlider_thumb_default_transition-duration` | `thumb` | `default` | `transition-duration` | `var(--pw_transition-duration)` | Thumb visual duration. | Web, Capacitor; NativeScript adapter |
|
|
65
|
+
| `--pw_RangeSlider_label_default_color` | `label` | `default` | `color` | `var(--pw_label_text_color)` | Label colour. | Web, Capacitor, NativeScript |
|
|
66
|
+
| `--pw_RangeSlider_label_default_font-size` | `label` | `default` | `font-size` | `var(--pw_font-size)` | Label font size. | Web, Capacitor, NativeScript |
|
|
67
|
+
| `--pw_RangeSlider_label_default_font-weight` | `label` | `default` | `font-weight` | `var(--pw_semibold_font-weight)` | Label font weight. | Web, Capacitor, NativeScript |
|
|
68
|
+
| `--pw_RangeSlider_label_default_line-height` | `label` | `default` | `line-height` | `var(--pw_line-height)` | Label line height. | Web, Capacitor, NativeScript |
|
|
69
|
+
|
|
70
|
+
```xts
|
|
71
|
+
import { useBeforeUnmount, useMediator, withDefaultProps } from '@pastweb/x';
|
|
72
|
+
import { rangeSliderMediator } from '@pastweb/components-core/RangeSlider';
|
|
73
|
+
import { DEFAULT_PROPS } from './constants';
|
|
74
|
+
|
|
75
|
+
export function RangeSlider(props: RangeSliderProps) {
|
|
76
|
+
const p = withDefaultProps<RangeSliderDefaultProps>(props, DEFAULT_PROPS);
|
|
77
|
+
const m = useMediator<RangeSliderMediator>(rangeSliderMediator, p);
|
|
78
|
+
const s = m.state;
|
|
79
|
+
|
|
80
|
+
useBeforeUnmount(m.useBeforeUnmount);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div class={p.cls('RangeSlider', `label-${p.labelPlacement}`, { 'is-disabled': s.isDisabled }, p.className)}>
|
|
84
|
+
<input class={p.cls('slider')} type="range" id={s.inputId} min={p.min} max={p.max}
|
|
85
|
+
step={p.step} value={s.value} disabled={s.isDisabled}
|
|
86
|
+
aria-label={s.ariaLabel}
|
|
87
|
+
@change={event => m.onChange(event.target.value, event)}
|
|
88
|
+
@pointerup={m.onChangeEnd} @keyup={m.onChangeEnd} />
|
|
89
|
+
<label #if={p.label} class={p.cls('label', p.labelPlacement)} for={s.inputId}>{p.label}</label>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Keyboard interaction
|
|
96
|
+
|
|
97
|
+
- Arrow keys, `Home`, `End`, `PageUp`, and `PageDown` adjust the native range
|
|
98
|
+
value.
|
|
99
|
+
- `Tab` and `Shift+Tab` move sequential focus to the available slider.
|
|
100
|
+
|
|
101
|
+
## Accessibility notes
|
|
102
|
+
|
|
103
|
+
- The native input supplies slider role, values, keyboard operation, and
|
|
104
|
+
disabled behavior.
|
|
105
|
+
- The visible label is connected through `for`/`id`. When it is absent,
|
|
106
|
+
`ariaLabel` names the slider. The default name is `Range value`.
|
|
107
|
+
- Disabled state is reflected through `.is-disabled` and native `disabled`.
|
|
108
|
+
- Shared web styles provide a visible `:focus-visible` indicator, forced-colours
|
|
109
|
+
treatment, and reduced-motion behavior.
|
|
110
|
+
|
|
111
|
+
## Known limitations
|
|
112
|
+
|
|
113
|
+
- Automated accessibility checks do not by themselves establish WCAG
|
|
114
|
+
conformance.
|
|
115
|
+
|
|
116
|
+
## Standards
|
|
117
|
+
|
|
118
|
+
- WCAG 2.2: https://www.w3.org/TR/WCAG22/
|
|
119
|
+
- WAI-ARIA Slider pattern: https://www.w3.org/WAI/ARIA/apg/patterns/slider/
|
|
120
|
+
- GOV.UK form controls: https://design-system.service.gov.uk/components/
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# ScrollArea
|
|
2
|
+
|
|
3
|
+
ScrollArea provides a named, keyboard-focusable viewport for overflowing
|
|
4
|
+
content. Its rendering contract targets WCAG 2.2 Level AA.
|
|
5
|
+
|
|
6
|
+
## When to use
|
|
7
|
+
|
|
8
|
+
Use it when content must remain within a bounded layout. Avoid nested scroll
|
|
9
|
+
areas and never hide an axis when that would make content unreachable.
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
| PropName | Type | Required | Default | Description |
|
|
14
|
+
|---|---|---:|---:|---|
|
|
15
|
+
| `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and part classes. |
|
|
16
|
+
| `ariaLabel` | `string` | no | `"Scrollable content"` | Names the region. Pass an empty string to omit the name. Prefer a specific label. |
|
|
17
|
+
| `id` | `string` | no | - | Root ID. |
|
|
18
|
+
| `className` | `string` | no | `""` | Additional root classes. |
|
|
19
|
+
| `hideX` | `boolean` | no | - | Applies `hide-x` and suppresses horizontal overflow. |
|
|
20
|
+
| `hideY` | `boolean` | no | - | Applies `hide-y` and suppresses vertical overflow. |
|
|
21
|
+
| `scrollRight` | `boolean` | no | - | Applies `scroll-right` and requests initial horizontal end alignment. |
|
|
22
|
+
| `scrollBottom` | `boolean` | no | - | Applies `scroll-bottom` and requests initial vertical end alignment. |
|
|
23
|
+
| `contentPadding` | `number \| string` | no | - | Inline padding scoped to `.content`. |
|
|
24
|
+
| `tabIndex` | `number` | no | `0` | Makes the scroll viewport keyboard focusable. |
|
|
25
|
+
|
|
26
|
+
The default slot supplies scrollable content.
|
|
27
|
+
|
|
28
|
+
## Mediator state
|
|
29
|
+
|
|
30
|
+
| Property | Type | Description |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `ariaLabel` | `string \| undefined` | Normalized region name. Empty labels are omitted. |
|
|
33
|
+
|
|
34
|
+
`getContentStyle()` returns `{ padding }` when `contentPadding` is provided,
|
|
35
|
+
including `0`. `getScrollAlignment()` returns semantic `start` or `end` axes
|
|
36
|
+
for the renderer to apply after layout.
|
|
37
|
+
|
|
38
|
+
## CSS Variables
|
|
39
|
+
|
|
40
|
+
| Variable | Part | State | CSS property | Default | Description | Platforms |
|
|
41
|
+
|---|---|---|---|---|---|---|
|
|
42
|
+
| `--pw_ScrollArea_content_default_scrollbar-color` | `content` | `default` | `scrollbar-color` | `#e3e3e3 transparent` | Standards scrollbar colours. | Web, Capacitor |
|
|
43
|
+
| `--pw_ScrollArea_content_default_scrollbar-width` | `content` | `default` | `scrollbar-width` | `thin` | Standards scrollbar width. | Web, Capacitor |
|
|
44
|
+
| `--pw_ScrollArea_scrollbar_default_width` | `scrollbar` | `default` | `width` | `12px` | WebKit vertical width. | Web, Capacitor |
|
|
45
|
+
| `--pw_ScrollArea_scrollbar_default_height` | `scrollbar` | `default` | `height` | `12px` | WebKit horizontal height. | Web, Capacitor |
|
|
46
|
+
| `--pw_ScrollArea_scrollbar-track_default_background-color` | `scrollbar-track` | `default` | `background-color` | `#fcfcfc` | Track colour. | Web, Capacitor |
|
|
47
|
+
| `--pw_ScrollArea_scrollbar-thumb_default_background-color` | `scrollbar-thumb` | `default` | `background-color` | `#dfdfdf` | Thumb colour. | Web, Capacitor |
|
|
48
|
+
| `--pw_ScrollArea_scrollbar-thumb_hover_background-color` | `scrollbar-thumb` | `hover` | `background-color` | `#7a7a7a` | Hover thumb colour. | Web, Capacitor |
|
|
49
|
+
| `--pw_ScrollArea_scrollbar-thumb_default_border-radius` | `scrollbar-thumb` | `default` | `border-radius` | `6px` | Thumb corners. | Web, Capacitor |
|
|
50
|
+
| `--pw_ScrollArea_scrollbar-corner_default_background-color` | `scrollbar-corner` | `default` | `background-color` | `#fbfbfb` | Corner colour. | Web, Capacitor |
|
|
51
|
+
|
|
52
|
+
```xts
|
|
53
|
+
import { useMediator, useMounted, useRef, withDefaultProps } from '@pastweb/x';
|
|
54
|
+
import { scrollAreaMediator } from '@pastweb/components-core/ScrollArea';
|
|
55
|
+
import { DEFAULT_PROPS } from './constants';
|
|
56
|
+
|
|
57
|
+
export function ScrollArea(props: ScrollAreaProps) {
|
|
58
|
+
const p = withDefaultProps<ScrollAreaDefaultProps>(props, DEFAULT_PROPS);
|
|
59
|
+
const m = useMediator<ScrollAreaMediator>(scrollAreaMediator, p);
|
|
60
|
+
const s = m.state;
|
|
61
|
+
const content = useRef();
|
|
62
|
+
|
|
63
|
+
useMounted(() => {
|
|
64
|
+
const alignment = m.getScrollAlignment();
|
|
65
|
+
if (alignment.horizontal === 'end') content.value.scrollLeft = content.value.scrollWidth - content.value.clientWidth;
|
|
66
|
+
if (alignment.vertical === 'end') content.value.scrollTop = content.value.scrollHeight - content.value.clientHeight;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div id={p.id} class={p.cls('ScrollArea', {
|
|
71
|
+
'hide-x': p.hideX, 'hide-y': p.hideY,
|
|
72
|
+
'scroll-bottom': p.scrollBottom, 'scroll-right': p.scrollRight,
|
|
73
|
+
}, p.className)}>
|
|
74
|
+
<div #ref={content} class={p.cls('content')} #style={m.getContentStyle()}
|
|
75
|
+
role="region" aria-label={s.ariaLabel} tabindex={p.tabIndex}><slot /></div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Keyboard interaction
|
|
82
|
+
|
|
83
|
+
- `Tab` and `Shift+Tab` move sequential focus to the named viewport.
|
|
84
|
+
- Arrow keys, `PageUp`, `PageDown`, `Home`, and `End` scroll native overflow
|
|
85
|
+
when the viewport is focused.
|
|
86
|
+
|
|
87
|
+
## Accessibility notes
|
|
88
|
+
|
|
89
|
+
- The web viewport is a named `region` and is keyboard focusable.
|
|
90
|
+
- Renderers consume `getScrollAlignment()` after layout instead of reversing
|
|
91
|
+
visual content order.
|
|
92
|
+
- Hiding an axis must not make content unreachable.
|
|
93
|
+
- Shared web styles provide a visible `:focus-visible` indicator.
|
|
94
|
+
|
|
95
|
+
## Known limitations
|
|
96
|
+
|
|
97
|
+
- Nested scroll areas make keyboard scrolling harder to discover.
|
|
98
|
+
- Automated accessibility checks do not by themselves establish WCAG
|
|
99
|
+
conformance.
|
|
100
|
+
|
|
101
|
+
## Standards
|
|
102
|
+
|
|
103
|
+
- WCAG 2.2: https://www.w3.org/TR/WCAG22/
|
|
104
|
+
- WAI-ARIA landmark regions: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/
|
|
105
|
+
- GOV.UK components: https://design-system.service.gov.uk/components/
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Select
|
|
2
|
+
|
|
3
|
+
Select chooses one value from a listbox attached to a trigger. The trigger is
|
|
4
|
+
either a styled native button (`content`) or a custom control (`target`). Its
|
|
5
|
+
rendering contract follows the WAI-ARIA Combobox/Listbox pattern and targets
|
|
6
|
+
WCAG 2.2 Level AA.
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
Use Select when the user must pick one option from a known list. Do not use it
|
|
11
|
+
for free-text entry. Prefer a native radio group for a very small set of options
|
|
12
|
+
that should stay visible.
|
|
13
|
+
|
|
14
|
+
## Props
|
|
15
|
+
|
|
16
|
+
| PropName | Type | Required | Default | Description |
|
|
17
|
+
|---|---|---:|---:|---|
|
|
18
|
+
| `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and state classes. |
|
|
19
|
+
| `id` | `string` | no | generated | Supplies the host ID. Generated IDs use the `select` prefix. |
|
|
20
|
+
| `ariaLabel` | `string` | no | `"Select an option"` | Names the trigger when there is no visible `label`. Pass an empty string to omit the accessible name. |
|
|
21
|
+
| `name` | `string` | no | - | Optional form name. |
|
|
22
|
+
| `label` | `string` | no | `""` | Visible label. When set, it names the trigger and `ariaLabel` is omitted. |
|
|
23
|
+
| `labelPlacement` | `Placement` | no | `"top-start"` | Places the visible label relative to the trigger. |
|
|
24
|
+
| `placeholder` | `string` | no | `"Select"` | Default text of the styled `content` trigger when that slot is empty. |
|
|
25
|
+
| `disabled` | `boolean` | no | - | Makes the Select unavailable. |
|
|
26
|
+
| `loading` | `boolean` | no | - | Marks the Select busy and unavailable. |
|
|
27
|
+
| `className` | `string` | no | `""` | Adds classes to the root. The `is-loading` class also enables loading. |
|
|
28
|
+
| `tabIndex` | `number` | no | `0` | Controls sequential keyboard focus order. |
|
|
29
|
+
| `value` | `any` | no | - | Controlled selected value. |
|
|
30
|
+
| `offset` | `number` | no | `3` | Listbox offset from the trigger. |
|
|
31
|
+
| `closeOnSelect` | `boolean` | no | `true` | Closes the listbox after a selection. |
|
|
32
|
+
| `hideOnClick` | `boolean` | no | `true` | Closes the listbox after activation inside the options. |
|
|
33
|
+
| `onChange` | `(value: any) => void` | no | noop | Reports the selected value. |
|
|
34
|
+
|
|
35
|
+
## Utility classes
|
|
36
|
+
|
|
37
|
+
Apply these classes through `className`.
|
|
38
|
+
|
|
39
|
+
| Class | Description |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `is-rounded` | Uses the rounded radius token on `.selectButton` and the listbox. Custom `target` triggers are not styled by this class. |
|
|
42
|
+
| `is-full-width` | Expands the Select to the available width. |
|
|
43
|
+
|
|
44
|
+
## Mediator API
|
|
45
|
+
|
|
46
|
+
| Property | Type | Description |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `id` | `string` | Resolved host ID. Generated IDs use the `select` prefix; an explicit `id` is exposed as `_${id}`. |
|
|
49
|
+
| `hide` | `() => void` | Closes the listbox. |
|
|
50
|
+
|
|
51
|
+
## Mediator state
|
|
52
|
+
|
|
53
|
+
| Property | Type | Description |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `ariaLabel` | `string \| undefined` | Normalized trigger accessible name. Omitted when `label` is set or `ariaLabel` is empty. |
|
|
56
|
+
| `highlightIndex` | `number \| null` | Keyboard highlight index in the listbox. |
|
|
57
|
+
| `inputId` | `string` | Root ID. Same value as mediator `id`. |
|
|
58
|
+
| `isDisabled` | `true \| undefined` | Is `true` when the Select is disabled; otherwise omitted. |
|
|
59
|
+
| `isLoading` | `true \| undefined` | Is `true` when the Select is loading; otherwise omitted. |
|
|
60
|
+
| `isOpen` | `boolean` | Whether the listbox is open. |
|
|
61
|
+
| `isUnavailable` | `true \| undefined` | Is `true` when disabled or loading. |
|
|
62
|
+
| `labelId` | `string` | Visible-label relationship ID in the form `label${id}`. |
|
|
63
|
+
| `listboxId` | `string` | Listbox relationship ID in the form `listbox${id}`. |
|
|
64
|
+
| `triggerId` | `string` | Trigger relationship ID in the form `trigger${id}`. |
|
|
65
|
+
| `value` | `any` | Current selected value. |
|
|
66
|
+
|
|
67
|
+
## Mediator context
|
|
68
|
+
|
|
69
|
+
SelectItem reads this API through the renderer context hook. Live getters stay synchronized with mediator state.
|
|
70
|
+
|
|
71
|
+
| Context property | Type | Description |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `hide` | `() => void` | Closes the listbox. |
|
|
74
|
+
| `onChange` | `(value: any) => void` | Selects a value. |
|
|
75
|
+
| `onOptionKeyDown` | `(event, value, disabled?) => void` | Handles option keyboard activation. |
|
|
76
|
+
| `registerItem` | `() => number` | Registers a SelectItem and returns its index. |
|
|
77
|
+
| `state` | `SelectState` | Live mediator state for descendants. |
|
|
78
|
+
|
|
79
|
+
## Slots
|
|
80
|
+
|
|
81
|
+
Provide `content` for the styled button trigger, or `target` for a custom
|
|
82
|
+
trigger. Each trigger is mounted only when its slot has content. If neither
|
|
83
|
+
slot is provided, no trigger is rendered. Do not put another button inside
|
|
84
|
+
either trigger slot.
|
|
85
|
+
|
|
86
|
+
| SlotName | Description | Default content |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `target` | Replaces the native trigger. It receives the trigger ID, ARIA state, keyboard handler, toggle handler, and tab index through slot props; the replacement must apply them to one focusable control. | The native `content` trigger. |
|
|
89
|
+
| `content` | Labelled content of the styled native-button trigger (`.selectButton`), which includes a decorative arrow. When present, the button is rendered. | `placeholder` |
|
|
90
|
+
| `options` | Supplies listbox options such as `SelectItem`. | Empty. |
|
|
91
|
+
| default | Supplies additional listbox content. | Empty. |
|
|
92
|
+
|
|
93
|
+
## CSS Variables
|
|
94
|
+
|
|
95
|
+
| Variable | Part | State | CSS property | Default | Description | Platforms |
|
|
96
|
+
|---|---|---|---|---|---|---|
|
|
97
|
+
| `--pw_Select_root_default_min-width` | `root` | `default` | `min-width` | `12em` | Minimum control width. The control can grow with trigger content. | Web, Capacitor, NativeScript |
|
|
98
|
+
| `--pw_Select_options_default_width` | `options` | `default` | `width` | `max-content` | Listbox width, sized to option content. | Web, Capacitor |
|
|
99
|
+
| `--pw_Select_options_default_max-height` | `options` | `default` | `max-height` | `16em` | Listbox max height; overflow scrolls vertically. | Web, Capacitor |
|
|
100
|
+
| `--pw_Select_root_default_background-color` | `root` | `default` | `background-color` | `var(--pw_control_background-color)` | Control and listbox background. | Web, Capacitor, NativeScript |
|
|
101
|
+
| `--pw_Select_root_default_color` | `root` | `default` | `color` | `var(--pw_text_strong_color)` | Control and option foreground. | Web, Capacitor, NativeScript |
|
|
102
|
+
| `--pw_Select_label_default_color` | `label` | `default` | `color` | `var(--pw_label_text_color)` | Visible label colour. | Web, Capacitor, NativeScript |
|
|
103
|
+
| `--pw_Select_root_default_border-color` | `root` | `default` | `border-color` | `var(--pw_border-color)` | Listbox border colour. | Web, Capacitor, NativeScript |
|
|
104
|
+
| `--pw_Select_root_default_border-width` | `root` | `default` | `border-width` | `var(--pw_border-width)` | Listbox border width. | Web, Capacitor, NativeScript |
|
|
105
|
+
| `--pw_Select_root_default_border-style` | `root` | `default` | `border-style` | `var(--pw_border-style)` | Listbox border style. | Web, Capacitor, NativeScript |
|
|
106
|
+
| `--pw_Select_root_default_border-radius` | `root` | `default` | `border-radius` | `var(--pw_border-radius)` | Listbox corner radius. | Web, Capacitor, NativeScript |
|
|
107
|
+
| `--pw_Select_root_default_padding` | `root` | `default` | `padding` | `var(--pw_padding)` | Option padding. | Web, Capacitor, NativeScript |
|
|
108
|
+
| `--pw_Select_root_default_margin` | `root` | `default` | `margin` | vertical/horizontal margin | Outer spacing. | Web, Capacitor, NativeScript |
|
|
109
|
+
| `--pw_Select_root_disabled_opacity` | `root` | `disabled` | `opacity` | `var(--pw_disabled_opacity)` | Disabled opacity. | Web, Capacitor, NativeScript |
|
|
110
|
+
|
|
111
|
+
```xts
|
|
112
|
+
import {
|
|
113
|
+
setContext,
|
|
114
|
+
useBeforeUnmount,
|
|
115
|
+
useMediator,
|
|
116
|
+
useMounted,
|
|
117
|
+
useRef,
|
|
118
|
+
withDefaultProps,
|
|
119
|
+
} from '@pastweb/x';
|
|
120
|
+
import { selectMediator } from '@pastweb/components-core/Select';
|
|
121
|
+
import { DEFAULT_PROPS, SELECT_CONTEXT_KEY } from './constants';
|
|
122
|
+
|
|
123
|
+
export function Select(props: SelectProps) {
|
|
124
|
+
const p = withDefaultProps<SelectDefaultProps>(props, DEFAULT_PROPS);
|
|
125
|
+
const m = useMediator<SelectMediator>(selectMediator, p);
|
|
126
|
+
const s = m.state;
|
|
127
|
+
const c = m.getContext();
|
|
128
|
+
const triggerReference = useRef(null);
|
|
129
|
+
const listboxReference = useRef(null);
|
|
130
|
+
|
|
131
|
+
setContext(SELECT_CONTEXT_KEY, c);
|
|
132
|
+
useMounted(() => {
|
|
133
|
+
if (triggerReference.value && listboxReference.value) {
|
|
134
|
+
m.useMounted(triggerReference.value, listboxReference.value);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
useBeforeUnmount(m.useBeforeUnmount);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
id={s.inputId}
|
|
142
|
+
class={p.cls('Select', `label-${p.labelPlacement}`, p.className, {
|
|
143
|
+
'is-disabled': s.isDisabled,
|
|
144
|
+
'is-loading': s.isLoading,
|
|
145
|
+
'is-open': s.isOpen,
|
|
146
|
+
})}
|
|
147
|
+
>
|
|
148
|
+
<div class={p.cls('selectControl')}>
|
|
149
|
+
<slot
|
|
150
|
+
name='target'
|
|
151
|
+
props={{
|
|
152
|
+
ariaBusy: s.isLoading || undefined,
|
|
153
|
+
ariaControls: s.listboxId,
|
|
154
|
+
ariaExpanded: s.isOpen,
|
|
155
|
+
ariaHasPopup: 'listbox',
|
|
156
|
+
ariaLabel: s.ariaLabel,
|
|
157
|
+
ariaLabelledBy: p.label ? s.labelId : undefined,
|
|
158
|
+
ariaDisabled: s.isUnavailable,
|
|
159
|
+
id: s.triggerId,
|
|
160
|
+
onKeyDown: m.onKeyDown,
|
|
161
|
+
onToggle: m.onToggle,
|
|
162
|
+
tabIndex: p.tabIndex,
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
<button
|
|
166
|
+
#ref={triggerReference}
|
|
167
|
+
id={s.triggerId}
|
|
168
|
+
type='button'
|
|
169
|
+
class={p.cls('selectButton')}
|
|
170
|
+
tabIndex={p.tabIndex}
|
|
171
|
+
disabled={s.isUnavailable}
|
|
172
|
+
aria-haspopup='listbox'
|
|
173
|
+
aria-expanded={s.isOpen}
|
|
174
|
+
aria-controls={s.listboxId}
|
|
175
|
+
aria-labelledby={p.label ? s.labelId : undefined}
|
|
176
|
+
aria-label={s.ariaLabel}
|
|
177
|
+
aria-busy={s.isLoading || undefined}
|
|
178
|
+
@keydown={m.onKeyDown}
|
|
179
|
+
@click={m.onToggle}
|
|
180
|
+
>
|
|
181
|
+
<slot name='content'>{p.placeholder}</slot>
|
|
182
|
+
<span class={p.cls('arrow')} aria-hidden='true'>▼</span>
|
|
183
|
+
</button>
|
|
184
|
+
</slot>
|
|
185
|
+
<ul
|
|
186
|
+
#ref={listboxReference}
|
|
187
|
+
id={s.listboxId}
|
|
188
|
+
role='listbox'
|
|
189
|
+
class={p.cls('options')}
|
|
190
|
+
aria-labelledby={p.label ? s.labelId : s.triggerId}
|
|
191
|
+
hidden={!s.isOpen}
|
|
192
|
+
>
|
|
193
|
+
<slot name='options' />
|
|
194
|
+
<slot />
|
|
195
|
+
</ul>
|
|
196
|
+
</div>
|
|
197
|
+
<label #if={p.label} id={s.labelId} class={p.cls('label', p.labelPlacement)} for={s.triggerId}>
|
|
198
|
+
{p.label}
|
|
199
|
+
</label>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Keyboard interaction
|
|
206
|
+
|
|
207
|
+
- `Enter` or `Space` on the trigger toggles the listbox.
|
|
208
|
+
- `ArrowDown` opens and highlights the first option, or moves down.
|
|
209
|
+
- `ArrowUp` opens and highlights the last option, or moves up.
|
|
210
|
+
- `Home` and `End` move to the first and last option while open.
|
|
211
|
+
- `Escape` closes an open listbox and restores trigger focus.
|
|
212
|
+
- `Tab` closes an open listbox and continues document order.
|
|
213
|
+
|
|
214
|
+
## Accessibility notes
|
|
215
|
+
|
|
216
|
+
- Provide either a `content` trigger or a `target` trigger. Each trigger exposes
|
|
217
|
+
expanded, controls, disabled, and busy state.
|
|
218
|
+
- The `content` trigger is a native button with a decorative arrow. Do not place
|
|
219
|
+
another button in the `content` slot.
|
|
220
|
+
- A `target` replacement receives the required trigger semantics as slot props. Apply them to one focusable control and do not nest controls.
|
|
221
|
+
- Visible `label` text is the accessible name. `ariaLabel` is used only when there is no visible label.
|
|
222
|
+
- The visible `label` is associated with the `content` button through `for` and
|
|
223
|
+
`triggerId`. The `target` trigger is named with `aria-labelledby` when a
|
|
224
|
+
visible label is present.
|
|
225
|
+
- Disabled and loading states are unavailable and cannot open the listbox.
|
|
226
|
+
- Closed options use `hidden` so they leave the accessibility tree and tab order.
|
|
227
|
+
- The listbox is sized to option content and at least as wide as the trigger. It
|
|
228
|
+
flips only to the opposite edge when the viewport has no room below, and
|
|
229
|
+
scrolls vertically when options exceed the max height.
|
|
230
|
+
- Shared web styles provide a visible focus indicator, forced-colours treatment,
|
|
231
|
+
and reduced-motion behavior.
|
|
232
|
+
|
|
233
|
+
## Known limitations
|
|
234
|
+
|
|
235
|
+
- Content-slot and target-slot content should not introduce a nested button.
|
|
236
|
+
- Listbox positioning uses web layout measurement.
|
|
237
|
+
- Automated accessibility checks do not by themselves establish WCAG conformance.
|
|
238
|
+
|
|
239
|
+
## Standards
|
|
240
|
+
|
|
241
|
+
- WCAG 2.2: https://www.w3.org/TR/WCAG22/
|
|
242
|
+
- WAI-ARIA Combobox/Listbox pattern: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
|
|
243
|
+
- GOV.UK select: https://design-system.service.gov.uk/components/select/
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# SheetController
|
|
2
|
+
|
|
3
|
+
SheetController presents an animated, optionally modal dialog surface from an
|
|
4
|
+
edge of its portal container. Its rendering contract follows the WAI-ARIA Dialog
|
|
5
|
+
(Modal) pattern and targets WCAG 2.2 Level AA.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
Use it for secondary tasks that need more space than a popover without
|
|
10
|
+
navigating away. Do not use it for essential content that should remain
|
|
11
|
+
permanently visible. A modal sheet must include an obvious close control inside
|
|
12
|
+
its content.
|
|
13
|
+
|
|
14
|
+
## Props
|
|
15
|
+
|
|
16
|
+
| PropName | Type | Required | Default | Description |
|
|
17
|
+
|---|---|---:|---:|---|
|
|
18
|
+
| `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and part classes. |
|
|
19
|
+
| `id` | `string` | no | generated | Supplies the dialog ID. Generated IDs use the `sheetController` prefix. Explicit IDs are stored as `_${id}`. |
|
|
20
|
+
| `ariaLabel` | `string` | no | `'Sheet'` | Accessible dialog name. Pass an empty string to omit the name. |
|
|
21
|
+
| `className` | `string` | no | `''` | Additional root classes. |
|
|
22
|
+
| `direction` | `'left' \| 'right' \| 'up' \| 'down'` | no | `'left'` | Entry and exit edge. |
|
|
23
|
+
| `backdrop` | `boolean` | no | - | Displays the native backdrop button and applies `has-backdrop`. |
|
|
24
|
+
| `modal` | `boolean` | no | - | Enables `aria-modal`, focus containment, and `is-modal`. |
|
|
25
|
+
| `closeOnBackdropClick` | `boolean` | no | `true` | Allows backdrop activation to close. |
|
|
26
|
+
| `closeOnEscape` | `boolean` | no | `true` | Allows Escape to close unless the event is prevented. |
|
|
27
|
+
| `animationTime` | `number` | no | `400` | Fallback close duration in milliseconds. |
|
|
28
|
+
| `width`, `height` | `number \| string` | no | - | Inline sheet dimensions. |
|
|
29
|
+
| `top`, `right`, `bottom`, `left` | `number \| string` | no | - | Inline sheet offsets. |
|
|
30
|
+
| `portal`, `portalElement`, `entryId` | portal integration | no | - | Optional host integration used for update/remove/close events. |
|
|
31
|
+
| `onClose` | `() => void` | no | noop | Reports completed closure once. |
|
|
32
|
+
| `onKeyDown` | `(event: SheetKeyboardEvent) => void` | no | noop | Observes keyboard input before default Escape handling. |
|
|
33
|
+
|
|
34
|
+
The default slot supplies dialog content. Include a native close button that
|
|
35
|
+
calls `useSheetContext().close()`.
|
|
36
|
+
|
|
37
|
+
## Mediator state
|
|
38
|
+
|
|
39
|
+
| Property | Type | Description |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `ariaLabel` | `string \| undefined` | Normalized dialog name. Empty labels are omitted. |
|
|
42
|
+
| `id` | `string` | Dialog ID from an explicit `id` or a generated `sheetController` prefix. |
|
|
43
|
+
| `isClosed` | `boolean` | Is `true` after closure is finalized and the surface should unmount. |
|
|
44
|
+
| `isClosing` | `boolean` | Is `true` during the exit animation; content is inert. |
|
|
45
|
+
| `isOpen` | `boolean` | Is `true` while the sheet is shown and not closing. |
|
|
46
|
+
|
|
47
|
+
## CSS classes
|
|
48
|
+
|
|
49
|
+
| Class | Applied when | Notes |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `SheetController` | Always | Root overlay class. |
|
|
52
|
+
| `has-backdrop` | `backdrop` is `true` | Backdrop overlay is present. |
|
|
53
|
+
| `is-open` | `isOpen` is `true` | Sheet is visible. |
|
|
54
|
+
| `is-modal` | `modal` is `true` | Modal focus containment is active. |
|
|
55
|
+
| `is-closing` | `isClosing` is `true` | Exit animation; pointer events disabled. |
|
|
56
|
+
|
|
57
|
+
## CSS Variables
|
|
58
|
+
|
|
59
|
+
| Variable | Part | State | CSS property | Default | Description | Platforms |
|
|
60
|
+
|---|---|---|---|---|---|---|
|
|
61
|
+
| `--pw_SheetController_root_default_width` | `root` | `default` | `width` | `100%` | Overlay width. | Web, Capacitor, NativeScript |
|
|
62
|
+
| `--pw_SheetController_root_default_height` | `root` | `default` | `height` | `100%` | Overlay height. | Web, Capacitor, NativeScript |
|
|
63
|
+
| `--pw_SheetController_backdrop_default_background-color` | `backdrop` | `default` | `background-color` | shared backdrop | Backdrop colour. | Web, Capacitor, NativeScript |
|
|
64
|
+
| `--pw_SheetController_sheet_default_background-color` | `sheet` | `default` | `background-color` | shared background | Sheet background. | Web, Capacitor, NativeScript |
|
|
65
|
+
| `--pw_SheetController_sheet_default_color` | `sheet` | `default` | `color` | shared text | Sheet foreground. | Web, Capacitor, NativeScript |
|
|
66
|
+
| `--pw_SheetController_sheet_default_box-shadow` | `sheet` | `default` | `box-shadow` | elevation shadow | Sheet elevation. | Web, Capacitor |
|
|
67
|
+
| `--pw_SheetController_sheet_default_animation-duration` | `sheet` | `default` | `animation-duration` | `400ms` | Edge animation duration. | Web, Capacitor; native adapter |
|
|
68
|
+
| `--pw_SheetController_sheet_default_animation-timing-function` | `sheet` | `default` | `animation-timing-function` | `ease` | Edge animation timing. | Web, Capacitor; native adapter |
|
|
69
|
+
| `--pw_SheetController_sheet_default_animation-fill-mode` | `sheet` | `default` | `animation-fill-mode` | `forwards` | Edge animation fill. | Web, Capacitor; native adapter |
|
|
70
|
+
| `--pw_SheetController_sheet_default_outline` | `sheet` | `default` | `outline` | shared outline | Keyboard focus indicator. | Web, Capacitor |
|
|
71
|
+
| `--pw_SheetController_sheet_default_outline-offset` | `sheet` | `default` | `outline-offset` | `0.125em` | Focus outline offset. | Web, Capacitor |
|
|
72
|
+
|
|
73
|
+
```xts
|
|
74
|
+
import { setContext, useBeforeUnmount, useMediator, useMounted, useRef, withDefaultProps } from '@pastweb/x';
|
|
75
|
+
import { sheetControllerMediator, SHEET_CONTEXT_KEY } from '@pastweb/components-core/SheetController';
|
|
76
|
+
import { DEFAULT_PROPS } from './constants';
|
|
77
|
+
|
|
78
|
+
export function SheetController(props: SheetControllerProps) {
|
|
79
|
+
const p = withDefaultProps<SheetControllerDefaultProps>(props, DEFAULT_PROPS);
|
|
80
|
+
const m = useMediator<SheetControllerMediator>(sheetControllerMediator, p);
|
|
81
|
+
const s = m.state;
|
|
82
|
+
const c = m.getContext();
|
|
83
|
+
const sheet = useRef();
|
|
84
|
+
|
|
85
|
+
setContext(SHEET_CONTEXT_KEY, c);
|
|
86
|
+
useMounted(() => m.useMounted(sheet.value, p.portalElement));
|
|
87
|
+
useBeforeUnmount(m.useBeforeUnmount);
|
|
88
|
+
|
|
89
|
+
if (s.isClosed) return null;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div class={p.cls('SheetController', {
|
|
93
|
+
'has-backdrop': p.backdrop, 'is-closing': s.isClosing,
|
|
94
|
+
'is-modal': p.modal, 'is-open': s.isOpen,
|
|
95
|
+
}, p.className)} #style={{ animation-duration: `${p.animationTime}ms` }}>
|
|
96
|
+
<button #if={p.backdrop} type="button" class={p.cls('backdrop')}
|
|
97
|
+
tabindex={-1} aria-hidden="true" @click={m.onBackdrop} />
|
|
98
|
+
<div #ref={sheet} id={m.id} class={p.cls('sheet', {
|
|
99
|
+
[`sheet-open-${p.direction}`]: s.isOpen,
|
|
100
|
+
[`sheet-close-${p.direction}`]: s.isClosing,
|
|
101
|
+
})} role="dialog" aria-label={s.ariaLabel} aria-modal={p.modal || undefined}
|
|
102
|
+
aria-hidden={s.isClosing || undefined} inert={s.isClosing || undefined} tabindex={-1}
|
|
103
|
+
@keydown={m.onKeyDown} @animationend={m.onTransitionEnd}>
|
|
104
|
+
<slot />
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Keyboard interaction
|
|
112
|
+
|
|
113
|
+
- Focus moves to the sheet when mounted.
|
|
114
|
+
- `Escape` closes when `closeOnEscape` is enabled, unless the consumer prevents the event.
|
|
115
|
+
- Modal sheets keep `Tab` and `Shift+Tab` inside their focusable content.
|
|
116
|
+
- Focus returns to the previously focused element when closing or unmounting.
|
|
117
|
+
|
|
118
|
+
## Accessibility notes
|
|
119
|
+
|
|
120
|
+
- The web renderer exposes a named `dialog`. `aria-modal` is set only when `modal` is true.
|
|
121
|
+
- The backdrop is a native `button` excluded from the tab order and accessibility tree.
|
|
122
|
+
- Closing content becomes inert immediately; animation completion or the timer finalizes portal removal.
|
|
123
|
+
- Shared web styles keep a visible `:focus-visible` indicator and disable non-essential motion under `prefers-reduced-motion`.
|
|
124
|
+
|
|
125
|
+
## Known limitations
|
|
126
|
+
|
|
127
|
+
- Nested sheets require distinct labels so dialog announcements remain understandable.
|
|
128
|
+
- A modal sheet still depends on its content to provide a visible close control.
|
|
129
|
+
- Automated accessibility checks do not by themselves establish WCAG conformance.
|
|
130
|
+
|
|
131
|
+
## Native renderer mapping
|
|
132
|
+
|
|
133
|
+
NativeScript maps the sheet to an accessible modal or non-modal native container,
|
|
134
|
+
uses native focus containment and restoration, maps edge directions to native
|
|
135
|
+
animations, makes closing content unavailable immediately, and calls
|
|
136
|
+
`onTransitionEnd` when animation completes. Back gestures and Escape-compatible
|
|
137
|
+
keys call the same close intent.
|
|
138
|
+
|
|
139
|
+
## Standards
|
|
140
|
+
|
|
141
|
+
- WCAG 2.2: https://www.w3.org/TR/WCAG22/
|
|
142
|
+
- WAI-ARIA Dialog (Modal) pattern: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
|
|
143
|
+
- GOV.UK components: https://design-system.service.gov.uk/components/
|