@reliability-design/web 1.4.3 → 1.4.4
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/components/rel-autocomplete/README.md +1 -0
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +56 -1
- package/dist/components/rel-checkbox/README.md +69 -22
- package/dist/components/rel-checkbox/rel-checkbox.ts +116 -9
- package/dist/components/rel-pagination/README.md +14 -0
- package/dist/components/rel-pagination/rel-pagination.ts +30 -14
- package/dist/components/rel-range-picker/rel-range-picker.ts +117 -2
- package/dist/components/rel-select/README.md +1 -0
- package/dist/components/rel-select/rel-select.ts +1237 -1170
- package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +134 -0
- package/dist/components/rel-upload/rel-upload-actions.ts +163 -0
- package/dist/components/rel-upload/rel-upload.ts +943 -0
- package/dist/index.d.mts +267 -2
- package/dist/index.d.ts +267 -2
- package/dist/index.js +1259 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1273 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -94,3 +94,4 @@ Two data modes:
|
|
|
94
94
|
- Form validation is *displayed* (`error`, `error-message`, `required`), not performed — validity logic belongs to the form layer (e.g. Angular validators)
|
|
95
95
|
- CSS parts: `panel`, `list`, `option`, plus re-exported textfield parts (`label`, `control`, `input`, `helper`, `right-icon`)
|
|
96
96
|
- Panel styling matches `rel-select`'s listbox; override via `--rel-autocomplete-bg`, `--rel-autocomplete-menu-border`, `--rel-autocomplete-menu-radius`, `--rel-autocomplete-menu-shadow`, `--rel-autocomplete-option-hover`
|
|
97
|
+
- The open panel is promoted to the browser **top layer** (native Popover API, `popover="manual"`), so it renders correctly inside transformed / `overflow: hidden` containers — a centered `rel-modal`, a bottom `rel-drawer`. Engines without the Popover API fall back to `position: fixed` with a measure-and-cancel correction for transformed ancestors
|
|
@@ -143,6 +143,15 @@ export class RelAutocomplete extends LitElement {
|
|
|
143
143
|
position: fixed;
|
|
144
144
|
z-index: 1001;
|
|
145
145
|
display: none;
|
|
146
|
+
/* Reset native [popover] UA styles (the panel is promoted to the
|
|
147
|
+
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
148
|
+
panel — can't hijack its containing block or clip it). */
|
|
149
|
+
inset: auto;
|
|
150
|
+
margin: 0;
|
|
151
|
+
padding: 0;
|
|
152
|
+
width: auto;
|
|
153
|
+
height: auto;
|
|
154
|
+
color: inherit;
|
|
146
155
|
box-sizing: border-box;
|
|
147
156
|
background: var(--rel-autocomplete-bg, var(--rel-bg-neutral));
|
|
148
157
|
border: 1px solid var(--rel-autocomplete-menu-border, var(--rel-border-default));
|
|
@@ -222,6 +231,7 @@ export class RelAutocomplete extends LitElement {
|
|
|
222
231
|
disconnectedCallback() {
|
|
223
232
|
super.disconnectedCallback();
|
|
224
233
|
this.removeGlobalListeners();
|
|
234
|
+
this.hideNativePopover();
|
|
225
235
|
clearTimeout(this.debounceTimer);
|
|
226
236
|
}
|
|
227
237
|
|
|
@@ -309,7 +319,10 @@ export class RelAutocomplete extends LitElement {
|
|
|
309
319
|
this.open = true;
|
|
310
320
|
this.highlightedIndex = -1;
|
|
311
321
|
this.addGlobalListeners();
|
|
312
|
-
requestAnimationFrame(() =>
|
|
322
|
+
requestAnimationFrame(() => {
|
|
323
|
+
this.showNativePopover();
|
|
324
|
+
this.updatePosition();
|
|
325
|
+
});
|
|
313
326
|
}
|
|
314
327
|
|
|
315
328
|
private closePanel() {
|
|
@@ -317,6 +330,35 @@ export class RelAutocomplete extends LitElement {
|
|
|
317
330
|
this.open = false;
|
|
318
331
|
this.highlightedIndex = -1;
|
|
319
332
|
this.removeGlobalListeners();
|
|
333
|
+
this.hideNativePopover();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
337
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
338
|
+
* descendants, breaking viewport-based coordinates and letting the
|
|
339
|
+
* panel's overflow:hidden clip the suggestions. Promoting the panel to
|
|
340
|
+
* the browser top layer sidesteps both — same fix as rel-popover.
|
|
341
|
+
* `manual` keeps light-dismiss and Escape handling under this
|
|
342
|
+
* component's own control. */
|
|
343
|
+
|
|
344
|
+
private showNativePopover() {
|
|
345
|
+
const p = this.panelEl;
|
|
346
|
+
if (!p || typeof p.showPopover !== "function") return;
|
|
347
|
+
try {
|
|
348
|
+
if (!p.matches(":popover-open")) p.showPopover();
|
|
349
|
+
} catch {
|
|
350
|
+
/* older engines / detached element — fixed-position fallback applies */
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private hideNativePopover() {
|
|
355
|
+
const p = this.panelEl;
|
|
356
|
+
if (!p || typeof p.hidePopover !== "function") return;
|
|
357
|
+
try {
|
|
358
|
+
if (p.matches(":popover-open")) p.hidePopover();
|
|
359
|
+
} catch {
|
|
360
|
+
/* already hidden or unsupported — nothing to do */
|
|
361
|
+
}
|
|
320
362
|
}
|
|
321
363
|
|
|
322
364
|
private updatePosition = () => {
|
|
@@ -337,6 +379,18 @@ export class RelAutocomplete extends LitElement {
|
|
|
337
379
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
338
380
|
}
|
|
339
381
|
|
|
382
|
+
// Without top-layer support a transformed ancestor may be the containing
|
|
383
|
+
// block for position:fixed, shifting the panel away from the viewport
|
|
384
|
+
// coordinates computed above. Apply, re-measure, and cancel the offset.
|
|
385
|
+
this.panelEl.style.top = `${top}px`;
|
|
386
|
+
this.panelEl.style.left = `${left}px`;
|
|
387
|
+
this.panelEl.style.width = `${t.width}px`;
|
|
388
|
+
const applied = this.panelEl.getBoundingClientRect();
|
|
389
|
+
top -= applied.top - top;
|
|
390
|
+
left -= applied.left - left;
|
|
391
|
+
this.panelEl.style.top = `${top}px`;
|
|
392
|
+
this.panelEl.style.left = `${left}px`;
|
|
393
|
+
|
|
340
394
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
341
395
|
};
|
|
342
396
|
|
|
@@ -583,6 +637,7 @@ export class RelAutocomplete extends LitElement {
|
|
|
583
637
|
id="${panelId}"
|
|
584
638
|
part="panel list"
|
|
585
639
|
class="ac-panel"
|
|
640
|
+
popover="manual"
|
|
586
641
|
role="listbox"
|
|
587
642
|
aria-label="${this.accessibleName || "Suggestions"}"
|
|
588
643
|
style="${this.panelStyle}"
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
# rel-checkbox
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
|
-
A tri-state checkbox component supporting checked, unchecked, and indeterminate states. Includes label
|
|
4
|
+
A tri-state checkbox component supporting checked, unchecked, and indeterminate states. Includes label, optional supporting text, and optional label prefix icon, with built-in error and disabled states. Supports two sizes, two densities, and leading/trailing box alignment — matching the Figma Checkbox sheet vocabulary (Size, Density, Alignment) verbatim.
|
|
5
5
|
|
|
6
6
|
## Usage
|
|
7
7
|
```html
|
|
8
8
|
<!-- Basic example -->
|
|
9
9
|
<rel-checkbox label="Accept terms"></rel-checkbox>
|
|
10
10
|
|
|
11
|
-
<!--
|
|
11
|
+
<!-- Supporting text, compact density (default: text on its own line, indented past the box) -->
|
|
12
12
|
<rel-checkbox
|
|
13
13
|
label="Subscribe to newsletter"
|
|
14
14
|
supportingtext="We'll send you weekly updates"
|
|
15
15
|
status="unchecked"
|
|
16
16
|
state="default">
|
|
17
17
|
</rel-checkbox>
|
|
18
|
+
|
|
19
|
+
<!-- Comfortable density: label + supporting text stack beside the box -->
|
|
20
|
+
<rel-checkbox
|
|
21
|
+
density="comfortable"
|
|
22
|
+
label="Notify the broker"
|
|
23
|
+
supportingtext="Sends an email when the POD is uploaded">
|
|
24
|
+
</rel-checkbox>
|
|
25
|
+
|
|
26
|
+
<!-- Trailing alignment: box at the far edge of the row (settings-row style).
|
|
27
|
+
Trailing always renders comfortable density — compact has no trailing layout. -->
|
|
28
|
+
<rel-checkbox
|
|
29
|
+
alignment="trailing"
|
|
30
|
+
label="Email notifications"
|
|
31
|
+
supportingtext="Weekly summary of load activity">
|
|
32
|
+
</rel-checkbox>
|
|
33
|
+
|
|
34
|
+
<!-- Small size -->
|
|
35
|
+
<rel-checkbox size="small" label="Include archived"></rel-checkbox>
|
|
36
|
+
|
|
37
|
+
<!-- Indeterminate (programmatic-only; user toggle resolves to checked/unchecked) -->
|
|
38
|
+
<rel-checkbox label="Select all" status="indeterminate"></rel-checkbox>
|
|
18
39
|
```
|
|
19
40
|
|
|
20
41
|
## API
|
|
@@ -23,42 +44,62 @@ A tri-state checkbox component supporting checked, unchecked, and indeterminate
|
|
|
23
44
|
| Name | Type | Default | Description |
|
|
24
45
|
|------|------|---------|-------------|
|
|
25
46
|
| `label` | `string` | `"Label"` | Label text displayed next to the checkbox |
|
|
26
|
-
| `icon` | `string` | `""` | `rel-icon` name rendered between the checkbox and the label (label prefix icon). Unset renders no icon and reserves no space |
|
|
27
|
-
| `supportingtext` | `string` | `""` | Helper text
|
|
47
|
+
| `icon` | `string` | `""` | `rel-icon` name rendered between the checkbox and the label (label prefix icon; scales with `size`). Unset renders no icon and reserves no space |
|
|
48
|
+
| `supportingtext` | `string` | `""` | Helper text; placement depends on `density` |
|
|
28
49
|
| `state` | `"default" \| "error" \| "disabled"` | `"default"` | Visual and interaction state of the checkbox |
|
|
29
|
-
| `status` | `"unchecked" \| "checked" \| "indeterminate"` | `"unchecked"` | Current
|
|
50
|
+
| `status` | `"unchecked" \| "checked" \| "indeterminate"` | `"unchecked"` | Current check state. Indeterminate is programmatic-only |
|
|
51
|
+
| `checked` | `boolean` (accessor) | `false` | Convenience accessor over `status` (used by the Angular CVA) |
|
|
52
|
+
| `size` | `"small" \| "medium"` | `"medium"` | Control scale: medium = 18px box, body-lg label, body-sm supporting text; small = 14px box, body-md label, body-xs supporting text (reflected) |
|
|
53
|
+
| `density` | `"compact" \| "comfortable"` | `"compact"` | Supporting-text layout: compact puts it on its own line under the row, indented past the box; comfortable stacks label + supporting text beside the box (reflected) |
|
|
54
|
+
| `alignment` | `"leading" \| "trailing"` | `"leading"` | Box position: leading = before the label; trailing = far edge of the row. Trailing always renders comfortable density — compact+trailing is "Not available" in the Figma sheet, and a dev-mode `console.warn` fires if requested (reflected) |
|
|
55
|
+
| `value` | `string` | `""` | Value reported in the `rel-change` event detail |
|
|
56
|
+
|
|
57
|
+
### Layout matrix (mirrors the Figma sheet)
|
|
58
|
+
| Size | Density | Leading | Trailing |
|
|
59
|
+
|------|---------|---------|----------|
|
|
60
|
+
| M | Comfortable | ✓ | ✓ |
|
|
61
|
+
| M | Compact | ✓ (default) | — renders comfortable |
|
|
62
|
+
| S | Comfortable | ✓ | ✓ |
|
|
63
|
+
| S | Compact | ✓ | — renders comfortable |
|
|
30
64
|
|
|
31
65
|
## Events
|
|
32
66
|
| Event | Detail | Description |
|
|
33
67
|
|-------|--------|-------------|
|
|
34
|
-
| `change` | `{ checked: boolean,
|
|
68
|
+
| `rel-change` | `{ checked: boolean, value: string }` | Fired on user toggle (bubbles, composed) |
|
|
35
69
|
|
|
36
70
|
## Slots
|
|
37
71
|
Default slot not used.
|
|
38
72
|
|
|
39
73
|
## CSS Parts
|
|
40
|
-
|
|
74
|
+
- `base` — outer wrapper
|
|
75
|
+
- `label` — label text span
|
|
76
|
+
- `helper` — supporting text span
|
|
41
77
|
|
|
42
78
|
## Tokens / CSS Variables
|
|
43
79
|
|
|
44
80
|
### Semantic Tokens
|
|
45
81
|
- `--rel-fonts-font-family` - Font family
|
|
46
|
-
- `--rel-body-
|
|
47
|
-
- `--rel-
|
|
82
|
+
- `--rel-body-lg-regular-*` / `--rel-body-sm-regular-*` - Label and supporting text type (medium size)
|
|
83
|
+
- `--rel-body-md-regular-*` / `--rel-body-xs-regular-*` - Label and supporting text type (small size)
|
|
84
|
+
|
|
85
|
+
### Spacing Tokens
|
|
86
|
+
- `--rel-spacing-xs` - Gap between box, icon, and label
|
|
87
|
+
- `--rel-spacing-3xs` - Gap between label and supporting text (comfortable) / row and supporting text (compact)
|
|
48
88
|
|
|
49
89
|
### Color Tokens
|
|
50
|
-
- `--rel-text-
|
|
51
|
-
- `--rel-
|
|
52
|
-
- `--rel-
|
|
53
|
-
- `--rel-text-
|
|
54
|
-
- `--rel-
|
|
55
|
-
- `--rel-border-
|
|
90
|
+
- `--rel-text-default` - Box border and label text color (rest)
|
|
91
|
+
- `--rel-text-body` - Hover color for box, label, and icon
|
|
92
|
+
- `--rel-bg-neutral-inverse-2` - Checked box fill and indeterminate dash
|
|
93
|
+
- `--rel-text-inverse` - Checkmark color
|
|
94
|
+
- `--rel-text-caption` - Supporting text color
|
|
95
|
+
- `--rel-border-focus` - Focus outline color
|
|
96
|
+
- `--rel-border-danger` / `--rel-action-danger-fill` / `--rel-text-danger` - Error state colors
|
|
56
97
|
|
|
57
98
|
## Accessibility
|
|
58
99
|
- Uses visually hidden native checkbox input for screen reader support
|
|
59
100
|
- Custom visual checkbox with proper focus indicators
|
|
60
101
|
- Focus visible state with outline offset for keyboard users
|
|
61
|
-
- Label click targets the checkbox for larger interaction area
|
|
102
|
+
- Label click targets the checkbox for larger interaction area (in trailing alignment the whole row is clickable)
|
|
62
103
|
- Disabled state communicated through opacity reduction and cursor change
|
|
63
104
|
|
|
64
105
|
### Keyboard Interactions
|
|
@@ -67,12 +108,18 @@ None exposed.
|
|
|
67
108
|
|
|
68
109
|
### Focus Behavior
|
|
69
110
|
- Focus outline appears with offset around the custom checkbox box
|
|
70
|
-
- Uses
|
|
111
|
+
- Uses focus border color for high contrast
|
|
71
112
|
|
|
72
113
|
## Notes / Edge Cases
|
|
73
|
-
-
|
|
114
|
+
- User toggling is binary: indeterminate is never entered by interaction — toggling from indeterminate resolves to checked. Set `status="indeterminate"` programmatically (e.g. a Select-All parent driven by app state)
|
|
74
115
|
- When `state="disabled"`, click events are blocked and opacity is reduced to 0.55
|
|
75
|
-
- The
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
|
|
116
|
+
- The host is `display: block`, so a trailing checkbox stretches to its container width; constrain the parent to control row width
|
|
117
|
+
- Comfortable density with no supporting text is visually identical to compact
|
|
118
|
+
- The hidden native input is absolutely positioned against the `.row` containing block; keep `position: relative` on `.row` (Tab-scroll page-jump fix, shared with rel-radio)
|
|
119
|
+
|
|
120
|
+
## Recipes (intentionally NOT component props)
|
|
121
|
+
- **Selectable list row**: `alignment="trailing"` checkbox inside a padded, hover-highlighted container
|
|
122
|
+
- **Select All / nested group**: parent checkbox with `status="indeterminate"` wired to children in app state
|
|
123
|
+
- **Selectable card**: `rel-card` wrapping a checkbox; toggle a selected border on `rel-change`
|
|
124
|
+
|
|
125
|
+
See the Storybook Checkbox stories for working recipe markup.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
1
|
+
import { LitElement, html, css, type PropertyValues } from "lit";
|
|
2
2
|
import { customElement, property } from "lit/decorators.js";
|
|
3
3
|
import "@reliability-design/icons/rel-icon";
|
|
4
4
|
|
|
5
5
|
type CheckboxState = "default" | "error" | "disabled";
|
|
6
6
|
type CheckboxStatus = "unchecked" | "checked" | "indeterminate";
|
|
7
|
+
type CheckboxSize = "small" | "medium";
|
|
8
|
+
type CheckboxDensity = "compact" | "comfortable";
|
|
9
|
+
type CheckboxAlignment = "leading" | "trailing";
|
|
7
10
|
|
|
8
11
|
@customElement("rel-checkbox")
|
|
9
12
|
export class RelCheckbox extends LitElement {
|
|
@@ -15,6 +18,21 @@ export class RelCheckbox extends LitElement {
|
|
|
15
18
|
@property({ type: String }) status: CheckboxStatus = "unchecked";
|
|
16
19
|
@property({ type: String }) value = "";
|
|
17
20
|
|
|
21
|
+
/** Control scale (Figma Size). `"medium"` (default) = 18px box with body-lg
|
|
22
|
+
* label; `"small"` = 14px box with body-md label. */
|
|
23
|
+
@property({ type: String, reflect: true }) size: CheckboxSize = "medium";
|
|
24
|
+
|
|
25
|
+
/** Supporting-text layout (Figma Density). `"compact"` (default) puts the
|
|
26
|
+
* supporting text on its own line under the row, indented past the box;
|
|
27
|
+
* `"comfortable"` stacks label + supporting text beside the box. */
|
|
28
|
+
@property({ type: String, reflect: true }) density: CheckboxDensity = "compact";
|
|
29
|
+
|
|
30
|
+
/** Box position (Figma Alignment). `"leading"` (default) renders the box
|
|
31
|
+
* before the label; `"trailing"` pushes it to the far edge of the row.
|
|
32
|
+
* Trailing has no compact layout in the spec, so it always renders with
|
|
33
|
+
* comfortable density. */
|
|
34
|
+
@property({ type: String, reflect: true }) alignment: CheckboxAlignment = "leading";
|
|
35
|
+
|
|
18
36
|
get checked(): boolean {
|
|
19
37
|
return this.status === "checked";
|
|
20
38
|
}
|
|
@@ -53,6 +71,29 @@ export class RelCheckbox extends LitElement {
|
|
|
53
71
|
cursor: pointer;
|
|
54
72
|
}
|
|
55
73
|
|
|
74
|
+
/* Trailing alignment — box moves to the far edge, label stack to the
|
|
75
|
+
start. row-reverse keeps the box first in DOM order (input + .box
|
|
76
|
+
adjacency drives the focus ring) while space-between stretches the
|
|
77
|
+
row to its container. */
|
|
78
|
+
:host([alignment="trailing"]) .row {
|
|
79
|
+
flex-direction: row-reverse;
|
|
80
|
+
justify-content: space-between;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Comfortable density — label and supporting text stack beside the box. */
|
|
84
|
+
.content {
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
gap: var(--rel-spacing-3xs);
|
|
88
|
+
min-width: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.label-line {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: var(--rel-spacing-xs);
|
|
95
|
+
}
|
|
96
|
+
|
|
56
97
|
input {
|
|
57
98
|
position: absolute;
|
|
58
99
|
inset: 0;
|
|
@@ -195,7 +236,6 @@ export class RelCheckbox extends LitElement {
|
|
|
195
236
|
|
|
196
237
|
.text {
|
|
197
238
|
color: var(--rel-text-caption);
|
|
198
|
-
padding-left: 28px;
|
|
199
239
|
|
|
200
240
|
font-family: var(--rel-body-sm-regular-font-family);
|
|
201
241
|
|
|
@@ -208,8 +248,63 @@ export class RelCheckbox extends LitElement {
|
|
|
208
248
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
209
249
|
|
|
210
250
|
}
|
|
251
|
+
|
|
252
|
+
/* Compact density — supporting text sits under the whole row, indented
|
|
253
|
+
past the box (box width + row gap, matching the pre-existing 28px). */
|
|
254
|
+
.text-below {
|
|
255
|
+
padding-left: 28px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Size variant (medium is the base scale above). */
|
|
259
|
+
:host([size="small"]) .box {
|
|
260
|
+
width: 14px;
|
|
261
|
+
height: 14px;
|
|
262
|
+
border-radius: 3px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
:host([size="small"]) svg {
|
|
266
|
+
width: 10px;
|
|
267
|
+
height: 10px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
:host([size="small"]) .label {
|
|
271
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
272
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
273
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
274
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
275
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
:host([size="small"]) .text {
|
|
279
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
280
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
281
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
282
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
283
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
:host([size="small"]) .text-below {
|
|
287
|
+
padding-left: 24px;
|
|
288
|
+
}
|
|
211
289
|
`;
|
|
212
290
|
|
|
291
|
+
protected willUpdate(changed: PropertyValues<this>) {
|
|
292
|
+
if (
|
|
293
|
+
(changed.has("alignment") || changed.has("density")) &&
|
|
294
|
+
this.alignment === "trailing" &&
|
|
295
|
+
this.density === "compact"
|
|
296
|
+
) {
|
|
297
|
+
console.warn(
|
|
298
|
+
'[rel-checkbox] density="compact" has no trailing layout; rendering comfortable density.'
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Trailing has no compact layout in the spec — trailing wins. */
|
|
304
|
+
private get effectiveDensity(): CheckboxDensity {
|
|
305
|
+
return this.alignment === "trailing" ? "comfortable" : this.density;
|
|
306
|
+
}
|
|
307
|
+
|
|
213
308
|
private toggle() {
|
|
214
309
|
if (this.state === "disabled") return;
|
|
215
310
|
|
|
@@ -256,15 +351,27 @@ export class RelCheckbox extends LitElement {
|
|
|
256
351
|
</svg>
|
|
257
352
|
</span>
|
|
258
353
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
354
|
+
<span class="content">
|
|
355
|
+
<span class="label-line">
|
|
356
|
+
${this.icon
|
|
357
|
+
? html`<rel-icon
|
|
358
|
+
name="${this.icon}"
|
|
359
|
+
size="${this.size === "small" ? "sm" : "md"}"
|
|
360
|
+
class="icon-prefix"
|
|
361
|
+
></rel-icon>`
|
|
362
|
+
: null}
|
|
363
|
+
|
|
364
|
+
<span part="label" class="label">${this.label}</span>
|
|
365
|
+
</span>
|
|
366
|
+
|
|
367
|
+
${this.effectiveDensity === "comfortable" && this.supportingtext
|
|
368
|
+
? html`<span part="helper" class="text">${this.supportingtext}</span>`
|
|
369
|
+
: null}
|
|
370
|
+
</span>
|
|
264
371
|
</label>
|
|
265
372
|
|
|
266
|
-
${this.supportingtext
|
|
267
|
-
? html`<span part="helper" class="text">${this.supportingtext}</span>`
|
|
373
|
+
${this.effectiveDensity === "compact" && this.supportingtext
|
|
374
|
+
? html`<span part="helper" class="text text-below">${this.supportingtext}</span>`
|
|
268
375
|
: null}
|
|
269
376
|
</div>
|
|
270
377
|
`;
|
|
@@ -377,6 +377,20 @@ All interactions disabled:
|
|
|
377
377
|
<rel-pagination totalPages="10" page="5" disabled></rel-pagination>
|
|
378
378
|
```
|
|
379
379
|
|
|
380
|
+
### Very Large Page Counts
|
|
381
|
+
|
|
382
|
+
Page buttons auto-widen to fit their number (min-width 40px). Far first/last
|
|
383
|
+
buttons with numbers ≥ 10,000 are abbreviated so the control stays compact:
|
|
384
|
+
`10000` → `10k`, `368178` → `368k`, `1234567` → `1.2M`. The exact page number
|
|
385
|
+
is preserved in the button's `aria-label` and `title` (hover tooltip). The
|
|
386
|
+
current page and its immediate neighbors always render exact — abbreviating
|
|
387
|
+
them would make adjacent pages indistinguishable.
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<rel-pagination totalPages="368178" page="7"></rel-pagination>
|
|
391
|
+
<!-- [<] [1] ... [6] [7] [8] ... [368k] [>] -->
|
|
392
|
+
```
|
|
393
|
+
|
|
380
394
|
## Browser Support
|
|
381
395
|
|
|
382
396
|
Compatible with all modern browsers supporting:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
1
|
+
import { LitElement, html, css, nothing } from "lit";
|
|
2
2
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
3
|
|
|
4
4
|
@customElement("rel-pagination")
|
|
@@ -133,9 +133,10 @@ export class RelPagination extends LitElement {
|
|
|
133
133
|
display: inline-flex;
|
|
134
134
|
align-items: center;
|
|
135
135
|
justify-content: center;
|
|
136
|
-
width:
|
|
136
|
+
width: auto;
|
|
137
|
+
min-width: 40px;
|
|
137
138
|
height: 40px;
|
|
138
|
-
padding: var(--rel-spacing-xs);
|
|
139
|
+
padding: 0 var(--rel-spacing-xs);
|
|
139
140
|
border-radius: var(--rel-border-radius-sm);
|
|
140
141
|
border: 1px solid var(--rel-border-default);
|
|
141
142
|
background: var(--rel-bg-neutral);
|
|
@@ -376,6 +377,15 @@ export class RelPagination extends LitElement {
|
|
|
376
377
|
return pages;
|
|
377
378
|
}
|
|
378
379
|
|
|
380
|
+
/** Abbreviate large page numbers (368178 → "368k") so buttons stay compact.
|
|
381
|
+
Exact below 10,000; floored to k up to 1M; then M with one decimal. */
|
|
382
|
+
private formatPageLabel(n: number): string {
|
|
383
|
+
if (n < 10_000) return String(n);
|
|
384
|
+
if (n < 1_000_000) return `${Math.floor(n / 1000)}k`;
|
|
385
|
+
const m = Math.floor(n / 100_000) / 10;
|
|
386
|
+
return `${Number.isInteger(m) ? m.toFixed(0) : m}M`;
|
|
387
|
+
}
|
|
388
|
+
|
|
379
389
|
private handlePageChange(newPage: number) {
|
|
380
390
|
const total = this.computedTotalPages;
|
|
381
391
|
if (this.disabled || newPage < 1 || newPage > total || newPage === this.activePage) return;
|
|
@@ -427,17 +437,23 @@ export class RelPagination extends LitElement {
|
|
|
427
437
|
<div class="pag-stack">
|
|
428
438
|
${this.renderNavBtn("prev")}
|
|
429
439
|
<div class="pag-numbers">
|
|
430
|
-
${pages.map((p) =>
|
|
431
|
-
p === -1
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
440
|
+
${pages.map((p) => {
|
|
441
|
+
if (p === -1) return html`<span class="pag-ellipsis">...</span>`;
|
|
442
|
+
/* Only far first/last buttons abbreviate. Anything in the
|
|
443
|
+
visible window stays exact — abbreviating window pages like
|
|
444
|
+
368174/368175 would render identical "368k" buttons. */
|
|
445
|
+
const isNear = Math.abs(p - current) <= 1;
|
|
446
|
+
const isEdge = p === 1 || p === total;
|
|
447
|
+
const label = isEdge && !isNear ? this.formatPageLabel(p) : String(p);
|
|
448
|
+
return html`<button
|
|
449
|
+
type="button"
|
|
450
|
+
class="pag-page-btn ${isNear ? "near" : ""} ${p === 1 || p === total ? "edge" : ""}"
|
|
451
|
+
aria-label="Page ${p}"
|
|
452
|
+
title="${label === String(p) ? nothing : `Page ${p}`}"
|
|
453
|
+
aria-current="${p === current ? "page" : "false"}"
|
|
454
|
+
@click="${() => this.handlePageChange(p)}"
|
|
455
|
+
>${label}</button>`;
|
|
456
|
+
})}
|
|
441
457
|
</div>
|
|
442
458
|
${this.renderNavBtn("next")}
|
|
443
459
|
</div>
|