@arun-dev/headless 2.1.0 → 3.0.1
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/README.md +38 -22
- package/dist/button/index.cjs +25 -136
- package/dist/button/index.d.cts +34 -23
- package/dist/button/index.d.ts +34 -23
- package/dist/button/index.js +23 -16
- package/dist/{chunk-PYLPZVWQ.js → chunk-OQTWWZ4B.js} +3 -38
- package/dist/index.cjs +3 -43
- package/dist/index.d.cts +18 -61
- package/dist/index.d.ts +18 -61
- package/dist/index.js +1 -7
- package/dist/switch/index.cjs +117 -199
- package/dist/switch/index.d.cts +41 -42
- package/dist/switch/index.d.ts +41 -42
- package/dist/switch/index.js +74 -40
- package/package.json +2 -2
- package/dist/chunk-TNMGZWHA.js +0 -96
package/README.md
CHANGED
|
@@ -28,24 +28,32 @@ import { Switch } from '@arun-dev/headless/switch';
|
|
|
28
28
|
|
|
29
29
|
| Component | Parts | Props |
|
|
30
30
|
| --------- | ----------------------- | --------------------------------------------------------------------------- |
|
|
31
|
-
| `Button` | — | `disabled`, `
|
|
31
|
+
| `Button` | — | `disabled`, `href`, `type` |
|
|
32
32
|
| `Switch` | `Switch.Root`, `.Thumb` | `checked`, `defaultChecked`, `onCheckedChange`, `disabled`, `name`, `value` |
|
|
33
33
|
|
|
34
|
-
`Button`
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
`Button` renders a `<button>`, or a real `<a href>` when you give it an `href` — a control that
|
|
35
|
+
navigates should be an anchor, so middle-click, cmd-click and "link" in assistive technology all
|
|
36
|
+
keep working. It defaults `type="button"` so a button never submits a form by accident, and a
|
|
37
|
+
disabled `href` renders a `<button disabled>`, because a link that navigates nowhere is not a link.
|
|
38
|
+
A `render` component cannot be inspected, so a disabled Button gives it `aria-disabled` and
|
|
39
|
+
`data-disabled` rather than `disabled` — actually disabling itself is up to the component.
|
|
38
40
|
|
|
39
41
|
```tsx
|
|
40
42
|
import { Button } from '@arun-dev/headless/button';
|
|
41
43
|
|
|
42
|
-
<Button
|
|
44
|
+
<Button href="/docs" target="_blank" rel="noreferrer">
|
|
45
|
+
Docs
|
|
46
|
+
</Button>;
|
|
43
47
|
```
|
|
44
48
|
|
|
45
49
|
`Switch.Root` renders a native `<button>`, so focus, `Space`, `Enter` and disabled semantics come
|
|
46
50
|
from the platform. It carries `role="switch"` and `aria-checked`, but **no accessible name** —
|
|
47
51
|
wrap it in a `<label>` or pass `aria-label`. A headless component should not guess at your copy.
|
|
48
52
|
|
|
53
|
+
In a form it behaves like a native checkbox: with a `name` it submits its `value` only when checked
|
|
54
|
+
and enabled, and `form.reset()` returns it to the state it mounted with, reported through
|
|
55
|
+
`onCheckedChange`.
|
|
56
|
+
|
|
49
57
|
## State reaches CSS through `data-*`
|
|
50
58
|
|
|
51
59
|
A headless component owns no class names, so state is projected onto the DOM instead. That
|
|
@@ -87,6 +95,15 @@ Every part takes a `render` prop to change the element, and spreads unrecognised
|
|
|
87
95
|
`className` is concatenated, `style` is merged, and refs are merged — so a `ref` on the `render`
|
|
88
96
|
element and a `ref` on the component both receive the node.
|
|
89
97
|
|
|
98
|
+
Everything else, `children` included, follows the plain rule: the `render` element's own props
|
|
99
|
+
come last and win. An element that declares no children inherits yours, one that declares some
|
|
100
|
+
keeps them.
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<Button render={<a href="/docs" />}>Docs</Button> // renders "Docs"
|
|
104
|
+
<Button render={<a href="/docs">Read the docs</a>}>Docs</Button> // renders "Read the docs"
|
|
105
|
+
```
|
|
106
|
+
|
|
90
107
|
Event handlers are chained rather than replaced, and **your handler runs before the component's**,
|
|
91
108
|
so you can stop it:
|
|
92
109
|
|
|
@@ -101,24 +118,23 @@ so you can stop it:
|
|
|
101
118
|
The primitives the components are built from are exported from the root, for building your own:
|
|
102
119
|
|
|
103
120
|
```ts
|
|
104
|
-
import {
|
|
105
|
-
useRender,
|
|
106
|
-
useControlled,
|
|
107
|
-
mergeProps,
|
|
108
|
-
getStateAttributes,
|
|
109
|
-
booleanAttribute,
|
|
110
|
-
} from '@arun-dev/headless';
|
|
121
|
+
import { useRender, useControlled, mergeProps } from '@arun-dev/headless';
|
|
111
122
|
```
|
|
112
123
|
|
|
113
|
-
| Export
|
|
114
|
-
|
|
|
115
|
-
| `useRender`
|
|
116
|
-
| `useControlled`
|
|
117
|
-
| `mergeProps`
|
|
118
|
-
| `
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
| Export | Purpose |
|
|
125
|
+
| ---------------- | ------------------------------------------------------------------------- |
|
|
126
|
+
| `useRender` | Resolves what a part renders — merges props and applies `render` |
|
|
127
|
+
| `useControlled` | One value, controlled or uncontrolled, decided at mount |
|
|
128
|
+
| `mergeProps` | Merges prop objects: handlers chain, `className` concatenates, refs merge |
|
|
129
|
+
| `ComponentEvent` | Type for a handler that can call `preventComponentHandler()` |
|
|
130
|
+
|
|
131
|
+
State attributes are plain objects a component spreads onto its element — React drops the
|
|
132
|
+
`undefined` ones — so there is no mapping layer to learn:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
'data-checked': checked ? '' : undefined,
|
|
136
|
+
'data-unchecked': checked ? undefined : '',
|
|
137
|
+
```
|
|
122
138
|
|
|
123
139
|
Everything this library uses to implement its own components stays private, so it can
|
|
124
140
|
change without breaking anyone. The public surface is the table above plus the
|
package/dist/button/index.cjs
CHANGED
|
@@ -24,9 +24,6 @@ __export(button_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(button_exports);
|
|
26
26
|
|
|
27
|
-
// src/button/Button.tsx
|
|
28
|
-
var import_react3 = require("react");
|
|
29
|
-
|
|
30
27
|
// src/core/useRender.ts
|
|
31
28
|
var import_react = require("react");
|
|
32
29
|
|
|
@@ -62,18 +59,9 @@ function isSyntheticEvent(event) {
|
|
|
62
59
|
function makeEventPreventable(event) {
|
|
63
60
|
if (Object.prototype.hasOwnProperty.call(event, "preventComponentHandler")) return;
|
|
64
61
|
const target = event;
|
|
65
|
-
|
|
62
|
+
target.preventComponentHandler = () => {
|
|
66
63
|
target.componentHandlerPrevented = true;
|
|
67
64
|
};
|
|
68
|
-
target.preventComponentHandler = prevent;
|
|
69
|
-
const native = event.nativeEvent;
|
|
70
|
-
const stopImmediate = native?.stopImmediatePropagation?.bind(native);
|
|
71
|
-
if (native && stopImmediate) {
|
|
72
|
-
native.stopImmediatePropagation = () => {
|
|
73
|
-
stopImmediate();
|
|
74
|
-
prevent();
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
65
|
}
|
|
78
66
|
function isComponentHandlerPrevented(event) {
|
|
79
67
|
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
@@ -111,150 +99,51 @@ function mergeProps(...objects) {
|
|
|
111
99
|
return merged;
|
|
112
100
|
}
|
|
113
101
|
|
|
114
|
-
// src/core/stateAttributes.ts
|
|
115
|
-
function getStateAttributes(state, mapping) {
|
|
116
|
-
if (!mapping) return {};
|
|
117
|
-
const attributes = {};
|
|
118
|
-
for (const key of Object.keys(state)) {
|
|
119
|
-
const toAttributes = mapping[key];
|
|
120
|
-
if (!toAttributes) continue;
|
|
121
|
-
const result = toAttributes(state[key]);
|
|
122
|
-
if (result) Object.assign(attributes, result);
|
|
123
|
-
}
|
|
124
|
-
return attributes;
|
|
125
|
-
}
|
|
126
|
-
function booleanAttribute(whenTrue, whenFalse) {
|
|
127
|
-
return (value) => {
|
|
128
|
-
if (value) return { [whenTrue]: "" };
|
|
129
|
-
return whenFalse ? { [whenFalse]: "" } : null;
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
var disabledAttribute = booleanAttribute("data-disabled");
|
|
133
|
-
|
|
134
102
|
// src/core/useRender.ts
|
|
135
103
|
function useRender({
|
|
136
104
|
render,
|
|
137
105
|
defaultTagName,
|
|
138
106
|
props,
|
|
139
|
-
consumerProps
|
|
140
|
-
state,
|
|
141
|
-
stateAttributes
|
|
107
|
+
consumerProps
|
|
142
108
|
}) {
|
|
143
|
-
const
|
|
144
|
-
const merged = mergeProps(attributes, props, consumerProps);
|
|
109
|
+
const merged = mergeProps(props, consumerProps);
|
|
145
110
|
if ((0, import_react.isValidElement)(render)) {
|
|
146
111
|
return (0, import_react.cloneElement)(render, mergeProps(merged, render.props));
|
|
147
112
|
}
|
|
148
113
|
return (0, import_react.createElement)(defaultTagName, merged);
|
|
149
114
|
}
|
|
150
115
|
|
|
151
|
-
// src/useButton.ts
|
|
152
|
-
var import_react2 = require("react");
|
|
153
|
-
var ACTIVATION_HANDLERS = [
|
|
154
|
-
"onClick",
|
|
155
|
-
"onDoubleClick",
|
|
156
|
-
"onMouseDown",
|
|
157
|
-
"onMouseUp",
|
|
158
|
-
"onPointerDown",
|
|
159
|
-
"onPointerUp",
|
|
160
|
-
"onTouchStart",
|
|
161
|
-
"onTouchEnd",
|
|
162
|
-
"onKeyDown",
|
|
163
|
-
"onKeyUp",
|
|
164
|
-
"onKeyPress"
|
|
165
|
-
];
|
|
166
|
-
function activationHandlers() {
|
|
167
|
-
return {
|
|
168
|
-
onKeyDown(event) {
|
|
169
|
-
if (event.target !== event.currentTarget || event.defaultPrevented) return;
|
|
170
|
-
if (event.key === " ") {
|
|
171
|
-
event.preventDefault();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (event.key === "Enter") {
|
|
175
|
-
event.preventDefault();
|
|
176
|
-
click(event.currentTarget);
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
onKeyUp(event) {
|
|
180
|
-
if (event.target !== event.currentTarget || event.defaultPrevented) return;
|
|
181
|
-
if (event.key === " ") click(event.currentTarget);
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function click(element) {
|
|
186
|
-
if (element && typeof element.click === "function") {
|
|
187
|
-
element.click();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function useButton({
|
|
191
|
-
disabled = false,
|
|
192
|
-
native,
|
|
193
|
-
props = {}
|
|
194
|
-
}) {
|
|
195
|
-
const elementRef = (0, import_react2.useRef)(null);
|
|
196
|
-
(0, import_react2.useEffect)(() => {
|
|
197
|
-
if (process.env.NODE_ENV === "production") return;
|
|
198
|
-
const element = elementRef.current;
|
|
199
|
-
if (!element) return;
|
|
200
|
-
const isButtonTag = element.tagName === "BUTTON";
|
|
201
|
-
if (native && !isButtonTag) {
|
|
202
|
-
console.error(
|
|
203
|
-
`A component expected a native <button> but rendered <${element.tagName.toLowerCase()}>. Focus, keyboard activation and \`disabled\` will not behave natively.`
|
|
204
|
-
);
|
|
205
|
-
} else if (!native && isButtonTag) {
|
|
206
|
-
console.error(
|
|
207
|
-
"A component rendered a native <button> while treating it as a non-native element, so it carries synthesised attributes it does not need."
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}, [native]);
|
|
211
|
-
return { props: buttonProps({ disabled, native, props }), ref: elementRef };
|
|
212
|
-
}
|
|
213
|
-
function buttonProps({ disabled, native, props }) {
|
|
214
|
-
if (!disabled) {
|
|
215
|
-
return native ? mergeProps({ type: "button" }, props) : mergeProps({ tabIndex: 0 }, activationHandlers(), props);
|
|
216
|
-
}
|
|
217
|
-
if (native) return mergeProps({ type: "button" }, props, { disabled: true, "data-disabled": "" });
|
|
218
|
-
const sanitised = {};
|
|
219
|
-
for (const key of Object.keys(props)) {
|
|
220
|
-
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) continue;
|
|
221
|
-
sanitised[key] = props[key];
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
...sanitised,
|
|
225
|
-
"aria-disabled": true,
|
|
226
|
-
"data-disabled": "",
|
|
227
|
-
// Mirrors a native disabled button, which is not focusable.
|
|
228
|
-
tabIndex: -1
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function retractActivationProps(props) {
|
|
232
|
-
const overrides = {};
|
|
233
|
-
for (const key of Object.keys(props)) {
|
|
234
|
-
if (key === "href" || ACTIVATION_HANDLERS.includes(key)) overrides[key] = void 0;
|
|
235
|
-
}
|
|
236
|
-
return overrides;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
116
|
// src/button/Button.tsx
|
|
240
117
|
function Button({
|
|
241
118
|
disabled = false,
|
|
242
|
-
|
|
119
|
+
href,
|
|
120
|
+
type = "button",
|
|
243
121
|
className,
|
|
244
122
|
children,
|
|
245
123
|
render,
|
|
246
124
|
...rest
|
|
247
125
|
}) {
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
126
|
+
const linksOut = href !== void 0 && !disabled;
|
|
127
|
+
const rendersButton = render === void 0 ? !linksOut : render.type === "button";
|
|
128
|
+
if (process.env.NODE_ENV !== "production" && disabled && render !== void 0) {
|
|
129
|
+
const renderProps = render.props;
|
|
130
|
+
if (renderProps?.href !== void 0) {
|
|
131
|
+
console.error(
|
|
132
|
+
"Button: `disabled` cannot remove the `href` on a `render` element \u2014 its own props outrank the component. Pass the URL as `href` on Button instead."
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const elementProps = rendersButton ? { type, disabled: disabled || void 0 } : { href: linksOut ? href : void 0, "aria-disabled": disabled || void 0 };
|
|
253
137
|
return useRender({
|
|
254
|
-
render
|
|
255
|
-
defaultTagName: "button",
|
|
256
|
-
props: {
|
|
257
|
-
|
|
138
|
+
render,
|
|
139
|
+
defaultTagName: linksOut ? "a" : "button",
|
|
140
|
+
props: {
|
|
141
|
+
...elementProps,
|
|
142
|
+
"data-disabled": disabled ? "" : void 0,
|
|
143
|
+
className,
|
|
144
|
+
children
|
|
145
|
+
},
|
|
146
|
+
consumerProps: rest
|
|
258
147
|
});
|
|
259
148
|
}
|
|
260
149
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/button/index.d.cts
CHANGED
|
@@ -1,44 +1,55 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Button's own props. Everything else — `id`, `className`, `children`, `aria-*`,
|
|
6
|
+
* `data-*`, event handlers — comes from React's own `<button>` props, so it is typed
|
|
7
|
+
* and checked without being declared here.
|
|
8
|
+
*/
|
|
9
|
+
type ButtonOwnProps = {
|
|
5
10
|
/**
|
|
6
|
-
* Prevents activation. On a
|
|
7
|
-
*
|
|
8
|
-
*
|
|
11
|
+
* Prevents activation. On a `<button>` the platform does it; on the `<a>` that
|
|
12
|
+
* `href` renders, dropping the `href` does it — the DOM leaves an anchor without one
|
|
13
|
+
* unfocusable, unactivatable and no longer a link.
|
|
14
|
+
*
|
|
15
|
+
* A `render` component cannot be inspected, so it gets `aria-disabled` and
|
|
16
|
+
* `data-disabled` rather than `disabled`: actually disabling itself is up to it.
|
|
9
17
|
*/
|
|
10
18
|
disabled?: boolean;
|
|
11
19
|
/**
|
|
12
|
-
*
|
|
20
|
+
* Renders an `<a href>` instead of a `<button>`.
|
|
13
21
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
22
|
+
* A control that navigates should be an anchor, so middle-click, cmd-click, the
|
|
23
|
+
* status bar and "link" in assistive technology all work. It is a prop rather than
|
|
24
|
+
* something to write on a `render` element because `disabled` has to be able to take
|
|
25
|
+
* it away, and a `render` element's own props outrank everything.
|
|
17
26
|
*/
|
|
18
|
-
|
|
19
|
-
className?: string;
|
|
20
|
-
children?: ReactNode;
|
|
27
|
+
href?: string;
|
|
21
28
|
/**
|
|
22
|
-
* Element to render instead of the default
|
|
29
|
+
* Element or component to render instead of the default. Props, className, event
|
|
23
30
|
* handlers and ref are merged onto it.
|
|
31
|
+
*
|
|
32
|
+
* @example <Button render={<NextLink href="/docs" />}>Docs</Button>
|
|
24
33
|
*/
|
|
25
34
|
render?: ReactElement;
|
|
35
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
26
36
|
ref?: Ref<HTMLElement>;
|
|
27
|
-
}
|
|
37
|
+
};
|
|
38
|
+
type ButtonProps = ButtonOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ButtonOwnProps> & Pick<ComponentPropsWithRef<'a'>, 'target' | 'rel' | 'download'>;
|
|
28
39
|
/**
|
|
29
40
|
* A button — behaviour only, no styling.
|
|
30
41
|
*
|
|
31
|
-
*
|
|
32
|
-
* and
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* It has no `href`. A control that navigates should be an `<a>`, so that middle-click,
|
|
36
|
-
* cmd-click, the status bar and "link" in assistive technology all work — pass one in:
|
|
42
|
+
* Renders a `<button>`, or an `<a href>` when `href` is set. Both are natively
|
|
43
|
+
* focusable and keyboard-activatable, so nothing here is synthesised: the component
|
|
44
|
+
* picks the right element, defaults `type` so a button does not submit by accident,
|
|
45
|
+
* and expresses `disabled` the way that element already understands.
|
|
37
46
|
*
|
|
38
|
-
*
|
|
47
|
+
* A disabled `href` renders a `<button disabled>` rather than a dead link — the
|
|
48
|
+
* platform then removes it from the tab order and suppresses activation, with no
|
|
49
|
+
* handler stripping or `tabindex` bookkeeping of our own.
|
|
39
50
|
*
|
|
40
|
-
* Emits `data-disabled` so one selector styles a disabled control
|
|
51
|
+
* Emits `data-disabled` so one selector styles a disabled control either way.
|
|
41
52
|
*/
|
|
42
|
-
declare function Button({ disabled,
|
|
53
|
+
declare function Button({ disabled, href, type, className, children, render, ...rest }: ButtonProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
43
54
|
|
|
44
55
|
export { Button, type ButtonProps };
|
package/dist/button/index.d.ts
CHANGED
|
@@ -1,44 +1,55 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Button's own props. Everything else — `id`, `className`, `children`, `aria-*`,
|
|
6
|
+
* `data-*`, event handlers — comes from React's own `<button>` props, so it is typed
|
|
7
|
+
* and checked without being declared here.
|
|
8
|
+
*/
|
|
9
|
+
type ButtonOwnProps = {
|
|
5
10
|
/**
|
|
6
|
-
* Prevents activation. On a
|
|
7
|
-
*
|
|
8
|
-
*
|
|
11
|
+
* Prevents activation. On a `<button>` the platform does it; on the `<a>` that
|
|
12
|
+
* `href` renders, dropping the `href` does it — the DOM leaves an anchor without one
|
|
13
|
+
* unfocusable, unactivatable and no longer a link.
|
|
14
|
+
*
|
|
15
|
+
* A `render` component cannot be inspected, so it gets `aria-disabled` and
|
|
16
|
+
* `data-disabled` rather than `disabled`: actually disabling itself is up to it.
|
|
9
17
|
*/
|
|
10
18
|
disabled?: boolean;
|
|
11
19
|
/**
|
|
12
|
-
*
|
|
20
|
+
* Renders an `<a href>` instead of a `<button>`.
|
|
13
21
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
22
|
+
* A control that navigates should be an anchor, so middle-click, cmd-click, the
|
|
23
|
+
* status bar and "link" in assistive technology all work. It is a prop rather than
|
|
24
|
+
* something to write on a `render` element because `disabled` has to be able to take
|
|
25
|
+
* it away, and a `render` element's own props outrank everything.
|
|
17
26
|
*/
|
|
18
|
-
|
|
19
|
-
className?: string;
|
|
20
|
-
children?: ReactNode;
|
|
27
|
+
href?: string;
|
|
21
28
|
/**
|
|
22
|
-
* Element to render instead of the default
|
|
29
|
+
* Element or component to render instead of the default. Props, className, event
|
|
23
30
|
* handlers and ref are merged onto it.
|
|
31
|
+
*
|
|
32
|
+
* @example <Button render={<NextLink href="/docs" />}>Docs</Button>
|
|
24
33
|
*/
|
|
25
34
|
render?: ReactElement;
|
|
35
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
26
36
|
ref?: Ref<HTMLElement>;
|
|
27
|
-
}
|
|
37
|
+
};
|
|
38
|
+
type ButtonProps = ButtonOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ButtonOwnProps> & Pick<ComponentPropsWithRef<'a'>, 'target' | 'rel' | 'download'>;
|
|
28
39
|
/**
|
|
29
40
|
* A button — behaviour only, no styling.
|
|
30
41
|
*
|
|
31
|
-
*
|
|
32
|
-
* and
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* It has no `href`. A control that navigates should be an `<a>`, so that middle-click,
|
|
36
|
-
* cmd-click, the status bar and "link" in assistive technology all work — pass one in:
|
|
42
|
+
* Renders a `<button>`, or an `<a href>` when `href` is set. Both are natively
|
|
43
|
+
* focusable and keyboard-activatable, so nothing here is synthesised: the component
|
|
44
|
+
* picks the right element, defaults `type` so a button does not submit by accident,
|
|
45
|
+
* and expresses `disabled` the way that element already understands.
|
|
37
46
|
*
|
|
38
|
-
*
|
|
47
|
+
* A disabled `href` renders a `<button disabled>` rather than a dead link — the
|
|
48
|
+
* platform then removes it from the tab order and suppresses activation, with no
|
|
49
|
+
* handler stripping or `tabindex` bookkeeping of our own.
|
|
39
50
|
*
|
|
40
|
-
* Emits `data-disabled` so one selector styles a disabled control
|
|
51
|
+
* Emits `data-disabled` so one selector styles a disabled control either way.
|
|
41
52
|
*/
|
|
42
|
-
declare function Button({ disabled,
|
|
53
|
+
declare function Button({ disabled, href, type, className, children, render, ...rest }: ButtonProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
43
54
|
|
|
44
55
|
export { Button, type ButtonProps };
|
package/dist/button/index.js
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
retractActivationProps,
|
|
3
|
-
useButton
|
|
4
|
-
} from "../chunk-TNMGZWHA.js";
|
|
5
1
|
import {
|
|
6
2
|
useRender
|
|
7
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-OQTWWZ4B.js";
|
|
8
4
|
|
|
9
5
|
// src/button/Button.tsx
|
|
10
|
-
import { cloneElement } from "react";
|
|
11
6
|
function Button({
|
|
12
7
|
disabled = false,
|
|
13
|
-
|
|
8
|
+
href,
|
|
9
|
+
type = "button",
|
|
14
10
|
className,
|
|
15
11
|
children,
|
|
16
12
|
render,
|
|
17
13
|
...rest
|
|
18
14
|
}) {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
const linksOut = href !== void 0 && !disabled;
|
|
16
|
+
const rendersButton = render === void 0 ? !linksOut : render.type === "button";
|
|
17
|
+
if (process.env.NODE_ENV !== "production" && disabled && render !== void 0) {
|
|
18
|
+
const renderProps = render.props;
|
|
19
|
+
if (renderProps?.href !== void 0) {
|
|
20
|
+
console.error(
|
|
21
|
+
"Button: `disabled` cannot remove the `href` on a `render` element \u2014 its own props outrank the component. Pass the URL as `href` on Button instead."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const elementProps = rendersButton ? { type, disabled: disabled || void 0 } : { href: linksOut ? href : void 0, "aria-disabled": disabled || void 0 };
|
|
24
26
|
return useRender({
|
|
25
|
-
render
|
|
26
|
-
defaultTagName: "button",
|
|
27
|
-
props: {
|
|
28
|
-
|
|
27
|
+
render,
|
|
28
|
+
defaultTagName: linksOut ? "a" : "button",
|
|
29
|
+
props: {
|
|
30
|
+
...elementProps,
|
|
31
|
+
"data-disabled": disabled ? "" : void 0,
|
|
32
|
+
className,
|
|
33
|
+
children
|
|
34
|
+
},
|
|
35
|
+
consumerProps: rest
|
|
29
36
|
});
|
|
30
37
|
}
|
|
31
38
|
export {
|
|
@@ -36,18 +36,9 @@ function isSyntheticEvent(event) {
|
|
|
36
36
|
function makeEventPreventable(event) {
|
|
37
37
|
if (Object.prototype.hasOwnProperty.call(event, "preventComponentHandler")) return;
|
|
38
38
|
const target = event;
|
|
39
|
-
|
|
39
|
+
target.preventComponentHandler = () => {
|
|
40
40
|
target.componentHandlerPrevented = true;
|
|
41
41
|
};
|
|
42
|
-
target.preventComponentHandler = prevent;
|
|
43
|
-
const native = event.nativeEvent;
|
|
44
|
-
const stopImmediate = native?.stopImmediatePropagation?.bind(native);
|
|
45
|
-
if (native && stopImmediate) {
|
|
46
|
-
native.stopImmediatePropagation = () => {
|
|
47
|
-
stopImmediate();
|
|
48
|
-
prevent();
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
42
|
}
|
|
52
43
|
function isComponentHandlerPrevented(event) {
|
|
53
44
|
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
@@ -85,38 +76,15 @@ function mergeProps(...objects) {
|
|
|
85
76
|
return merged;
|
|
86
77
|
}
|
|
87
78
|
|
|
88
|
-
// src/core/stateAttributes.ts
|
|
89
|
-
function getStateAttributes(state, mapping) {
|
|
90
|
-
if (!mapping) return {};
|
|
91
|
-
const attributes = {};
|
|
92
|
-
for (const key of Object.keys(state)) {
|
|
93
|
-
const toAttributes = mapping[key];
|
|
94
|
-
if (!toAttributes) continue;
|
|
95
|
-
const result = toAttributes(state[key]);
|
|
96
|
-
if (result) Object.assign(attributes, result);
|
|
97
|
-
}
|
|
98
|
-
return attributes;
|
|
99
|
-
}
|
|
100
|
-
function booleanAttribute(whenTrue, whenFalse) {
|
|
101
|
-
return (value) => {
|
|
102
|
-
if (value) return { [whenTrue]: "" };
|
|
103
|
-
return whenFalse ? { [whenFalse]: "" } : null;
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
var disabledAttribute = booleanAttribute("data-disabled");
|
|
107
|
-
|
|
108
79
|
// src/core/useRender.ts
|
|
109
80
|
import { cloneElement, createElement, isValidElement } from "react";
|
|
110
81
|
function useRender({
|
|
111
82
|
render,
|
|
112
83
|
defaultTagName,
|
|
113
84
|
props,
|
|
114
|
-
consumerProps
|
|
115
|
-
state,
|
|
116
|
-
stateAttributes
|
|
85
|
+
consumerProps
|
|
117
86
|
}) {
|
|
118
|
-
const
|
|
119
|
-
const merged = mergeProps(attributes, props, consumerProps);
|
|
87
|
+
const merged = mergeProps(props, consumerProps);
|
|
120
88
|
if (isValidElement(render)) {
|
|
121
89
|
return cloneElement(render, mergeProps(merged, render.props));
|
|
122
90
|
}
|
|
@@ -126,8 +94,5 @@ function useRender({
|
|
|
126
94
|
export {
|
|
127
95
|
__export,
|
|
128
96
|
mergeProps,
|
|
129
|
-
getStateAttributes,
|
|
130
|
-
booleanAttribute,
|
|
131
|
-
disabledAttribute,
|
|
132
97
|
useRender
|
|
133
98
|
};
|