@crisp-ui-kit/crisp 0.29.0 → 0.30.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/cjs/components/slider/slider.js +90 -7
- package/dist/cjs/components/slider/slider.recipe.js +6 -0
- package/dist/esm/components/slider/slider.js +57 -7
- package/dist/esm/components/slider/slider.recipe.js +6 -0
- package/dist/slider.css +6 -0
- package/dist/styles.css +6 -0
- package/dist/types/components/slider/slider.d.ts +13 -2
- package/package.json +1 -1
|
@@ -1,22 +1,105 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
"use client";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
37
|
exports.Slider = Slider;
|
|
5
38
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
39
|
const react_slider_1 = require("@seed-design/react-slider");
|
|
40
|
+
const React = __importStar(require("react"));
|
|
7
41
|
const slider_recipe_1 = require("./slider.recipe");
|
|
8
|
-
|
|
9
|
-
|
|
42
|
+
const defaultThumbLabel = (index, count) => count === 2 ? (index === 0 ? "minimum" : "maximum") : String(index + 1);
|
|
43
|
+
/**
|
|
44
|
+
* The name for each thumb.
|
|
45
|
+
*
|
|
46
|
+
* The thumbs are what carry `role="slider"`, and they are rendered by the
|
|
47
|
+
* engine, so a name put on the crisp root — an `aria-label` prop, or a
|
|
48
|
+
* FormField's `<label for>` — reached nothing at all (#248). The root is a
|
|
49
|
+
* plain `<div>`: it has no role to name.
|
|
50
|
+
*
|
|
51
|
+
* Both sources are threaded down. Text (`aria-label`) is composed directly;
|
|
52
|
+
* an id reference (`aria-labelledby`, or the label found for the root's own
|
|
53
|
+
* id) is concatenated with a hidden span per thumb, because that is how ARIA
|
|
54
|
+
* builds a name out of parts without inventing a second copy of the label.
|
|
55
|
+
*/
|
|
56
|
+
function Thumbs({ ariaLabel, labelledBy, thumbLabel, idBase, }) {
|
|
10
57
|
const { values } = (0, react_slider_1.useSliderContext)();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
58
|
+
const count = values.length;
|
|
59
|
+
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: values.map((_, i) => {
|
|
60
|
+
const suffix = count > 1 ? thumbLabel(i, count) : undefined;
|
|
61
|
+
const suffixId = `${idBase}-thumb-${i}`;
|
|
62
|
+
return ((0, jsx_runtime_1.jsxs)(React.Fragment
|
|
63
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: thumbs are positional — the index IS the identity the engine addresses them by
|
|
64
|
+
, { children: [suffix && labelledBy && !ariaLabel && ((0, jsx_runtime_1.jsx)("span", { id: suffixId, className: "slider-thumblabel", children: suffix })), (0, jsx_runtime_1.jsx)(react_slider_1.SliderThumb, { thumbIndex: i, className: "slider-thumb", "aria-label": ariaLabel
|
|
65
|
+
? suffix
|
|
66
|
+
? `${ariaLabel} ${suffix}`
|
|
67
|
+
: ariaLabel
|
|
68
|
+
: undefined, "aria-labelledby": !ariaLabel && labelledBy
|
|
69
|
+
? suffix
|
|
70
|
+
? `${labelledBy} ${suffixId}`
|
|
71
|
+
: labelledBy
|
|
72
|
+
: undefined })] }, i));
|
|
73
|
+
}) }));
|
|
14
74
|
}
|
|
15
75
|
/**
|
|
16
76
|
* A range input, driven by seed-design's headless engine so the keyboard model,
|
|
17
77
|
* the pointer maths and multi-thumb constraints are theirs by construction.
|
|
18
78
|
* Pass one value for a simple slider, two for a range.
|
|
19
79
|
*/
|
|
20
|
-
function Slider({ className, ...props }) {
|
|
21
|
-
|
|
80
|
+
function Slider({ className, thumbLabel = defaultThumbLabel, ...props }) {
|
|
81
|
+
const rootRef = React.useRef(null);
|
|
82
|
+
const generatedId = React.useId();
|
|
83
|
+
const idBase = props.id ?? generatedId;
|
|
84
|
+
const ariaLabel = props["aria-label"];
|
|
85
|
+
const labelledByProp = props["aria-labelledby"];
|
|
86
|
+
// A `<label for>` on the root cannot name a div, and the thumbs are what
|
|
87
|
+
// need the name, so the label is resolved from the DOM and threaded down.
|
|
88
|
+
// State rather than a mutation: the thumbs take props, so the name belongs
|
|
89
|
+
// in the render rather than being written over it afterwards.
|
|
90
|
+
const [foundLabelId, setFoundLabelId] = React.useState();
|
|
91
|
+
React.useEffect(() => {
|
|
92
|
+
if (ariaLabel || labelledByProp)
|
|
93
|
+
return;
|
|
94
|
+
const el = rootRef.current;
|
|
95
|
+
if (!el?.id)
|
|
96
|
+
return;
|
|
97
|
+
const label = el.ownerDocument.querySelector(`label[for="${CSS.escape(el.id)}"]`);
|
|
98
|
+
if (!label)
|
|
99
|
+
return;
|
|
100
|
+
if (!label.id)
|
|
101
|
+
label.id = `${el.id}-label`;
|
|
102
|
+
setFoundLabelId(label.id);
|
|
103
|
+
}, [ariaLabel, labelledByProp]);
|
|
104
|
+
return ((0, jsx_runtime_1.jsx)(react_slider_1.SliderRoot, { ref: rootRef, ...props, className: (0, slider_recipe_1.sliderRecipe)({ className }), children: (0, jsx_runtime_1.jsxs)("span", { className: "slider-control", children: [(0, jsx_runtime_1.jsx)("span", { className: "slider-track", children: (0, jsx_runtime_1.jsx)(react_slider_1.SliderRange, { className: "slider-range" }) }), (0, jsx_runtime_1.jsx)(Thumbs, { ariaLabel: ariaLabel, labelledBy: labelledByProp ?? foundLabelId, thumbLabel: thumbLabel, idBase: idBase })] }) }));
|
|
22
105
|
}
|
|
@@ -27,6 +27,12 @@ const config = (0, core_1.defineRecipe)({
|
|
|
27
27
|
width: 100%; min-width: 0; gap: ${tokens_1.contract.space["0.5"]};
|
|
28
28
|
font-family: ${tokens_1.contract.font.sans}; user-select: none; touch-action: none;
|
|
29
29
|
|
|
30
|
+
/* the position words ("minimum"/"maximum") that ARIA concatenates onto a
|
|
31
|
+
thumb's name — read, never drawn */
|
|
32
|
+
& .slider-thumblabel {
|
|
33
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
34
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
35
|
+
}
|
|
30
36
|
& .slider-control { position: relative; height: 26px; display: flex; align-items: center; }
|
|
31
37
|
|
|
32
38
|
& .slider-track {
|
|
@@ -1,19 +1,69 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { SliderRange, SliderRoot, SliderThumb, useSliderContext, } from "@seed-design/react-slider";
|
|
4
|
+
import * as React from "react";
|
|
4
5
|
import { sliderRecipe } from "./slider.recipe.js";
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
const defaultThumbLabel = (index, count) => count === 2 ? (index === 0 ? "minimum" : "maximum") : String(index + 1);
|
|
7
|
+
/**
|
|
8
|
+
* The name for each thumb.
|
|
9
|
+
*
|
|
10
|
+
* The thumbs are what carry `role="slider"`, and they are rendered by the
|
|
11
|
+
* engine, so a name put on the crisp root — an `aria-label` prop, or a
|
|
12
|
+
* FormField's `<label for>` — reached nothing at all (#248). The root is a
|
|
13
|
+
* plain `<div>`: it has no role to name.
|
|
14
|
+
*
|
|
15
|
+
* Both sources are threaded down. Text (`aria-label`) is composed directly;
|
|
16
|
+
* an id reference (`aria-labelledby`, or the label found for the root's own
|
|
17
|
+
* id) is concatenated with a hidden span per thumb, because that is how ARIA
|
|
18
|
+
* builds a name out of parts without inventing a second copy of the label.
|
|
19
|
+
*/
|
|
20
|
+
function Thumbs({ ariaLabel, labelledBy, thumbLabel, idBase, }) {
|
|
7
21
|
const { values } = useSliderContext();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
22
|
+
const count = values.length;
|
|
23
|
+
return (_jsx(_Fragment, { children: values.map((_, i) => {
|
|
24
|
+
const suffix = count > 1 ? thumbLabel(i, count) : undefined;
|
|
25
|
+
const suffixId = `${idBase}-thumb-${i}`;
|
|
26
|
+
return (_jsxs(React.Fragment
|
|
27
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: thumbs are positional — the index IS the identity the engine addresses them by
|
|
28
|
+
, { children: [suffix && labelledBy && !ariaLabel && (_jsx("span", { id: suffixId, className: "slider-thumblabel", children: suffix })), _jsx(SliderThumb, { thumbIndex: i, className: "slider-thumb", "aria-label": ariaLabel
|
|
29
|
+
? suffix
|
|
30
|
+
? `${ariaLabel} ${suffix}`
|
|
31
|
+
: ariaLabel
|
|
32
|
+
: undefined, "aria-labelledby": !ariaLabel && labelledBy
|
|
33
|
+
? suffix
|
|
34
|
+
? `${labelledBy} ${suffixId}`
|
|
35
|
+
: labelledBy
|
|
36
|
+
: undefined })] }, i));
|
|
37
|
+
}) }));
|
|
11
38
|
}
|
|
12
39
|
/**
|
|
13
40
|
* A range input, driven by seed-design's headless engine so the keyboard model,
|
|
14
41
|
* the pointer maths and multi-thumb constraints are theirs by construction.
|
|
15
42
|
* Pass one value for a simple slider, two for a range.
|
|
16
43
|
*/
|
|
17
|
-
export function Slider({ className, ...props }) {
|
|
18
|
-
|
|
44
|
+
export function Slider({ className, thumbLabel = defaultThumbLabel, ...props }) {
|
|
45
|
+
const rootRef = React.useRef(null);
|
|
46
|
+
const generatedId = React.useId();
|
|
47
|
+
const idBase = props.id ?? generatedId;
|
|
48
|
+
const ariaLabel = props["aria-label"];
|
|
49
|
+
const labelledByProp = props["aria-labelledby"];
|
|
50
|
+
// A `<label for>` on the root cannot name a div, and the thumbs are what
|
|
51
|
+
// need the name, so the label is resolved from the DOM and threaded down.
|
|
52
|
+
// State rather than a mutation: the thumbs take props, so the name belongs
|
|
53
|
+
// in the render rather than being written over it afterwards.
|
|
54
|
+
const [foundLabelId, setFoundLabelId] = React.useState();
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
if (ariaLabel || labelledByProp)
|
|
57
|
+
return;
|
|
58
|
+
const el = rootRef.current;
|
|
59
|
+
if (!el?.id)
|
|
60
|
+
return;
|
|
61
|
+
const label = el.ownerDocument.querySelector(`label[for="${CSS.escape(el.id)}"]`);
|
|
62
|
+
if (!label)
|
|
63
|
+
return;
|
|
64
|
+
if (!label.id)
|
|
65
|
+
label.id = `${el.id}-label`;
|
|
66
|
+
setFoundLabelId(label.id);
|
|
67
|
+
}, [ariaLabel, labelledByProp]);
|
|
68
|
+
return (_jsx(SliderRoot, { ref: rootRef, ...props, className: sliderRecipe({ className }), children: _jsxs("span", { className: "slider-control", children: [_jsx("span", { className: "slider-track", children: _jsx(SliderRange, { className: "slider-range" }) }), _jsx(Thumbs, { ariaLabel: ariaLabel, labelledBy: labelledByProp ?? foundLabelId, thumbLabel: thumbLabel, idBase: idBase })] }) }));
|
|
19
69
|
}
|
|
@@ -24,6 +24,12 @@ const config = defineRecipe({
|
|
|
24
24
|
width: 100%; min-width: 0; gap: ${c.space["0.5"]};
|
|
25
25
|
font-family: ${c.font.sans}; user-select: none; touch-action: none;
|
|
26
26
|
|
|
27
|
+
/* the position words ("minimum"/"maximum") that ARIA concatenates onto a
|
|
28
|
+
thumb's name — read, never drawn */
|
|
29
|
+
& .slider-thumblabel {
|
|
30
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
31
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
32
|
+
}
|
|
27
33
|
& .slider-control { position: relative; height: 26px; display: flex; align-items: center; }
|
|
28
34
|
|
|
29
35
|
& .slider-track {
|
package/dist/slider.css
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
width: 100%; min-width: 0; gap: var(--space-0-5);
|
|
5
5
|
font-family: var(--font-sans); user-select: none; touch-action: none;
|
|
6
6
|
|
|
7
|
+
/* the position words ("minimum"/"maximum") that ARIA concatenates onto a
|
|
8
|
+
thumb's name — read, never drawn */
|
|
9
|
+
& .slider-thumblabel {
|
|
10
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
11
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
12
|
+
}
|
|
7
13
|
& .slider-control { position: relative; height: 26px; display: flex; align-items: center; }
|
|
8
14
|
|
|
9
15
|
& .slider-track {
|
package/dist/styles.css
CHANGED
|
@@ -1454,6 +1454,12 @@
|
|
|
1454
1454
|
width: 100%; min-width: 0; gap: var(--space-0-5);
|
|
1455
1455
|
font-family: var(--font-sans); user-select: none; touch-action: none;
|
|
1456
1456
|
|
|
1457
|
+
/* the position words ("minimum"/"maximum") that ARIA concatenates onto a
|
|
1458
|
+
thumb's name — read, never drawn */
|
|
1459
|
+
& .slider-thumblabel {
|
|
1460
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
1461
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
1462
|
+
}
|
|
1457
1463
|
& .slider-control { position: relative; height: 26px; display: flex; align-items: center; }
|
|
1458
1464
|
|
|
1459
1465
|
& .slider-track {
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { type SliderRootProps } from "@seed-design/react-slider";
|
|
2
|
-
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type SliderProps = SliderRootProps & {
|
|
4
|
+
/**
|
|
5
|
+
* What to call each thumb when there is more than one, appended to the
|
|
6
|
+
* slider's own name: "Price minimum" / "Price maximum".
|
|
7
|
+
*
|
|
8
|
+
* A formatter, not two strings, because the number of thumbs is open-ended
|
|
9
|
+
* and the words differ by language. With a single thumb it is never called —
|
|
10
|
+
* the thumb simply takes the slider's name.
|
|
11
|
+
*/
|
|
12
|
+
thumbLabel?: (index: number, count: number) => string;
|
|
13
|
+
};
|
|
3
14
|
/**
|
|
4
15
|
* A range input, driven by seed-design's headless engine so the keyboard model,
|
|
5
16
|
* the pointer maths and multi-thumb constraints are theirs by construction.
|
|
6
17
|
* Pass one value for a simple slider, two for a range.
|
|
7
18
|
*/
|
|
8
|
-
export declare function Slider({ className, ...props }: SliderProps):
|
|
19
|
+
export declare function Slider({ className, thumbLabel, ...props }: SliderProps): React.JSX.Element;
|