@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,212 @@
1
+ # Input
2
+
3
+ Input renders a labelled native text, search, password, email, or number field
4
+ with optional affixes, loading state, and number step controls. Its rendering
5
+ contract targets WCAG 2.2 Level AA.
6
+
7
+ ## When to use
8
+
9
+ Use Input for short, single-line values. Use TextArea for multi-line content and
10
+ a purpose-built control for dates or constrained choices. A visible label is
11
+ preferred; placeholders do not replace labels.
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 | `"Input"` | Names the input when no visible `label` is supplied. Pass an empty string to omit the accessible name, including when an external label is associated through `id`. |
19
+ | `className` | `string` | no | `""` | Adds classes to the root. The `is-loading` class is also treated as a loading signal. |
20
+ | `autocomplete` | `string` | no | - | Supplies the native autocomplete token. Use it for personal data and sign-in fields. |
21
+ | `tabIndex` | `number` | no | `0` | Controls sequential focus order. Prefer document order. |
22
+ | `type` | `'text' \| 'search' \| 'password' \| 'email' \| 'number'` | no | `'text'` | Selects native input behavior. |
23
+ | `placeholder` | `string` | no | `""` | Supplies a short input hint. Do not use it as the only label. |
24
+ | `disabled` | `boolean` | no | - | Makes the field unavailable. |
25
+ | `loading` | `boolean` | no | - | Marks the field busy and temporarily unavailable. |
26
+ | `required` | `boolean` | no | - | Marks the value as required. |
27
+ | `name` | `string` | no | - | Supplies the form field name. |
28
+ | `id` | `string` | no | generated | Supplies the native input ID. Generated IDs use the `input` prefix. |
29
+ | `min` | `number` | no | - | Native minimum for number inputs. |
30
+ | `max` | `number` | no | - | Native maximum for number inputs. |
31
+ | `step` | `number` | no | - | Native step for number inputs. |
32
+ | `value` | `string` | no | - | Controls the input value. |
33
+ | `label` | `string` | no | `""` | Supplies the visible accessible label. |
34
+ | `labelPlacement` | `Placement` | no | `'top-start'` | Positions the visible label. |
35
+ | `incrementTitle` | `string` | no | `"Increment"` | Names the optional number increment control. |
36
+ | `decrementTitle` | `string` | no | `"Decrement"` | Names the optional number decrement control. |
37
+ | `onChange` | `(value: string, event?: unknown) => void` | no | noop | Reports value input. Programmatic number stepping has no renderer event. |
38
+ | `onKeyDown` | `(event) => void` | no | noop | Reports available-field keyboard input. |
39
+
40
+ ## Mediator state
41
+
42
+ | Property | Type | Description |
43
+ |---|---|---|
44
+ | `ariaLabel` | `string \| undefined` | Normalized accessible name when no visible `label` is present. Empty labels are omitted. |
45
+ | `disableDownBtn` | `true \| undefined` | Is `true` when the number value is at `min`; otherwise omitted. |
46
+ | `disableUpBtn` | `true \| undefined` | Is `true` when the number value is at `max`; otherwise omitted. |
47
+ | `inputId` | `string` | Native input ID from an explicit `id` or a generated `input` prefix. |
48
+ | `isDisabled` | `true \| undefined` | Is `true` when the Input is disabled; otherwise omitted. Renderers use this value directly for `.is-disabled`, `aria-disabled`, and native `disabled` when loading is not also blocking the field. |
49
+ | `isLoading` | `true \| undefined` | Is `true` when the Input is busy; otherwise omitted. Renderers use this value directly for `.is-loading` and `aria-busy`. |
50
+
51
+ ## Slots
52
+
53
+ | SlotName | Description | Default content |
54
+ |---|---|---|
55
+ | `left` | Supplies a leading affix inside `.leftComponent`. | Empty. |
56
+ | `right` | Supplies a trailing affix inside `.rightComponent`. | Empty. |
57
+ | `loading` | Supplies the politely announced busy status. | A `loading` status region containing `Loading...`. |
58
+
59
+ Affix content is not automatically decorative. Renderers must preserve an
60
+ interactive affix's accessible name and keyboard behavior. Decorative icons in
61
+ an affix should be marked `aria-hidden` by the consumer.
62
+
63
+ ## Utility classes
64
+
65
+ Apply these classes through `className`.
66
+
67
+ | Class | Description |
68
+ |---|---|
69
+ | `is-rounded` | Uses the rounded input radius token. |
70
+ | `is-full-width` | Expands the field to the available width. |
71
+ | `is-outlined` | Inverts affix icon colour against the control background. |
72
+ | `is-loading` | Compatibility signal for the loading state. Prefer the `loading` prop. |
73
+
74
+ ## CSS Variables
75
+
76
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
77
+ |---|---|---|---|---|---|---|
78
+ | `--pw_Input_root_default_margin` | `root` | `default` | `margin` | `var(--pw_vertical_margin)` | Component spacing. | Web, Capacitor, NativeScript |
79
+ | `--pw_Input_root_disabled_opacity` | `root` | `disabled` | `opacity` | `var(--pw_disabled_opacity)` | Disabled opacity. | Web, Capacitor, NativeScript |
80
+ | `--pw_Input_container_default_min-width` | `container` | `default` | `min-width` | `5em` | Minimum control width. | Web, Capacitor, NativeScript |
81
+ | `--pw_Input_container_default_min-height` | `container` | `default` | `min-height` | `2.75em` | Minimum control height, with a 44px web target. | Web, Capacitor, NativeScript |
82
+ | `--pw_Input_container_default_background-color` | `container` | `default` | `background-color` | `var(--pw_control_background-color)` | Control background. | Web, Capacitor, NativeScript |
83
+ | `--pw_Input_container_default_color` | `container` | `default` | `color` | `var(--pw_text_color)` | Input and affix foreground. | Web, Capacitor, NativeScript |
84
+ | `--pw_Input_container_default_border-color` | `container` | `default` | `border-color` | `var(--pw_border-color)` | Control border colour. | Web, Capacitor, NativeScript |
85
+ | `--pw_Input_container_default_border-width` | `container` | `default` | `border-width` | `var(--pw_border-width)` | Control border width. | Web, Capacitor, NativeScript |
86
+ | `--pw_Input_container_default_border-style` | `container` | `default` | `border-style` | `var(--pw_border-style)` | Control border style. | Web, Capacitor, NativeScript |
87
+ | `--pw_Input_container_default_border-radius` | `container` | `default` | `border-radius` | `var(--pw_border-radius)` | Control corner radius. | Web, Capacitor, NativeScript |
88
+ | `--pw_Input_container_rounded_border-radius` | `container` | `rounded` | `border-radius` | `var(--pw_rounded_border-radius)` | Rounded variant radius. | Web, Capacitor, NativeScript |
89
+ | `--pw_Input_container_hover_box-shadow` | `container` | `hover` | `box-shadow` | shared inset shadow | Pointer hover treatment. | Web, Capacitor |
90
+ | `--pw_Input_container_focus-visible_box-shadow` | `container` | `focus-visible` | `box-shadow` | shared focus shadow | Keyboard focus treatment. | Web, Capacitor |
91
+ | `--pw_Input_container_default_transition-duration` | `container` | `default` | `transition-duration` | `var(--pw_transition-duration)` | Visual state duration. | Web, Capacitor; NativeScript adapter |
92
+ | `--pw_Input_input_default_padding` | `input` | `default` | `padding` | `var(--pw_padding)` | Input spacing. | Web, Capacitor, NativeScript |
93
+ | `--pw_Input_input_default_font-size` | `input` | `default` | `font-size` | `var(--pw_font-size)` | Input text size. | Web, Capacitor, NativeScript |
94
+ | `--pw_Input_input_default_font-weight` | `input` | `default` | `font-weight` | `var(--pw_font-weight)` | Input text weight. | Web, Capacitor, NativeScript |
95
+ | `--pw_Input_affix_default_padding` | `affix` | `default` | `padding` | `var(--pw_horizontal_padding)` | Affix spacing. | Web, Capacitor, NativeScript |
96
+ | `--pw_Input_label_default_color` | `label` | `default` | `color` | `var(--pw_label_text_color)` | Label foreground. | Web, Capacitor, NativeScript |
97
+ | `--pw_Input_label_default_font-size` | `label` | `default` | `font-size` | `var(--pw_font-size)` | Label font size. | Web, Capacitor, NativeScript |
98
+ | `--pw_Input_label_default_font-weight` | `label` | `default` | `font-weight` | `var(--pw_semibold_font-weight)` | Label weight. | Web, Capacitor, NativeScript |
99
+ | `--pw_Input_number-buttons_default_width` | `number-buttons` | `default` | `width` | `2em` | Step-control column width. | Web, Capacitor, NativeScript |
100
+ | `--pw_Input_number-button_default_min-height` | `number-button` | `default` | `min-height` | `1.5em` | Step-control minimum height. | Web, Capacitor, NativeScript |
101
+ | `--pw_Input_number-button_default_font-size` | `number-button` | `default` | `font-size` | `0.813em` | Step-control glyph size. | Web, Capacitor, NativeScript |
102
+
103
+ ```xts
104
+ import {
105
+ useBeforeUnmount,
106
+ useMediator,
107
+ useMounted,
108
+ useRef,
109
+ withDefaultProps,
110
+ } from '@pastweb/x';
111
+ import { inputMediator } from '@pastweb/components-core/Input';
112
+ import { DEFAULT_PROPS } from './constants';
113
+
114
+ export function Input(props: InputProps) {
115
+ const p = withDefaultProps<InputDefaultProps>(props, DEFAULT_PROPS);
116
+ const m = useMediator<InputMediator>(inputMediator, p);
117
+ const s = m.state;
118
+ const inputRef = useRef(null);
119
+
120
+ useMounted(() => m.useMounted(inputRef.value));
121
+ useBeforeUnmount(m.useBeforeUnmount);
122
+
123
+ return (
124
+ <div
125
+ class={p.cls('Input', `label-${p.labelPlacement}`, p.className, {
126
+ 'is-disabled': s.isDisabled,
127
+ 'is-loading': s.isLoading,
128
+ })}
129
+ aria-busy={s.isLoading}
130
+ >
131
+ <div class={p.cls('inputContainer')}>
132
+ <slot name="left" map={content => <span class={p.cls('leftComponent')}>{content}</span>} />
133
+ <input
134
+ #ref={inputRef}
135
+ class={p.cls('inputElement')}
136
+ type={p.type}
137
+ tabIndex={p.tabIndex}
138
+ autocomplete={p.autocomplete}
139
+ placeholder={p.placeholder}
140
+ disabled={s.isDisabled || s.isLoading}
141
+ aria-disabled={s.isDisabled}
142
+ name={p.name}
143
+ id={s.inputId}
144
+ value={p.value}
145
+ required={p.required}
146
+ max={p.max}
147
+ min={p.min}
148
+ step={p.step}
149
+ aria-label={s.ariaLabel}
150
+ @input={event => m.onChange(event.target.value, event)}
151
+ @keydown={m.onKeyDown}
152
+ />
153
+ <div #if={p.type === 'number'} class={p.cls('number-buttons')}>
154
+ <button type="button" class={p.cls('number-btn')} @click={m.stepUp} disabled={s.isDisabled || s.isLoading || s.disableUpBtn} aria-label={p.incrementTitle}>▲</button>
155
+ <button type="button" class={p.cls('number-btn')} @click={m.stepDown} disabled={s.isDisabled || s.isLoading || s.disableDownBtn} aria-label={p.decrementTitle}>▼</button>
156
+ </div>
157
+ <slot name="right" map={content => <span class={p.cls('rightComponent')}>{content}</span>} />
158
+ <div #if={s.isLoading} class={p.cls('loading')} role="status" aria-live="polite">
159
+ <slot name="loading">Loading...</slot>
160
+ </div>
161
+ </div>
162
+ <label #if={p.label} class={p.cls('label', p.labelPlacement)} for={s.inputId}>{p.label}</label>
163
+ </div>
164
+ );
165
+ }
166
+ ```
167
+
168
+ ## Keyboard interaction
169
+
170
+ - `Tab` and `Shift+Tab` move focus to and from the native input in document order.
171
+ - Printable keys, `Backspace`, and `Delete` edit the value through native input
172
+ behavior.
173
+ - `ArrowUp` and `ArrowDown` on a number input use native stepping. The mediator
174
+ does not duplicate those keys.
175
+ - Number-step buttons are `type="button"` and are operable with `Enter` or
176
+ `Space` when available.
177
+
178
+ ## Accessibility notes
179
+
180
+ - The native input supplies keyboard, form, validation, and autocomplete behavior.
181
+ - The visible label and generated or explicit ID remain connected.
182
+ - When visible label text is absent, the mediator exposes `ariaLabel`. The
183
+ default name is `Input`. An explicit empty string omits the name so an
184
+ external label can name the control through `for` and `id`.
185
+ - Disabled inputs use `.is-disabled`, the native `disabled` property, and
186
+ `aria-disabled="true"`.
187
+ - Loading inputs use `.is-loading`, `aria-busy="true"`, and native `disabled`.
188
+ Number-step buttons mirror the same unavailable state.
189
+ - Native Arrow Up and Arrow Down behavior is not duplicated by the mediator.
190
+ - Shared web styles provide at least a 44 by 44 CSS-pixel text-field target,
191
+ visible keyboard focus, forced-colours treatment, and reduced-motion behavior.
192
+
193
+ ## Native renderer mapping
194
+
195
+ NativeScript renderers map the contract to the appropriate editable native view,
196
+ keyboard type, secure-entry state, form constraints, label relationship, busy
197
+ state, and native accessibility name. Web DOM refs and events are renderer
198
+ details. Do not copy ARIA attributes into native markup.
199
+
200
+ ## Known limitations
201
+
202
+ - Placeholders do not replace a visible or accessible name.
203
+ - Hint and error text are not built in. Associate them with the native input
204
+ through `id` when the surrounding form provides that content.
205
+ - Automated accessibility checks do not by themselves establish WCAG
206
+ conformance.
207
+
208
+ ## Standards
209
+
210
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
211
+ - WAI-ARIA Textbox pattern: https://www.w3.org/WAI/ARIA/apg/patterns/
212
+ - GOV.UK text input guidance: https://design-system.service.gov.uk/components/text-input/
@@ -0,0 +1,108 @@
1
+ # Link
2
+
3
+ Link renders a native anchor for navigation and prevents unavailable links from
4
+ remaining actionable or focusable. Its rendering contract follows the WAI-ARIA
5
+ Link pattern and targets WCAG 2.2 Level AA.
6
+
7
+ ## When to use
8
+
9
+ Use Link to navigate to another resource or location. Use Button for actions on
10
+ the current page. Link text should describe its destination without relying on
11
+ surrounding text such as “click here.”
12
+
13
+ ## Props
14
+
15
+ | PropName | Type | Required | Default | Description |
16
+ |---|---|---:|---:|---|
17
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and state classes. |
18
+ | `href` | `string` | no | `"#"` | Supplies the destination. Prefer a real URL. |
19
+ | `className` | `string` | no | `""` | Adds classes to the anchor. |
20
+ | `disabled` | `boolean` | no | - | Removes navigation and sequential focus. |
21
+ | `target` | `'_self' \| '_blank'` | no | `'_self'` | Selects the browsing context. New-window links must communicate that behavior in their accessible content. |
22
+ | `id` | `string` | no | - | Supplies the anchor ID. |
23
+ | `onClick` | `(event: unknown) => void` | no | noop | Reports available-link activation. Renderers may narrow the event type. |
24
+
25
+ ## Mediator state
26
+
27
+ | Property | Type | Description |
28
+ |---|---|---|
29
+ | `isDisabled` | `true \| undefined` | Is `true` when the link is unavailable; otherwise omitted. Renderers use this value directly for `.is-disabled`, `aria-disabled`, omitted `href`, and `tabIndex={-1}`. |
30
+
31
+ ## Slots
32
+
33
+ | SlotName | Description | Default content |
34
+ |---|---|---|
35
+ | default | Supplies descriptive link content. | Empty. |
36
+
37
+ ## CSS Variables
38
+
39
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
40
+ |---|---|---|---|---|---|---|
41
+ | `--pw_Link_root_default_color` | `root` | `default` | `color` | `var(--pw_link_color)` | Link foreground. | Web, Capacitor, NativeScript |
42
+ | `--pw_Link_root_hover_color` | `root` | `hover` | `color` | `var(--pw_link_hover_color)` | Pointer hover foreground. | Web, Capacitor |
43
+ | `--pw_Link_root_default_font-size` | `root` | `default` | `font-size` | `var(--pw_link_font-size)` | Link text size. | Web, Capacitor, NativeScript |
44
+ | `--pw_Link_root_default_font-weight` | `root` | `default` | `font-weight` | `var(--pw_link_font-weight)` | Link text weight. | Web, Capacitor, NativeScript |
45
+ | `--pw_Link_root_default_line-height` | `root` | `default` | `line-height` | `var(--pw_link_line-height)` | Link line height. | Web, Capacitor, NativeScript |
46
+ | `--pw_Link_root_disabled_opacity` | `root` | `disabled` | `opacity` | `var(--pw_disabled_opacity)` | Disabled opacity. | Web, Capacitor, NativeScript |
47
+
48
+ ```xts
49
+ import { useMediator, withDefaultProps } from '@pastweb/x';
50
+ import { linkMediator } from '@pastweb/components-core/Link';
51
+ import { DEFAULT_PROPS } from './constants';
52
+
53
+ export function Link(props: LinkProps) {
54
+ const p = withDefaultProps<LinkDefaultProps>(props, DEFAULT_PROPS);
55
+ const m = useMediator<LinkMediator>(linkMediator, p);
56
+ const s = m.state;
57
+
58
+ return (
59
+ <a
60
+ class={p.cls('Link', p.className, { 'is-disabled': s.isDisabled })}
61
+ href={s.isDisabled ? undefined : p.href}
62
+ target={p.target}
63
+ rel={p.target === '_blank' ? 'noopener noreferrer' : undefined}
64
+ id={p.id}
65
+ aria-disabled={s.isDisabled}
66
+ tabIndex={s.isDisabled ? -1 : undefined}
67
+ @click={m.onClick}
68
+ >
69
+ <slot />
70
+ </a>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ## Keyboard interaction
76
+
77
+ - Available links retain native anchor keyboard activation, typically `Enter`.
78
+ - `Tab` and `Shift+Tab` move sequential focus to available links. Disabled
79
+ links leave the tab order.
80
+
81
+ ## Accessibility notes
82
+
83
+ - Available links retain native anchor semantics and keyboard activation.
84
+ - Disabled links omit `href`, expose `aria-disabled="true"`, leave sequential
85
+ focus, and prevent activation in the mediator.
86
+ - Links opening a new browsing context use `noopener noreferrer`; their content
87
+ should also tell users that a new tab or window will open.
88
+ - Shared web styles keep underlining, visible keyboard focus, and
89
+ forced-colours treatment.
90
+
91
+ ## Native renderer mapping
92
+
93
+ NativeScript renderers map the destination to native URL navigation, unavailable
94
+ state to native enabled/focus/accessibility properties, and external navigation
95
+ to the platform link-opening service. Do not copy ARIA attributes into native
96
+ markup.
97
+
98
+ ## Known limitations
99
+
100
+ - Placeholders such as `href="#"` do not describe a destination.
101
+ - Automated accessibility checks do not by themselves establish WCAG
102
+ conformance.
103
+
104
+ ## Standards
105
+
106
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
107
+ - WAI-ARIA Link pattern: https://www.w3.org/WAI/ARIA/apg/patterns/link/
108
+ - GOV.UK links guidance: https://www.gov.uk/guidance/content-design/links
@@ -0,0 +1,141 @@
1
+ # Notification
2
+
3
+ Notification presents a short, politely announced message with optional title,
4
+ close action, and footer content. Its rendering contract targets WCAG 2.2
5
+ Level AA.
6
+
7
+ ## When to use
8
+
9
+ Use Notification for non-urgent status information that may remain in the page.
10
+ Use an alert pattern for urgent information requiring immediate attention, and a
11
+ dialog when users must make a decision before continuing.
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 | `""` | Names the optional notification region. Pass an empty string to omit the region role. |
19
+ | `className` | `string` | no | `""` | Adds classes to the root article. |
20
+ | `closeButton` | `boolean` | no | `true` | Displays the native close button. |
21
+ | `closeLabel` | `string` | no | `"Close notification"` | Names the close button. |
22
+ | `onClose` | `() => void` | no | noop | Reports close intent. |
23
+ | `rtl` | `boolean` | no | `false` | Reverses header content order. |
24
+ | `title` | `string` | no | - | Supplies the default title content. |
25
+
26
+ ## Mediator state
27
+
28
+ | Property | Type | Description |
29
+ |---|---|---|
30
+ | `ariaLabel` | `string \| undefined` | Normalized accessible name. Empty labels are omitted. |
31
+ | `role` | `'region' \| undefined` | Is `region` when `ariaLabel` supplies a name; otherwise omitted. |
32
+
33
+ ## Slots
34
+
35
+ | SlotName | Description | Default content | Props |
36
+ |---|---|---|---|
37
+ | default | Supplies the politely announced message. | Empty. | - |
38
+ | `header` | Replaces title and close-button content inside the semantic header. | Title and optional close button. | `{ title, onClose }` |
39
+ | `footer` | Supplies content wrapped in the semantic footer. | Empty. | - |
40
+
41
+ ## Utility classes
42
+
43
+ Apply these classes through `className`.
44
+
45
+ | Class | Description |
46
+ |---|---|
47
+ | `is-full-width` | Expands the notification to the available width. |
48
+
49
+ ## CSS Variables
50
+
51
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
52
+ |---|---|---|---|---|---|---|
53
+ | `--pw_Notification_root_default_background-color` | `root` | `default` | `background-color` | `var(--pw_control_background-color)` | Notification background. | Web, Capacitor, NativeScript |
54
+ | `--pw_Notification_root_default_color` | `root` | `default` | `color` | `var(--pw_text_color)` | Message foreground. | Web, Capacitor, NativeScript |
55
+ | `--pw_Notification_root_default_padding` | `root` | `default` | `padding` | `var(--pw_padding)` | Inner spacing. | Web, Capacitor, NativeScript |
56
+ | `--pw_Notification_root_default_border-color` | `root` | `default` | `border-color` | `var(--pw_border-color)` | Border colour. | Web, Capacitor, NativeScript |
57
+ | `--pw_Notification_root_default_border-width` | `root` | `default` | `border-width` | `var(--pw_border-width)` | Border width. | Web, Capacitor, NativeScript |
58
+ | `--pw_Notification_root_default_border-style` | `root` | `default` | `border-style` | `var(--pw_border-style)` | Border style. | Web, Capacitor, NativeScript |
59
+ | `--pw_Notification_root_default_border-radius` | `root` | `default` | `border-radius` | `var(--pw_border-radius)` | Corner radius. | Web, Capacitor, NativeScript |
60
+ | `--pw_Notification_root_default_box-shadow` | `root` | `default` | `box-shadow` | shared shadow | Elevation. | Web, Capacitor |
61
+ | `--pw_Notification_header_default_color` | `header` | `default` | `color` | `var(--pw_text_strong_color)` | Title foreground. | Web, Capacitor, NativeScript |
62
+ | `--pw_Notification_header_default_font-size` | `header` | `default` | `font-size` | `1em` | Title text size. | Web, Capacitor, NativeScript |
63
+ | `--pw_Notification_header_default_font-weight` | `header` | `default` | `font-weight` | `var(--pw_semibold_font-weight)` | Title weight. | Web, Capacitor, NativeScript |
64
+ | `--pw_Notification_header_default_line-height` | `header` | `default` | `line-height` | `var(--pw_line-height)` | Title line height. | Web, Capacitor, NativeScript |
65
+ | `--pw_Notification_header_default_margin-bottom` | `header` | `default` | `margin-bottom` | `0.5em` | Header spacing. | Web, Capacitor, NativeScript |
66
+ | `--pw_Notification_close-button_default_background-color` | `close-button` | `default` | `background-color` | `transparent` | Close-button background. | Web, Capacitor, NativeScript |
67
+ | `--pw_Notification_close-button_hover_background-color` | `close-button` | `hover` | `background-color` | header colour | Hover background. | Web, Capacitor |
68
+ | `--pw_Notification_close-button_default_min-width` | `close-button` | `default` | `min-width` | `1.563em` | Close-button width, matching Panel. | Web, Capacitor, NativeScript |
69
+ | `--pw_Notification_close-button_default_height` | `close-button` | `default` | `height` | `1.563em` | Close-button height, matching Panel. | Web, Capacitor, NativeScript |
70
+ | `--pw_Notification_close-button_default_transition-duration` | `close-button` | `default` | `transition-duration` | `0.5s` | Visual transition duration. | Web, Capacitor; NativeScript adapter |
71
+ | `--pw_Notification_close-bar_default_background-color` | `close-bar` | `default` | `background-color` | header colour | Close glyph colour. | Web, Capacitor, NativeScript |
72
+ | `--pw_Notification_close-bar_hover_background-color` | `close-bar` | `hover` | `background-color` | notification background | Hover glyph colour. | Web, Capacitor |
73
+ | `--pw_Notification_footer_default_margin-top` | `footer` | `default` | `margin-top` | `0.5em` | Footer spacing. | Web, Capacitor, NativeScript |
74
+
75
+ ```xts
76
+ import { useMediator, withDefaultProps } from '@pastweb/x';
77
+ import { notificationMediator } from '@pastweb/components-core/Notification';
78
+ import { DEFAULT_PROPS } from './constants';
79
+
80
+ export function Notification(props: NotificationProps) {
81
+ const p = withDefaultProps<NotificationDefaultProps>(props, DEFAULT_PROPS);
82
+ const m = useMediator<NotificationMediator>(notificationMediator, p);
83
+ const s = m.state;
84
+
85
+ return (
86
+ <article
87
+ class={p.cls('Notification', p.className)}
88
+ role={s.role}
89
+ aria-label={s.ariaLabel}
90
+ >
91
+ <header #if={p.title || p.closeButton} class={p.cls('header', { rtl: p.rtl })}>
92
+ <slot name="header" props={{ title: p.title, onClose: m.onClose }}>
93
+ <span #if={p.title} class={p.cls('title')}>{p.title}</span>
94
+ <button #if={p.closeButton} type="button" class={p.cls('closeButton')} @click={m.onClose} aria-label={p.closeLabel}>
95
+ <span class={p.cls('closeBar')} aria-hidden="true" />
96
+ <span class={p.cls('closeBar')} aria-hidden="true" />
97
+ </button>
98
+ </slot>
99
+ </header>
100
+ <div class={p.cls('content')} role="status" aria-live="polite" aria-atomic="true">
101
+ <slot />
102
+ </div>
103
+ <slot name="footer" map={content => <footer class={p.cls('footer')}>{content}</footer>} />
104
+ </article>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ## Keyboard interaction
110
+
111
+ - The close action is a native button and is operable with `Enter` or `Space`.
112
+ - Message content is not interactive unless projected content supplies its own
113
+ controls.
114
+
115
+ ## Accessibility notes
116
+
117
+ - Only message content is the polite live region; the interactive close button
118
+ is outside it.
119
+ - The close action is a native button with a configurable accessible name.
120
+ - `ariaLabel` creates a named region when consumers need direct navigation to a
121
+ persistent notification. The default is an empty string, which omits the
122
+ region role. Avoid unnecessary repeated regions.
123
+ - Shared web styles match the Panel close control size, with visible keyboard
124
+ focus, forced-colours treatment, and reduced-motion behavior.
125
+
126
+ ## Native renderer mapping
127
+
128
+ NativeScript renderers map the message to native polite announcement APIs, the
129
+ close action to a native button, and optional region naming to native
130
+ accessibility labels. Do not copy ARIA attributes into native markup.
131
+
132
+ ## Known limitations
133
+
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 Status and alert patterns: https://www.w3.org/WAI/ARIA/apg/patterns/
141
+ - GOV.UK notification banner: https://design-system.service.gov.uk/components/notification-banner/
@@ -0,0 +1,147 @@
1
+ # Pagination
2
+
3
+ Pagination provides bounded navigation through a known number of result pages.
4
+ Its rendering contract targets WCAG 2.2 Level AA and follows GOV.UK pagination
5
+ guidance for landmarks, current-page identification, and page naming.
6
+
7
+ ## When to use
8
+
9
+ Use Pagination when a collection is split into stable, addressable pages. Prefer
10
+ “load more” when preserving context matters more than direct page access. Do not
11
+ use it for linear form journeys; use a continue action and a back link instead.
12
+ Do not show pagination when there is only one page. Keep the current page
13
+ synchronized with the URL when pages are navigable resources.
14
+
15
+ ## Props
16
+
17
+ | PropName | Type | Required | Default | Description |
18
+ |---|---|---:|---:|---|
19
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component, part, and state classes. |
20
+ | `total` | `number` | yes | - | Supplies the number of pages. Values are floored and clamped to zero or greater. |
21
+ | `current` | `number` | yes | - | Supplies the current one-based page. It is clamped to the available range. |
22
+ | `ariaLabel` | `string` | no | `"Pagination"` | Names the navigation landmark. Pass an empty string to omit the name. Provide a unique name when more than one pagination control is on the page. |
23
+ | `disabled` | `boolean` | no | - | Makes every navigation action unavailable. |
24
+ | `onChange` | `(page: number) => void` | no | noop | Reports a valid changed page. |
25
+ | `className` | `string` | no | `""` | Adds classes to the navigation root. |
26
+ | `prev` | `unknown` | no | `◄` | Supplies previous visual content. |
27
+ | `next` | `unknown` | no | `►` | Supplies next visual content. |
28
+ | `prevAriaLabel` | `string` | no | `"Previous page"` | Names the previous control. |
29
+ | `nextAriaLabel` | `string` | no | `"Next page"` | Names the next control. |
30
+ | `pageAriaLabel` | `(page: number) => string` | no | `Page N` | Names a numbered page control. |
31
+ | `jumpBackAriaLabel` | `string` | no | `"Jump backward through pages"` | Names the backward ellipsis jump control. |
32
+ | `jumpForwardAriaLabel` | `string` | no | `"Jump forward through pages"` | Names the forward ellipsis jump control. |
33
+
34
+ ## Mediator state
35
+
36
+ | Property | Type | Description |
37
+ |---|---|---|
38
+ | `ariaLabel` | `string \| undefined` | Normalized navigation name. Empty labels are omitted. |
39
+ | `current` | `number` | Clamped one-based page. Renderers use this for `.is-active` and `aria-current="page"`. |
40
+ | `isDisabled` | `true \| undefined` | Is `true` when every action is unavailable; otherwise omitted. Renderers use this value directly for `.is-disabled` and page-control `disabled`. |
41
+ | `isNextDisabled` | `true \| undefined` | Is `true` when next is unavailable; otherwise omitted. |
42
+ | `isPrevDisabled` | `true \| undefined` | Is `true` when previous is unavailable; otherwise omitted. |
43
+ | `pages` | `PaginationItem[]` | Visible numbered pages and ellipsis jump controls. |
44
+
45
+ ## Utility classes
46
+
47
+ Apply these classes through `className`.
48
+
49
+ | Class | Description |
50
+ |---|---|
51
+ | `is-rounded` | Uses the rounded control radius token. |
52
+ | `is-full-width` | Expands the navigation to the available width. |
53
+
54
+ ## CSS Variables
55
+
56
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
57
+ |---|---|---|---|---|---|---|
58
+ | `--pw_Pagination_button_default_margin` | `button` | `default` | `margin` | `var(--pw_margin)` | Control spacing. | Web, Capacitor, NativeScript |
59
+ | `--pw_Pagination_button_default_padding` | `button` | `default` | `padding` | `var(--pw_padding)` | Control inner spacing. | Web, Capacitor, NativeScript |
60
+ | `--pw_Pagination_button_default_background-color` | `button` | `default` | `background-color` | `var(--pw_background-color)` | Control background. | Web, Capacitor, NativeScript |
61
+ | `--pw_Pagination_button_default_border-color` | `button` | `default` | `border-color` | `var(--pw_border-color)` | Control border colour. | Web, Capacitor, NativeScript |
62
+ | `--pw_Pagination_button_default_border-width` | `button` | `default` | `border-width` | `var(--pw_border-width)` | Control border width. | Web, Capacitor, NativeScript |
63
+ | `--pw_Pagination_button_default_border-style` | `button` | `default` | `border-style` | `var(--pw_border-style)` | Control border style. | Web, Capacitor, NativeScript |
64
+ | `--pw_Pagination_button_default_border-radius` | `button` | `default` | `border-radius` | `var(--pw_border-radius)` | Control corner radius. | Web, Capacitor, NativeScript |
65
+ | `--pw_Pagination_button_rounded_border-radius` | `button` | `rounded` | `border-radius` | `var(--pw_rounded_border-radius)` | Rounded control radius. | Web, Capacitor, NativeScript |
66
+ | `--pw_Pagination_button_default_color` | `button` | `default` | `color` | `var(--pw_text_strong_color)` | Control foreground. | Web, Capacitor, NativeScript |
67
+ | `--pw_Pagination_button_default_font-size` | `button` | `default` | `font-size` | `var(--pw_font-size)` | Control text size. | Web, Capacitor, NativeScript |
68
+ | `--pw_Pagination_button_default_font-weight` | `button` | `default` | `font-weight` | `var(--pw_semibold_font-weight)` | Control text weight. | Web, Capacitor, NativeScript |
69
+ | `--pw_Pagination_button_default_line-height` | `button` | `default` | `line-height` | `var(--pw_line-height)` | Control line height. | Web, Capacitor, NativeScript |
70
+ | `--pw_Pagination_button_default_transition-duration` | `button` | `default` | `transition-duration` | `var(--pw_transition-duration)` | Visual state duration. | Web, Capacitor; NativeScript adapter |
71
+ | `--pw_Pagination_button_disabled_opacity` | `button` | `disabled` | `opacity` | `var(--pw_disabled_opacity)` | Disabled control opacity. | Web, Capacitor, NativeScript |
72
+ | `--pw_Pagination_button_hover_box-shadow` | `button` | `hover` | `box-shadow` | shared inset shadow | Hover treatment. | Web, Capacitor |
73
+ | `--pw_Pagination_button_focus_box-shadow` | `button` | `focus` | `box-shadow` | shared focus shadow | Keyboard focus treatment. | Web, Capacitor |
74
+ | `--pw_Pagination_page_default_margin` | `page` | `default` | `margin` | shared page margin | Page-control spacing. | Web, Capacitor, NativeScript |
75
+ | `--pw_Pagination_page_active_color` | `page` | `active` | `color` | strong text colour | Current-page foreground. | Web, Capacitor, NativeScript |
76
+ | `--pw_Pagination_page_active_font-weight` | `page` | `active` | `font-weight` | bold | Current-page weight. | Web, Capacitor, NativeScript |
77
+ | `--pw_Pagination_page_active_background-color` | `page` | `active` | `background-color` | control background | Current-page background. | Web, Capacitor, NativeScript |
78
+
79
+ ```xts
80
+ import { useMediator, withDefaultProps } from '@pastweb/x';
81
+ import { paginationMediator } from '@pastweb/components-core/Pagination';
82
+ import { DEFAULT_PROPS } from './constants';
83
+
84
+ export function Pagination(props: PaginationProps) {
85
+ const p = withDefaultProps<PaginationDefaultProps>(props, DEFAULT_PROPS);
86
+ const m = useMediator<PaginationMediator>(paginationMediator, p);
87
+ const s = m.state;
88
+
89
+ return (
90
+ <nav class={p.cls('Pagination', p.className, { 'is-disabled': s.isDisabled })} aria-label={s.ariaLabel}>
91
+ <button type="button" class={p.cls('button', 'previous')} disabled={s.isPrevDisabled} @click={m.onPrev} aria-label={p.prevAriaLabel}>
92
+ <render content={p.prev} />
93
+ </button>
94
+ <ul class={p.cls('pages')}>
95
+ <li #for={page of s.pages} class={p.cls('pageItem')}>
96
+ <button
97
+ type="button"
98
+ class={p.cls('button', 'page', { 'is-active': s.current === page })}
99
+ disabled={s.isDisabled}
100
+ aria-current={s.current === page ? 'page' : undefined}
101
+ aria-label={typeof page === 'number' ? p.pageAriaLabel(page) : page === '... ' ? p.jumpBackAriaLabel : p.jumpForwardAriaLabel}
102
+ @click={() => m.onPageChange(page)}
103
+ >
104
+ {page}
105
+ </button>
106
+ </li>
107
+ </ul>
108
+ <button type="button" class={p.cls('button', 'next')} disabled={s.isNextDisabled} @click={m.onNext} aria-label={p.nextAriaLabel}>
109
+ <render content={p.next} />
110
+ </button>
111
+ </nav>
112
+ );
113
+ }
114
+ ```
115
+
116
+ ## Keyboard interaction
117
+
118
+ - Previous, next, page, and jump controls are native buttons and are operable
119
+ with `Enter` or `Space` when available.
120
+ - `Tab` and `Shift+Tab` move through available controls in document order.
121
+ - Disabled controls, including previous on the first page and next on the last
122
+ page, are not operable.
123
+
124
+ ## Accessibility notes
125
+
126
+ - The component is a named navigation landmark containing native buttons in a
127
+ list. Native buttons are used because page changes are reported through
128
+ `onChange` rather than URL navigation.
129
+ - The current numbered control exposes `aria-current="page"`.
130
+ - Ellipses are explicit jump controls with meaningful accessible names, not
131
+ mislabeled page numbers. GOV.UK treats ellipses as non-interactive; this
132
+ contract keeps them operable so skipped ranges remain reachable by keyboard.
133
+ - Previous and next remain in the layout and become native-disabled at the ends
134
+ of the range. All controls also mirror the disabled component state.
135
+ - Shared web styles provide at least a 44 by 44 CSS-pixel target, visible focus,
136
+ forced-colours treatment, and reduced-motion behavior.
137
+
138
+ ## Known limitations
139
+
140
+ - Automated accessibility checks do not by themselves establish WCAG
141
+ conformance.
142
+
143
+ ## Standards
144
+
145
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
146
+ - WAI-ARIA landmark regions: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/
147
+ - GOV.UK pagination: https://design-system.service.gov.uk/components/pagination/