@apostel/bedrock 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Headless React primitives that let the browser do the layering, positioning and
4
4
  dismissal.
5
5
 
6
- Radix-shaped anatomy compound components, `asChild`, the part names you
6
+ Radix-shaped anatomy: compound components, `asChild`, the part names you
7
7
  already type. Underneath, the top layer, invoker commands and anchor positioning
8
8
  do the work that `Portal`, `Presence`, `DismissableLayer` and Floating UI used to
9
9
  do.
@@ -45,22 +45,27 @@ bundle never arrives.
45
45
 
46
46
  Measured with esbuild, minified and gzipped, React external:
47
47
 
48
+ <!-- sizes-figure -->
49
+
48
50
  | primitive | bedrock | Radix | |
49
51
  | --- | --- | --- | --- |
50
- | Checkbox | **0.70 kB** | 5.9 kB | 8.5× |
51
- | Slider | **0.74 kB** | 9.8 kB | 13× |
52
- | Select | **0.84 kB** | 31.5 kB | 37× |
52
+ | Checkbox | **0.70 kB** | 5.9 kB | 8.4× |
53
+ | Slider | **0.74 kB** | 9.8 kB | 13.2× |
54
+ | Select | **0.84 kB** | 31.5 kB | 37.5× |
53
55
  | Accordion | **1.61 kB** | 8.8 kB | 5.5× |
54
- | Dialog | **1.95 kB** | 13.7 kB | 7× |
55
- | Popover | **2.11 kB** | 24.2 kB | 11× |
56
+ | Dialog | **1.95 kB** | 13.7 kB | 7.0× |
57
+ | Popover | **2.11 kB** | 24.2 kB | 11.5× |
56
58
  | Tooltip | **2.55 kB** | 19.3 kB | 7.6× |
57
59
  | DropdownMenu | **3.55 kB** | 31.6 kB | 8.9× |
58
60
 
59
- The gap widens where the platform hands over a whole element and narrows where
60
- roving focus gets involved menus save least, and
61
- [should you switch?](./docs/should-you-switch.md#3-the-saving-is-uneven-and-menus-barely-save-anything)
62
- says so in detail. The larger win is behavioural: no z-index fights, no portal
63
- clipping, no repositioning on every scroll frame.
61
+ The ratio is largest where the platform hands over a whole element and smallest
62
+ where it hands over nothing but the layering. Menus are the case to budget from:
63
+ they save more kilobytes than anything but Select, and they still leave the
64
+ largest bedrock bundle in the library, because roving focus and typeahead have
65
+ no native equivalent and ship either way.
66
+ [Should you switch?](./docs/should-you-switch.md#3-the-saving-is-uneven)
67
+ breaks that down by group. The larger win is behavioural anyway: no z-index
68
+ fights, no portal clipping, no repositioning on every scroll frame.
64
69
 
65
70
  ## What it costs
66
71
 
@@ -77,26 +82,26 @@ The component still opens, closes, traps focus and is announced correctly. Below
77
82
  it, nothing opens at all.
78
83
 
79
84
  [Browser support](./docs/compat.md) has every feature, its minimum version in
80
- each engine, and what its absence does to your UI measured in the browser you
85
+ each engine, and what its absence does to your UI, measured in the browser you
81
86
  open it in.
82
87
 
83
88
  ## Where to start
84
89
 
85
- - **[Getting started](./docs/getting-started.md)** install, first dialog, the
86
- two roots.
87
- - **[Should you switch?](./docs/should-you-switch.md)** the case against
90
+ - **[Getting started](./docs/getting-started.md)**: install, a first component,
91
+ and the one rule that applies to every trigger.
92
+ - **[Should you switch?](./docs/should-you-switch.md)**: the case against
88
93
  adopting this. Read it first if you are evaluating.
89
- - **[Migrating from Radix](./docs/migration-from-radix.md)** what changes,
94
+ - **[Migrating from Radix](./docs/migration-from-radix.md)**: what changes,
90
95
  what breaks, and what to do about it.
91
- - **[shadcn registry](./docs/shadcn-registry.md)** shadcn's own components with
96
+ - **[shadcn registry](./docs/shadcn-registry.md)**: shadcn's own components with
92
97
  Radix swapped for bedrock, installable with `npx shadcn add`.
93
- - **[Agent skill](./skills/migrate-to-bedrock/SKILL.md)** does the mechanical
98
+ - **[Agent skill](./skills/migrate-to-bedrock/SKILL.md)**: does the mechanical
94
99
  migration and brings you the four decisions it cannot make for you.
95
100
 
96
101
  ## Status
97
102
 
98
- `0.1.0` on npm. All 29 primitives, 141 Playwright tests against real Chrome.
99
- What is missing is soak time and other engines not components.
103
+ `0.1.1` on npm. All 29 primitives, 200 Playwright tests against real Chrome.
104
+ What is missing is soak time and other engines, not components.
100
105
 
101
106
  [Contributing](./CONTRIBUTING.md) · [Releasing](./RELEASING.md) ·
102
107
  [Changelog](./CHANGELOG.md)
@@ -5,9 +5,10 @@
5
5
  * All of them are safe to call while server-rendering.
6
6
  */
7
7
  /**
8
- * `interestfor` — hover and focus intent without timers. Not standardised, and
9
- * not in Chrome stable as of 141, which is why Tooltip and HoverCard ship the
10
- * JavaScript path as their normal one rather than as a contingency.
8
+ * `interestfor` — hover and focus intent without timers. In Chrome since 142
9
+ * and in no other engine, and still not on a standards track, which is why
10
+ * Tooltip and HoverCard ship the JavaScript path as their normal one rather
11
+ * than as a contingency.
11
12
  */
12
13
  export declare function supportsInterestInvokers(): boolean;
13
14
  /** Anchor positioning. Without it, overlays land where the UA puts them. */
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,OAAO,CAM9C;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD"}
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,OAAO,CAM9C;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD"}
@@ -8,9 +8,10 @@ import { useSyncExternalStore } from 'react';
8
8
  const hasDom = typeof window !== 'undefined';
9
9
  const noopSubscribe = () => () => { };
10
10
  /**
11
- * `interestfor` — hover and focus intent without timers. Not standardised, and
12
- * not in Chrome stable as of 141, which is why Tooltip and HoverCard ship the
13
- * JavaScript path as their normal one rather than as a contingency.
11
+ * `interestfor` — hover and focus intent without timers. In Chrome since 142
12
+ * and in no other engine, and still not on a standards track, which is why
13
+ * Tooltip and HoverCard ship the JavaScript path as their normal one rather
14
+ * than as a contingency.
14
15
  */
15
16
  export function supportsInterestInvokers() {
16
17
  return hasDom && 'interestForElement' in HTMLButtonElement.prototype;
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAE5C,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,WAAW,CAAA;AAC5C,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAA;AAEpC;;;;GAIG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,MAAM,IAAI,oBAAoB,IAAI,iBAAiB,CAAC,SAAS,CAAA;AACtE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,yBAAyB;IACvC,OAAO,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;AACrD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEzB,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC7C,OAAO,CAAC,OAAO,GAAG,MAAM,CAAA;IACxB,OAAO,OAAO,CAAC,OAAO,KAAK,MAAM,CAAA;AACnC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,oBAAoB,CAAC,aAAa,EAAE,wBAAwB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;AACnF,CAAC"}
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAE5C,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,WAAW,CAAA;AAC5C,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAA;AAEpC;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,MAAM,IAAI,oBAAoB,IAAI,iBAAiB,CAAC,SAAS,CAAA;AACtE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,yBAAyB;IACvC,OAAO,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;AACrD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEzB,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC7C,OAAO,CAAC,OAAO,GAAG,MAAM,CAAA;IACxB,OAAO,OAAO,CAAC,OAAO,KAAK,MAAM,CAAA;AACnC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,oBAAoB,CAAC,aAAa,EAAE,wBAAwB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;AACnF,CAAC"}
@@ -6,8 +6,8 @@ export interface CollapsibleRootProps extends ComponentPropsWithRef<'details'>,
6
6
  onOpenChange?(open: boolean): void;
7
7
  }
8
8
  /**
9
- * Unlike Dialog, the root *is* the element: `<summary>` only works as a child
10
- * of `<details>`, so there is nothing to wire by id and no invoker involved.
9
+ * `<summary>` only works as a child of `<details>`, so the parts are bound by
10
+ * nesting: nothing to wire by id, no invoker involved.
11
11
  *
12
12
  * Which means `defaultOpen` is a plain attribute rather than an imperative call
13
13
  * on mount — the disclosure is open in the HTML before any script runs.
@@ -5,8 +5,8 @@ import { useOpenState } from '../open-state.js';
5
5
  import { Slot } from '../slot.js';
6
6
  import { CollapsibleContext } from './shared.js';
7
7
  /**
8
- * Unlike Dialog, the root *is* the element: `<summary>` only works as a child
9
- * of `<details>`, so there is nothing to wire by id and no invoker involved.
8
+ * `<summary>` only works as a child of `<details>`, so the parts are bound by
9
+ * nesting: nothing to wire by id, no invoker involved.
10
10
  *
11
11
  * Which means `defaultOpen` is a plain attribute rather than an imperative call
12
12
  * on mount — the disclosure is open in the HTML before any script runs.
@@ -5,17 +5,24 @@ export interface InterestOptions {
5
5
  hoverableContent: boolean;
6
6
  }
7
7
  /**
8
- * Hover and focus intent, in JavaScript, for as long as `interestfor` is not
9
- * something we can rely on.
8
+ * Hover, focus and press intent, in JavaScript, for as long as `interestfor` is
9
+ * not something we can rely on.
10
10
  *
11
- * This is the fallback the compat table calls "replaceable": when the attribute
12
- * ships, `useInterest` stops attaching anything and the same props become
11
+ * This is the fallback the compat table calls "replaceable": where the attribute
12
+ * is supported, `useInterest` stops opening anything and the same props become
13
13
  * declarative. Nothing above it changes — which is the entire reason
14
14
  * `delayDuration` is not called `interest-show-delay`.
15
15
  *
16
- * Deliberately not a general hover library: it does the four things the pattern
17
- * needs pointer in, pointer out, focus, blur and leaves dismissal to the
18
- * popover, which already handles Escape and light dismiss.
16
+ * One thing stays attached either way. A long press ends in a click, and a
17
+ * browser answering the press itself still lets that click through, so the
18
+ * gesture previews the link *and* follows it. Tracking the press costs four
19
+ * listeners and an early return; leaving it to the platform costs a navigation
20
+ * nobody asked for.
21
+ *
22
+ * Deliberately not a general hover library: it does the gestures the pattern
23
+ * needs — pointer in, pointer out, focus, blur, and the long press that stands
24
+ * in for all four on a touch screen — and leaves dismissal to the popover,
25
+ * which already handles Escape and light dismiss.
19
26
  *
20
27
  * Listeners are attached when a node registers, not from an effect that runs
21
28
  * each render. That distinction is load-bearing: opening the card is itself a
@@ -1 +1 @@
1
- {"version":3,"file":"interest.d.ts","sourceRoot":"","sources":["../src/interest.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,eAAe;4BA0D5E,WAAW,GAAG,IAAI;oCAclB,WAAW,GAAG,IAAI;EAiB5B"}
1
+ {"version":3,"file":"interest.d.ts","sourceRoot":"","sources":["../src/interest.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AA6CD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,eAAe;4BAoO5E,WAAW,GAAG,IAAI;oCAWlB,WAAW,GAAG,IAAI;EAwB5B"}
package/dist/interest.js CHANGED
@@ -1,17 +1,63 @@
1
1
  import { useCallback, useEffect, useRef } from 'react';
2
2
  import { supportsInterestInvokers } from './capabilities.js';
3
3
  /**
4
- * Hover and focus intent, in JavaScript, for as long as `interestfor` is not
5
- * something we can rely on.
4
+ * A press long enough to be a deliberate gesture rather than a tap. iOS and
5
+ * Android both use half a second for their own long-press, so it is the number
6
+ * a thumb already knows — and it is deliberately not `showDelay`, because a
7
+ * press is a different gesture from a pointer coming to rest, and a consumer
8
+ * tuning one has said nothing about the other.
9
+ */
10
+ const PRESS_DURATION = 500;
11
+ /** How far a finger may drift and still be pressing rather than scrolling. */
12
+ const PRESS_SLOP = 10;
13
+ /** Long enough to catch the click a lift produces, short enough to catch only that one. */
14
+ const CLICK_GRACE = 400;
15
+ /**
16
+ * Whether the focus is one a tooltip should answer.
17
+ *
18
+ * Tapping a control focuses it on Android, and a tooltip that appears on tap is
19
+ * a popover with extra steps. `:focus-visible` is the platform's own answer to
20
+ * "did this focus come from a pointer", so we ask it rather than tracking the
21
+ * last input type ourselves.
22
+ */
23
+ function focusIsVisible(node) {
24
+ try {
25
+ return node.matches(':focus-visible');
26
+ }
27
+ catch {
28
+ // An engine that does not know the selector throws. Showing on every focus
29
+ // is the older behaviour and the safer one: an unwanted tooltip beats a
30
+ // keyboard user who can never see one.
31
+ return true;
32
+ }
33
+ }
34
+ /**
35
+ * A touch pointer reports enter on touchdown and leave on lift, so the hover
36
+ * path would make every tap a half-open tooltip. Touch has its own gesture; the
37
+ * hover handlers are for the pointers that actually hover.
38
+ */
39
+ function hovering(event) {
40
+ return event.pointerType !== 'touch';
41
+ }
42
+ /**
43
+ * Hover, focus and press intent, in JavaScript, for as long as `interestfor` is
44
+ * not something we can rely on.
6
45
  *
7
- * This is the fallback the compat table calls "replaceable": when the attribute
8
- * ships, `useInterest` stops attaching anything and the same props become
46
+ * This is the fallback the compat table calls "replaceable": where the attribute
47
+ * is supported, `useInterest` stops opening anything and the same props become
9
48
  * declarative. Nothing above it changes — which is the entire reason
10
49
  * `delayDuration` is not called `interest-show-delay`.
11
50
  *
12
- * Deliberately not a general hover library: it does the four things the pattern
13
- * needs pointer in, pointer out, focus, blur and leaves dismissal to the
14
- * popover, which already handles Escape and light dismiss.
51
+ * One thing stays attached either way. A long press ends in a click, and a
52
+ * browser answering the press itself still lets that click through, so the
53
+ * gesture previews the link *and* follows it. Tracking the press costs four
54
+ * listeners and an early return; leaving it to the platform costs a navigation
55
+ * nobody asked for.
56
+ *
57
+ * Deliberately not a general hover library: it does the gestures the pattern
58
+ * needs — pointer in, pointer out, focus, blur, and the long press that stands
59
+ * in for all four on a touch screen — and leaves dismissal to the popover,
60
+ * which already handles Escape and light dismiss.
15
61
  *
16
62
  * Listeners are attached when a node registers, not from an effect that runs
17
63
  * each render. That distinction is load-bearing: opening the card is itself a
@@ -23,18 +69,27 @@ export function useInterest({ showDelay, hideDelay, hoverableContent }) {
23
69
  const triggerRef = useRef(null);
24
70
  const contentRef = useRef(null);
25
71
  const timer = useRef(0);
72
+ const pressTimer = useRef(0);
73
+ const clickTimer = useRef(0);
74
+ // Null between gestures. `held` flips once our own press has lasted long
75
+ // enough to have opened something, which is what separates a press from a
76
+ // tap; `openAtDown` is what tells a press that opened the panel from a tap on
77
+ // a trigger whose panel was already showing.
78
+ const press = useRef(null);
26
79
  // Read at call time, so changing a delay never means rebinding anything.
27
80
  const options = useRef({ showDelay, hideDelay, hoverableContent });
28
81
  options.current = { showDelay, hideDelay, hoverableContent };
29
82
  const clear = useCallback(() => window.clearTimeout(timer.current), []);
83
+ const isOpen = useCallback(() => contentRef.current?.matches(':popover-open') === true, []);
84
+ const reveal = useCallback(() => {
85
+ const content = contentRef.current;
86
+ if (content && !isOpen())
87
+ content.showPopover();
88
+ }, [isOpen]);
30
89
  const show = useCallback(() => {
31
90
  clear();
32
- timer.current = window.setTimeout(() => {
33
- const content = contentRef.current;
34
- if (content && !content.matches(':popover-open'))
35
- content.showPopover();
36
- }, options.current.showDelay);
37
- }, [clear]);
91
+ timer.current = window.setTimeout(reveal, options.current.showDelay);
92
+ }, [clear, reveal]);
38
93
  const hide = useCallback(() => {
39
94
  clear();
40
95
  timer.current = window.setTimeout(() => {
@@ -43,32 +98,144 @@ export function useInterest({ showDelay, hideDelay, hoverableContent }) {
43
98
  content.hidePopover();
44
99
  }, options.current.hideDelay);
45
100
  }, [clear]);
46
- const keepOpen = useCallback(() => {
47
- if (options.current.hoverableContent)
101
+ const enter = useCallback((event) => {
102
+ if (hovering(event))
103
+ show();
104
+ }, [show]);
105
+ const leave = useCallback((event) => {
106
+ if (hovering(event))
107
+ hide();
108
+ }, [hide]);
109
+ const focus = useCallback((event) => {
110
+ if (focusIsVisible(event.currentTarget))
111
+ show();
112
+ }, [show]);
113
+ const keepOpen = useCallback((event) => {
114
+ if (hovering(event) && options.current.hoverableContent)
48
115
  clear();
49
116
  }, [clear]);
50
- const leaveContent = useCallback(() => {
51
- if (options.current.hoverableContent)
117
+ const leaveContent = useCallback((event) => {
118
+ if (hovering(event) && options.current.hoverableContent)
52
119
  hide();
53
120
  }, [hide]);
121
+ const suppressClick = useCallback((event) => {
122
+ event.preventDefault();
123
+ event.stopPropagation();
124
+ }, []);
125
+ /**
126
+ * The lift of a press produces a click, and on an `<a>` that click is a
127
+ * navigation. A press asked to see more about the control, not to activate
128
+ * it, so exactly one click is swallowed — with a deadline, so a press that
129
+ * ends without one does not leave a trap for the next real click.
130
+ */
131
+ const swallowClick = useCallback((node) => {
132
+ node.addEventListener('click', suppressClick, { capture: true, once: true });
133
+ window.clearTimeout(clickTimer.current);
134
+ clickTimer.current = window.setTimeout(() => node.removeEventListener('click', suppressClick, true), CLICK_GRACE);
135
+ }, [suppressClick]);
136
+ /**
137
+ * A long press is how a touch screen asks to see something without
138
+ * activating it — the gesture iOS uses for its own link previews, and the one
139
+ * `interestfor` answers where the platform runs intent itself. The press is
140
+ * tracked either way; only the opening is ours.
141
+ *
142
+ * While ours runs, selection is off. Otherwise the press is a text selection
143
+ * on Android and a selection magnifier on iOS before it is ever ours. It is
144
+ * turned off for the gesture rather than for the life of the trigger, because
145
+ * a hover card trigger is usually a link in a paragraph and permanently
146
+ * unselectable text drops out of the selection around it.
147
+ */
148
+ const startPress = useCallback((event) => {
149
+ const pointer = event;
150
+ if (pointer.pointerType !== 'touch')
151
+ return;
152
+ const node = event.currentTarget;
153
+ window.clearTimeout(pressTimer.current);
154
+ press.current = { x: pointer.clientX, y: pointer.clientY, held: false, openAtDown: isOpen() };
155
+ if (supportsInterestInvokers())
156
+ return;
157
+ node.style.setProperty('user-select', 'none');
158
+ node.style.setProperty('-webkit-user-select', 'none');
159
+ pressTimer.current = window.setTimeout(() => {
160
+ if (!press.current)
161
+ return;
162
+ press.current.held = true;
163
+ reveal();
164
+ }, PRESS_DURATION);
165
+ }, [isOpen, reveal]);
166
+ const endPress = useCallback((node) => {
167
+ window.clearTimeout(pressTimer.current);
168
+ press.current = null;
169
+ node.style.removeProperty('user-select');
170
+ node.style.removeProperty('-webkit-user-select');
171
+ }, []);
172
+ const movePress = useCallback((event) => {
173
+ const state = press.current;
174
+ if (!state || state.held)
175
+ return;
176
+ const pointer = event;
177
+ const drifted = Math.abs(pointer.clientX - state.x) > PRESS_SLOP ||
178
+ Math.abs(pointer.clientY - state.y) > PRESS_SLOP;
179
+ // A finger that has moved that far is scrolling, and a page that opens
180
+ // tooltips while you scroll past them is unusable.
181
+ if (drifted)
182
+ endPress(event.currentTarget);
183
+ }, [endPress]);
184
+ const liftPress = useCallback((event) => {
185
+ const state = press.current;
186
+ const node = event.currentTarget;
187
+ if (!state)
188
+ return;
189
+ endPress(node);
190
+ // The lift is a pointerup outside every open popover, and the popover the
191
+ // press opened did not exist when the finger went down — so as far as
192
+ // light dismiss is concerned it was never part of this gesture, and it
193
+ // closes. Re-assert it in the same task the dismissal ran in: nothing is
194
+ // painted between the two, so the card the press opened simply stays.
195
+ if (state.held)
196
+ reveal();
197
+ // Whoever opened it, the lift still produces a click, and on an `<a>` that
198
+ // click is a navigation. A browser answering the hold itself lets it
199
+ // through, so a long press there previews *and* follows the link; only
200
+ // that second half is ours to stop. A panel that was already showing when
201
+ // the finger went down is not this gesture's, and the tap under it stands.
202
+ if (state.held || (!state.openAtDown && isOpen()))
203
+ swallowClick(node);
204
+ }, [endPress, isOpen, reveal, swallowClick]);
205
+ const cancelPress = useCallback((event) => endPress(event.currentTarget), [endPress]);
206
+ // Android offers its own long-press menu on the same gesture, and whichever
207
+ // of the two the user gets should not be a race. iOS is asked in CSS instead,
208
+ // by the `-webkit-touch-callout` the trigger carries.
209
+ const suppressCallout = useCallback((event) => {
210
+ if (press.current)
211
+ event.preventDefault();
212
+ }, []);
54
213
  const bindTrigger = useCallback((node, add) => {
55
214
  const method = add ? 'addEventListener' : 'removeEventListener';
56
- node[method]('pointerenter', show);
57
- node[method]('pointerleave', hide);
215
+ // Touch captures its pointer implicitly, so every one of these lands on
216
+ // the trigger for the whole gesture, wherever the finger has gone. They
217
+ // are bound even where the platform runs intent itself, because the click
218
+ // a press ends in is left to us there — see `liftPress`.
219
+ node[method]('pointerdown', startPress);
220
+ node[method]('pointermove', movePress);
221
+ node[method]('pointerup', liftPress);
222
+ node[method]('pointercancel', cancelPress);
223
+ if (supportsInterestInvokers())
224
+ return;
225
+ node[method]('pointerenter', enter);
226
+ node[method]('pointerleave', leave);
58
227
  // Focus, not focusin: the trigger itself is the control, and a tooltip
59
228
  // should not appear because something inside it was focused.
60
- node[method]('focus', show);
229
+ node[method]('focus', focus);
61
230
  node[method]('blur', hide);
62
- }, [show, hide]);
231
+ node[method]('contextmenu', suppressCallout);
232
+ }, [startPress, movePress, liftPress, cancelPress, enter, leave, focus, hide, suppressCallout]);
63
233
  const bindContent = useCallback((node, add) => {
64
234
  const method = add ? 'addEventListener' : 'removeEventListener';
65
235
  node[method]('pointerenter', keepOpen);
66
236
  node[method]('pointerleave', leaveContent);
67
237
  }, [keepOpen, leaveContent]);
68
238
  const registerTrigger = useCallback((node) => {
69
- // When the platform does the work, nothing is attached and no timer runs.
70
- if (supportsInterestInvokers())
71
- return;
72
239
  const previous = triggerRef.current;
73
240
  if (previous)
74
241
  bindTrigger(previous, false);
@@ -77,18 +244,22 @@ export function useInterest({ showDelay, hideDelay, hoverableContent }) {
77
244
  bindTrigger(node, true);
78
245
  }, [bindTrigger]);
79
246
  const registerInterestContent = useCallback((node) => {
80
- if (supportsInterestInvokers())
81
- return;
82
247
  const previous = contentRef.current;
83
248
  if (previous)
84
249
  bindContent(previous, false);
250
+ // Held either way: where the platform runs intent, the lift still has to
251
+ // ask whether this press is the reason the panel is showing.
85
252
  contentRef.current = node;
86
- if (node)
253
+ if (node && !supportsInterestInvokers())
87
254
  bindContent(node, true);
88
255
  }, [bindContent]);
89
256
  // Only unmount. A pending timer outliving the component would call
90
257
  // showPopover() on a detached node.
91
- useEffect(() => clear, [clear]);
258
+ useEffect(() => () => {
259
+ window.clearTimeout(timer.current);
260
+ window.clearTimeout(pressTimer.current);
261
+ window.clearTimeout(clickTimer.current);
262
+ }, []);
92
263
  return { registerTrigger, registerInterestContent };
93
264
  }
94
265
  //# sourceMappingURL=interest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"interest.js","sourceRoot":"","sources":["../src/interest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interest.js","sourceRoot":"","sources":["../src/interest.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,13 @@ export interface TooltipTriggerProps extends ComponentPropsWithRef<'button'>, As
11
11
  * The `interestfor` attribute is emitted only when the platform can act on it.
12
12
  * Otherwise the same node is handed to the JavaScript fallback, and nothing
13
13
  * about the markup a consumer writes changes either way.
14
+ *
15
+ * The callout suppression goes with the fallback rather than with the trigger:
16
+ * a long press is how a touch screen asks to see more, and on iOS the callout
17
+ * menu — the share sheet on a link, the copy bar on text — takes that gesture
18
+ * first. Where the platform runs intent itself it also owns that conflict, so
19
+ * we leave it alone. Selection is only turned off for the length of a press,
20
+ * in `interest.ts`, so a link in a paragraph still comes with the paragraph.
14
21
  */
15
22
  export declare function TooltipTrigger({ asChild, ref, style, ...props }: TooltipTriggerProps): import("react").JSX.Element;
16
23
  export interface TooltipContentProps extends ComponentPropsWithRef<'div'>, AsChildProps {
@@ -1 +1 @@
1
- {"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/tooltip/parts.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAe,MAAM,OAAO,CAAA;AAC/D,OAAO,EAAmB,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAIlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAI5C,MAAM,WAAW,mBAAoB,SAAQ,qBAAqB,CAAC,QAAQ,CAAC,EAAE,YAAY;CAAG;AAE7F;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,+BAiBpF;AAED,MAAM,WAAW,mBAAoB,SAAQ,qBAAqB,CAAC,KAAK,CAAC,EAAE,YAAY;IACrF,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,QAAQ,EACR,IAAY,EACZ,KAAgB,EAChB,UAAc,EACd,eAAsB,EACtB,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,mBAAmB,+BAoBrB"}
1
+ {"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/tooltip/parts.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAe,MAAM,OAAO,CAAA;AAC/D,OAAO,EAAmB,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAIlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAI5C,MAAM,WAAW,mBAAoB,SAAQ,qBAAqB,CAAC,QAAQ,CAAC,EAAE,YAAY;CAAG;AAE7F;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,+BAuBpF;AAED,MAAM,WAAW,mBAAoB,SAAQ,qBAAqB,CAAC,KAAK,CAAC,EAAE,YAAY;IACrF,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,QAAQ,EACR,IAAY,EACZ,KAAgB,EAChB,UAAc,EACd,eAAsB,EACtB,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,mBAAmB,+BAoBrB"}
@@ -13,11 +13,22 @@ import { useTooltipContext } from './shared.js';
13
13
  * The `interestfor` attribute is emitted only when the platform can act on it.
14
14
  * Otherwise the same node is handed to the JavaScript fallback, and nothing
15
15
  * about the markup a consumer writes changes either way.
16
+ *
17
+ * The callout suppression goes with the fallback rather than with the trigger:
18
+ * a long press is how a touch screen asks to see more, and on iOS the callout
19
+ * menu — the share sheet on a link, the copy bar on text — takes that gesture
20
+ * first. Where the platform runs intent itself it also owns that conflict, so
21
+ * we leave it alone. Selection is only turned off for the length of a press,
22
+ * in `interest.ts`, so a link in a paragraph still comes with the paragraph.
16
23
  */
17
24
  export function TooltipTrigger({ asChild, ref, style, ...props }) {
18
25
  const { id, anchor, native, registerTrigger } = useTooltipContext('Tooltip.Trigger');
19
26
  const Part = asChild ? Slot : 'button';
20
- return (_jsx(Part, { ...props, ...(native ? { interestfor: id } : {}), "aria-describedby": id, style: { anchorName: anchor, ...style }, ref: useComposedRefs(ref, registerTrigger, (node) => validateTrigger(node, 'interest', 'Tooltip.Trigger')), "data-bedrock-tooltip-trigger": "" }));
27
+ return (_jsx(Part, { ...props, ...(native ? { interestfor: id } : {}), "aria-describedby": id, style: {
28
+ anchorName: anchor,
29
+ ...(native ? null : { WebkitTouchCallout: 'none' }),
30
+ ...style,
31
+ }, ref: useComposedRefs(ref, registerTrigger, (node) => validateTrigger(node, 'interest', 'Tooltip.Trigger')), "data-bedrock-tooltip-trigger": "" }));
21
32
  }
22
33
  export function TooltipContent({ asChild, children, side = 'top', align = 'center', sideOffset = 6, avoidCollisions = true, style, ref, ...props }) {
23
34
  const { id, open, anchor, kind, role, registerContent } = useTooltipContext('Tooltip.Content');
@@ -1 +1 @@
1
- {"version":3,"file":"parts.js","sourceRoot":"","sources":["../../src/tooltip/parts.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAyB,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAE9B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAI5C;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,EAAuB;IACnF,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;IACpF,MAAM,IAAI,GAAgB,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAA;IAEnD,OAAO,CACL,KAAC,IAAI,OACC,KAAK,KACL,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,sBAErB,EAAE,EACpB,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,KAAK,EAAkB,EACvD,GAAG,EAAE,eAAe,CAAc,GAAG,EAAE,eAAe,EAAE,CAAC,IAAI,EAAE,EAAE,CAC/D,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,iBAAiB,CAAC,CACrD,kCAC4B,EAAE,GAC/B,CACH,CAAA;AACH,CAAC;AASD,MAAM,UAAU,cAAc,CAAC,EAC7B,OAAO,EACP,QAAQ,EACR,IAAI,GAAG,KAAK,EACZ,KAAK,GAAG,QAAQ,EAChB,UAAU,GAAG,CAAC,EACd,eAAe,GAAG,IAAI,EACtB,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACY;IACpB,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;IAC9F,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAA;IAClC,MAAM,IAAI,GAAgB,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;IAEhD,OAAO,CACL,KAAC,IAAI,OACC,KAAK,EACT,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,IAAI,EACb,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,EAAE,GAAG,eAAe,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,eAClF,IAAI,gBACH,KAAK,EACjB,GAAG,EAAE,eAAe,CAAc,GAAG,EAAE,eAAe,CAAC,0BAClC,EAAE,YAEtB,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,GAC/B,CACR,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"parts.js","sourceRoot":"","sources":["../../src/tooltip/parts.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAyB,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAE9B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAI5C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,EAAuB;IACnF,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;IACpF,MAAM,IAAI,GAAgB,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAA;IAEnD,OAAO,CACL,KAAC,IAAI,OACC,KAAK,KACL,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,sBAErB,EAAE,EACpB,KAAK,EACH;YACE,UAAU,EAAE,MAAM;YAClB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,MAAM,EAAE,CAAC;YACnD,GAAG,KAAK;SACO,EAEnB,GAAG,EAAE,eAAe,CAAc,GAAG,EAAE,eAAe,EAAE,CAAC,IAAI,EAAE,EAAE,CAC/D,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,iBAAiB,CAAC,CACrD,kCAC4B,EAAE,GAC/B,CACH,CAAA;AACH,CAAC;AASD,MAAM,UAAU,cAAc,CAAC,EAC7B,OAAO,EACP,QAAQ,EACR,IAAI,GAAG,KAAK,EACZ,KAAK,GAAG,QAAQ,EAChB,UAAU,GAAG,CAAC,EACd,eAAe,GAAG,IAAI,EACtB,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACY;IACpB,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;IAC9F,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAA;IAClC,MAAM,IAAI,GAAgB,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;IAEhD,OAAO,CACL,KAAC,IAAI,OACC,KAAK,EACT,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,IAAI,EACb,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,EAAE,GAAG,eAAe,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,eAClF,IAAI,gBACH,KAAK,EACjB,GAAG,EAAE,eAAe,CAAc,GAAG,EAAE,eAAe,CAAC,0BAClC,EAAE,YAEtB,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,GAC/B,CACR,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apostel/bedrock",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Headless React primitives built on native platform features",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -43,26 +43,28 @@
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsc -p tsconfig.build.json && node scripts/postbuild.mjs",
46
- "typecheck": "tsc --noEmit -p tsconfig.json",
47
- "lint": "oxlint",
48
- "lint:graph": "depcruise src --config .dependency-cruiser.cjs",
46
+ "changeset": "changeset",
47
+ "compat:build": "node scripts/build-compat.mjs",
48
+ "demos:build": "vite build --config vite.demos.config.ts",
49
+ "docs:baseline": "node scripts/build-baseline.mjs",
50
+ "docs:build": "node scripts/build-docs.mjs && npm run demos:build",
49
51
  "format": "oxfmt",
50
52
  "format:check": "oxfmt --check",
53
+ "lint": "oxlint",
54
+ "lint:graph": "depcruise src --config .dependency-cruiser.cjs",
55
+ "prepublishOnly": "npm run verify",
51
56
  "registry:build": "node scripts/build-registry.mjs",
57
+ "release": "changeset publish",
52
58
  "test": "playwright test",
53
59
  "test:install": "playwright install chromium",
60
+ "typecheck": "tsc --noEmit -p tsconfig.json",
54
61
  "verify": "npm run format:check && npm run lint && npm run typecheck && npm run lint:graph && npm run build && npm run test",
55
- "prepublishOnly": "npm run verify",
56
- "docs:build": "node scripts/build-docs.mjs && npm run demos:build",
57
- "changeset": "changeset",
58
- "version": "changeset version",
59
- "release": "changeset publish",
60
- "demos:build": "vite build --config vite.demos.config.ts",
61
- "docs:baseline": "node scripts/build-baseline.mjs"
62
+ "version": "changeset version"
62
63
  },
63
64
  "devDependencies": {
64
65
  "@axe-core/playwright": "^4.12.1",
65
66
  "@changesets/cli": "^3.0.1",
67
+ "@mdn/browser-compat-data": "^8.0.13",
66
68
  "@playwright/test": "^1.62.1",
67
69
  "@tailwindcss/vite": "^4.3.3",
68
70
  "@types/react": "^19.0.0",
@@ -77,7 +79,8 @@
77
79
  "tailwind-merge": "^3.6.0",
78
80
  "tailwindcss": "^4.3.3",
79
81
  "typescript": "^5.6.0",
80
- "vite": "^8.2.0"
82
+ "vite": "^8.2.0",
83
+ "web-features": "^3.36.0"
81
84
  },
82
85
  "peerDependencies": {
83
86
  "react": ">=19",