@rcarls/rc-fab 0.6.0 → 0.7.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/CHANGELOG.md +102 -0
- package/README.md +11 -9
- package/dist/custom-elements.json +4 -4
- package/dist/rc-fab-CoU-hPfL.js +54 -0
- package/dist/rc-fab-CoU-hPfL.js.map +1 -0
- package/dist/rc-fab-define.js +1 -1
- package/dist/rc-fab.js +1 -1
- package/dist/types/packages/rc-fab/src/rc-fab.d.ts +4 -3
- package/package.json +2 -2
- package/dist/rc-fab-CdJ-peVY.js +0 -212
- package/dist/rc-fab-CdJ-peVY.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
1
|
# @rcarls/rc-fab
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2d79bd0: Name the `position` corners for what they already do: each pins to a
|
|
8
|
+
logical block and inline edge, so they turn with the writing mode.
|
|
9
|
+
|
|
10
|
+
**Breaking:** the `position` values are renamed. Migrate:
|
|
11
|
+
|
|
12
|
+
- `bottom-end` → `block-end-inline-end` (the default)
|
|
13
|
+
- `bottom-start` → `block-end-inline-start`
|
|
14
|
+
- `top-end` → `block-start-inline-end`
|
|
15
|
+
- `top-start` → `block-start-inline-start`
|
|
16
|
+
|
|
17
|
+
- 6e548d9: Several components handle `prefers-reduced-motion: reduce` in their own
|
|
18
|
+
shadow styles, outside a theme's reach, and refine it the same way the theme
|
|
19
|
+
layer already does: spatial motion (`translate`, `scale`, `transform`,
|
|
20
|
+
`inline-size`, `block-size`) goes fully to zero, while effects motion (color,
|
|
21
|
+
opacity) shortens instead of disappearing.
|
|
22
|
+
|
|
23
|
+
`rc-app-bar` zeros its host's hide/show `translate` but shortens the
|
|
24
|
+
expanded title's fade-in. `rc-disclosure` keeps its panel's expand/collapse
|
|
25
|
+
zeroed, but its summary/content backstop for a theme-added transition now
|
|
26
|
+
shortens instead of zeroing it outright. `rc-list-item`'s row and
|
|
27
|
+
state-layer transition only color and opacity, so the whole sweep shortens.
|
|
28
|
+
`rc-fab-menu`'s popup mixes `scale` with opacity/overlay/display under one
|
|
29
|
+
shared duration; reduced motion now zeros the scale and shortens the rest.
|
|
30
|
+
`rc-switch`'s thumb mixes background-color with inline-size/block-size/
|
|
31
|
+
transform the same way; the icons and track, which each transition only one
|
|
32
|
+
kind of property, needed only the zero-or-shorten call. `rc-fab`'s
|
|
33
|
+
scroll-reveal is opacity/visibility only with no time-based duration to
|
|
34
|
+
shorten in its scroll-linked path, so reduced motion narrows the scroll
|
|
35
|
+
window instead of collapsing it to zero; its JS-transition fallback path
|
|
36
|
+
shortens the same fade rather than removing it. `rc-progress`'s determinate
|
|
37
|
+
fill transition, previously unguarded under reduced motion, is now zeroed;
|
|
38
|
+
its indeterminate loop, a continuous sweep with no meaningful shortened
|
|
39
|
+
form, stays fully stopped rather than shortened.
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 5c006de: Ship component CSS minified. Package builds now run the CSS in Lit `css` styles
|
|
44
|
+
and light-DOM structural styles through Lightning CSS, removing comments and
|
|
45
|
+
whitespace while keeping source maps that resolve to each template. Rendered
|
|
46
|
+
styles are unchanged.
|
|
47
|
+
|
|
48
|
+
`@rcarls/rc-common` adds `cssText`, an identity tag that marks a plain CSS
|
|
49
|
+
string for minification. `rc-chip`, `rc-disclosure`, and `rc-accordion` now
|
|
50
|
+
depend on `@rcarls/rc-common` for it.
|
|
51
|
+
|
|
52
|
+
- a6553ff: Batch layout sampling into a shared animation-frame read phase so mounting a
|
|
53
|
+
large component route no longer alternates forced layout reads with component
|
|
54
|
+
writes. Closed anchored popups now skip placement work until they open, while
|
|
55
|
+
scroller boundaries, carousel positioning, flow state, and opted-in initial
|
|
56
|
+
scroll observation settle before the next paint.
|
|
57
|
+
|
|
58
|
+
Add `RafScheduler.schedulePhased()`, live `AnchorController.disabled` getters,
|
|
59
|
+
and the `ScrollObserverController.initialEvaluation` timing option.
|
|
60
|
+
|
|
61
|
+
- 899bb5e: Make `rc-dialog` move and resize options reactive. Toggling `movable`,
|
|
62
|
+
changing `resize`, or changing any other `move-*` or `resize-*` option after
|
|
63
|
+
setup now rebuilds that behavior, so `resize="none"` turns resizing off on a
|
|
64
|
+
mounted `rc-dialog` or `rc-bottom-sheet` and a later value turns it back on.
|
|
65
|
+
Re-wiring the inner `<dialog>` no longer leaves stale move or resize
|
|
66
|
+
controllers attached. `DragController`, `ResizeController`, and
|
|
67
|
+
`DragGestureController` gain `dispose()`. Detaching a handle restores its
|
|
68
|
+
focusability and inline resize styles when they still have controller-written
|
|
69
|
+
values, while preserving later author edits. Active gestures cancel
|
|
70
|
+
and release pointer capture when their controller is removed. `rc-fab` uses
|
|
71
|
+
the new `ScrollObserverController.dispose()` for its optional scroll fallback.
|
|
72
|
+
- Updated dependencies [e3dfa59]
|
|
73
|
+
- Updated dependencies [50d5610]
|
|
74
|
+
- Updated dependencies [9ee769c]
|
|
75
|
+
- Updated dependencies [1179f24]
|
|
76
|
+
- Updated dependencies [595fe37]
|
|
77
|
+
- Updated dependencies [38cd00c]
|
|
78
|
+
- Updated dependencies [5c006de]
|
|
79
|
+
- Updated dependencies [a6553ff]
|
|
80
|
+
- Updated dependencies [2205478]
|
|
81
|
+
- Updated dependencies [4341311]
|
|
82
|
+
- Updated dependencies [899bb5e]
|
|
83
|
+
- Updated dependencies [25fcfe0]
|
|
84
|
+
- Updated dependencies [51f6403]
|
|
85
|
+
- Updated dependencies [9caca6b]
|
|
86
|
+
- Updated dependencies [b764a3c]
|
|
87
|
+
- Updated dependencies [54d9ead]
|
|
88
|
+
- Updated dependencies [b9175e9]
|
|
89
|
+
- Updated dependencies [c8b762b]
|
|
90
|
+
- Updated dependencies [55e1e7f]
|
|
91
|
+
- Updated dependencies [f1ca82d]
|
|
92
|
+
- Updated dependencies [b87f27b]
|
|
93
|
+
- Updated dependencies [32ebead]
|
|
94
|
+
- Updated dependencies [bfefedd]
|
|
95
|
+
- Updated dependencies [c130173]
|
|
96
|
+
- Updated dependencies [11680b6]
|
|
97
|
+
- Updated dependencies [223d36c]
|
|
98
|
+
- Updated dependencies [7b91a44]
|
|
99
|
+
- Updated dependencies [aa04ead]
|
|
100
|
+
- Updated dependencies [ac65b5b]
|
|
101
|
+
- Updated dependencies [afcfef8]
|
|
102
|
+
- Updated dependencies [11ef1ad]
|
|
103
|
+
- @rcarls/rc-common@0.7.0
|
|
104
|
+
|
|
3
105
|
## 0.6.0
|
|
4
106
|
|
|
5
107
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# `@rcarls/rc-fab`
|
|
2
2
|
|
|
3
|
-
Sticky floating action button modeled after the
|
|
3
|
+
Sticky floating action button modeled after the
|
|
4
|
+
[Material 3 Floating action button](https://m3.material.io/components/floating-action-button/overview),
|
|
5
|
+
wrapping a consumer-supplied button with scroll-aware visibility.
|
|
4
6
|
|
|
5
7
|
Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-fab](https://richardcarls.github.io/rc-webcomponents/components/rc-fab).
|
|
6
8
|
|
|
7
|
-
Use this for "back to top"
|
|
9
|
+
Use this for "back to top," sticky CTAs, chat launchers, and of course as FABs in your
|
|
8
10
|
Material Design website or PWA.
|
|
9
11
|
|
|
10
12
|
Place a native `<button>` as the direct child. The button's own accessible name (text
|
|
@@ -50,16 +52,16 @@ import '@rcarls/rc-fab/define';
|
|
|
50
52
|
|
|
51
53
|
## Key attributes and CSS custom properties
|
|
52
54
|
|
|
53
|
-
| Attribute / property | Description
|
|
54
|
-
| -------------------- |
|
|
55
|
-
| `position` | Viewport corner: `
|
|
56
|
-
| `scroll-reveal` | Reveal the FAB only after scrolling past `--rc-fab-scroll-threshold`
|
|
55
|
+
| Attribute / property | Description |
|
|
56
|
+
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| `position` | Viewport corner: `block-end-inline-end` (default), `block-end-inline-start`, `block-start-inline-end`, `block-start-inline-start` |
|
|
58
|
+
| `scroll-reveal` | Reveal the FAB only after scrolling past `--rc-fab-scroll-threshold` |
|
|
57
59
|
|
|
58
60
|
| CSS custom property | Default | Description |
|
|
59
61
|
| --------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
60
62
|
| `--rc-fab-position` | `fixed` | CSS position value. Use `absolute` for layout-relative placement |
|
|
61
63
|
| `--rc-fab-scroll-threshold` | `300px` | Scroll distance before the FAB appears (requires `scroll-reveal`) |
|
|
62
|
-
| `--rc-fab-scroll-timeline` | `scroll(root block)` | Override scroll target for the CSS animation path (
|
|
64
|
+
| `--rc-fab-scroll-timeline` | `scroll(root block)` | Override scroll target for the CSS animation path (for example, `scroll(nearest block)`) |
|
|
63
65
|
| `--rc-fab-radius` | _(native)_ | Border-radius; defers to the native button's radius until set. Use `9999px` for pill-shaped, `50%` for a circle, or `1rem` for Material rounded-square |
|
|
64
66
|
| `--rc-fab-size` | _(native)_ | Height and minimum width; defers to native button sizing until set (packaged themes set `3.5rem`) |
|
|
65
67
|
| `--rc-fab-bg` | _(native)_ | Button background color; defers to the native button's background until set |
|
|
@@ -68,12 +70,12 @@ import '@rcarls/rc-fab/define';
|
|
|
68
70
|
| `--rc-fab-inset-block` | `1.5rem` | Distance from the block-axis edge |
|
|
69
71
|
| `--rc-fab-inset-inline` | `1.5rem` | Distance from the inline-axis edge |
|
|
70
72
|
|
|
71
|
-
## Scroll-reveal
|
|
73
|
+
## Scroll-reveal behavior
|
|
72
74
|
|
|
73
75
|
`scroll-reveal` uses CSS scroll-driven animations (`animation-timeline: scroll()`).
|
|
74
76
|
|
|
75
77
|
In browsers that do not yet support this API, a passive scroll listener provides
|
|
76
|
-
equivalent
|
|
78
|
+
equivalent behavior.
|
|
77
79
|
|
|
78
80
|
The `[scroll-below-threshold]` attribute is toggled on the host and CSS transitions
|
|
79
81
|
handle the show/hide. The FAB is always visible in any remaining edge cases.
|
|
@@ -187,9 +187,9 @@
|
|
|
187
187
|
"kind": "field",
|
|
188
188
|
"name": "position",
|
|
189
189
|
"type": {
|
|
190
|
-
"text": "'
|
|
190
|
+
"text": "| 'block-end-inline-end'\n | 'block-end-inline-start'\n | 'block-start-inline-end'\n | 'block-start-inline-start'"
|
|
191
191
|
},
|
|
192
|
-
"default": "'
|
|
192
|
+
"default": "'block-end-inline-end'",
|
|
193
193
|
"description": "Viewport corner where the FAB is anchored. Uses logical inline/block directions.",
|
|
194
194
|
"attribute": "position",
|
|
195
195
|
"reflects": true
|
|
@@ -231,9 +231,9 @@
|
|
|
231
231
|
"description": "Viewport corner where the FAB is anchored. Uses logical inline/block directions.",
|
|
232
232
|
"name": "position",
|
|
233
233
|
"type": {
|
|
234
|
-
"text": "'
|
|
234
|
+
"text": "| 'block-end-inline-end'\n | 'block-end-inline-start'\n | 'block-start-inline-end'\n | 'block-start-inline-start'"
|
|
235
235
|
},
|
|
236
|
-
"default": "'
|
|
236
|
+
"default": "'block-end-inline-end'",
|
|
237
237
|
"fieldName": "position"
|
|
238
238
|
},
|
|
239
239
|
{
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { css as b, LitElement as v, html as d } from "lit";
|
|
2
|
+
import { property as n } from "lit/decorators.js";
|
|
3
|
+
import { NativeChildController as h, ScrollObserverController as f, findNearestScrollAncestor as p } from "@rcarls/rc-common";
|
|
4
|
+
const u = b`:host{position:var(--rc-fab-position,fixed);z-index:var(--rc-fab-z-index,10);display:inline-flex;inset-block-end:var(--rc-fab-inset-block,1.5rem);inset-inline-end:var(--rc-fab-inset-inline,1.5rem)}:host([hidden]){display:none}:host([position=block-end-inline-start]){inset-inline-start:var(--rc-fab-inset-inline,1.5rem);inset-inline-end:unset}:host([position=block-start-inline-end]){inset-block-start:var(--rc-fab-inset-block,1.5rem);inset-block-end:unset}:host([position=block-start-inline-start]){inset-block-start:var(--rc-fab-inset-block,1.5rem);inset-block-end:unset;inset-inline-start:var(--rc-fab-inset-inline,1.5rem);inset-inline-end:unset}::slotted(button){justify-content:center;align-items:center;gap:var(--rc-fab-gap);min-width:var(--rc-fab-size);height:var(--rc-fab-size);padding-block:var(--rc-fab-padding-block,revert);padding-inline:var(--rc-fab-padding-inline,revert);background:var(--rc-fab-bg,revert);color:var(--rc-fab-color,revert);border:var(--rc-fab-border,revert);border-radius:var(--rc-fab-radius,revert);box-shadow:var(--rc-fab-shadow,revert);font-family:var(--rc-fab-font-family,revert);font-size:var(--rc-fab-font-size,revert);font-weight:var(--rc-fab-font-weight,revert);letter-spacing:var(--rc-fab-letter-spacing,revert);white-space:nowrap;transition:var(--rc-fab-transition,revert);display:inline-flex}:host(:has(button:hover)) ::slotted(button){background:var(--rc-fab-bg-hover,revert);box-shadow:var(--rc-fab-shadow-hover,revert)}:host(:has(button:active)) ::slotted(button){box-shadow:var(--rc-fab-shadow-active,revert);transform:var(--rc-fab-active-transform,revert)}:host(:has(button:focus-visible)) ::slotted(button){outline:var(--rc-fab-focus-ring,revert);outline-offset:var(--rc-fab-focus-ring-offset,revert)}:host(:has(button:disabled)) ::slotted(button){opacity:var(--rc-fab-disabled-opacity,revert);pointer-events:none;box-shadow:var(--rc-fab-disabled-shadow,revert)}@keyframes rc-fab-scroll-reveal{0%{opacity:0;visibility:hidden}50%{visibility:visible}to{opacity:1;visibility:visible}}@supports (animation-timeline:scroll()){:host([scroll-reveal]){--rc-fab-scroll-timeline:scroll(root block);animation-name:rc-fab-scroll-reveal;animation-duration:1ms;animation-timing-function:linear;animation-fill-mode:both;animation-timeline:var(--rc-fab-scroll-timeline);animation-range:calc(var(--rc-fab-scroll-threshold,300px) - 100px) var(--rc-fab-scroll-threshold,300px)}:host([scroll-reveal]):has(button:focus-visible){opacity:1;visibility:visible;animation:none}@media (prefers-reduced-motion:reduce){:host([scroll-reveal]){animation-range:calc(var(--rc-fab-scroll-threshold,300px) - 8px) var(--rc-fab-scroll-threshold,300px)}}}@supports not (animation-timeline:scroll()){:host([scroll-reveal]){transition:opacity .2s linear,visibility linear}:host([scroll-reveal][scroll-below-threshold]){opacity:0;visibility:hidden;transition:opacity .2s linear,visibility 0s linear .2s}:host([scroll-reveal][scroll-below-threshold]):has(button:focus-visible){opacity:1;visibility:visible;transition:none}@media (prefers-reduced-motion:reduce){:host([scroll-reveal]){transition:opacity 50ms linear,visibility linear}:host([scroll-reveal][scroll-below-threshold]){transition:opacity 50ms linear,visibility 0s linear 50ms}}}`;
|
|
5
|
+
var m = Object.defineProperty, c = (l, r, e, y) => {
|
|
6
|
+
for (var t = void 0, o = l.length - 1, a; o >= 0; o--)
|
|
7
|
+
(a = l[o]) && (t = a(r, e, t) || t);
|
|
8
|
+
return t && m(r, e, t), t;
|
|
9
|
+
};
|
|
10
|
+
const s = class s extends v {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(), this.position = "block-end-inline-end", this.scrollReveal = !1, new h(this, {
|
|
13
|
+
selector: ":scope > button",
|
|
14
|
+
observe: !0,
|
|
15
|
+
onMissing: () => {
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
updated(r) {
|
|
20
|
+
super.updated(r), (r.has("scrollReveal") || this.scrollReveal && !this._scrollCtrl) && this._syncScrollFallback();
|
|
21
|
+
}
|
|
22
|
+
_syncScrollFallback() {
|
|
23
|
+
if (this._scrollCtrl && (this._scrollCtrl.dispose(), this._scrollCtrl = void 0, this.removeAttribute("scroll-below-threshold")), !this.scrollReveal || CSS.supports("animation-timeline: scroll()"))
|
|
24
|
+
return;
|
|
25
|
+
const r = this._getThreshold();
|
|
26
|
+
this._scrollCtrl = new f(this, {
|
|
27
|
+
target: () => p(this),
|
|
28
|
+
threshold: r,
|
|
29
|
+
initialEvaluation: "frame",
|
|
30
|
+
onScroll: (e) => {
|
|
31
|
+
this.toggleAttribute("scroll-below-threshold", e < r);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
_getThreshold() {
|
|
36
|
+
const r = getComputedStyle(this).getPropertyValue("--rc-fab-scroll-threshold").trim(), e = parseFloat(r);
|
|
37
|
+
return Number.isFinite(e) ? e : 300;
|
|
38
|
+
}
|
|
39
|
+
render() {
|
|
40
|
+
return d`<slot></slot>`;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
s.styles = u;
|
|
44
|
+
let i = s;
|
|
45
|
+
c([
|
|
46
|
+
n({ type: String, reflect: !0 })
|
|
47
|
+
], i.prototype, "position");
|
|
48
|
+
c([
|
|
49
|
+
n({ type: Boolean, attribute: "scroll-reveal", reflect: !0 })
|
|
50
|
+
], i.prototype, "scrollReveal");
|
|
51
|
+
export {
|
|
52
|
+
i as R
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=rc-fab-CoU-hPfL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-fab-CoU-hPfL.js","sources":["../src/rc-fab.styles.ts","../src/rc-fab.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const fabStyles = css`\n :host {\n position: var(--rc-fab-position, fixed);\n inset-block-end: var(--rc-fab-inset-block, 1.5rem);\n inset-inline-end: var(--rc-fab-inset-inline, 1.5rem);\n z-index: var(--rc-fab-z-index, 10);\n display: inline-flex;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n :host([position='block-end-inline-start']) {\n inset-inline-end: unset;\n inset-inline-start: var(--rc-fab-inset-inline, 1.5rem);\n }\n\n :host([position='block-start-inline-end']) {\n inset-block-end: unset;\n inset-block-start: var(--rc-fab-inset-block, 1.5rem);\n }\n\n :host([position='block-start-inline-start']) {\n inset-block-end: unset;\n inset-block-start: var(--rc-fab-inset-block, 1.5rem);\n inset-inline-end: unset;\n inset-inline-start: var(--rc-fab-inset-inline, 1.5rem);\n }\n\n ::slotted(button) {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--rc-fab-gap);\n\n min-width: var(--rc-fab-size);\n height: var(--rc-fab-size);\n padding-block: var(--rc-fab-padding-block, revert);\n padding-inline: var(--rc-fab-padding-inline, revert);\n\n background: var(--rc-fab-bg, revert);\n color: var(--rc-fab-color, revert);\n\n border: var(--rc-fab-border, revert);\n border-radius: var(--rc-fab-radius, revert);\n box-shadow: var(--rc-fab-shadow, revert);\n\n font-family: var(--rc-fab-font-family, revert);\n font-size: var(--rc-fab-font-size, revert);\n font-weight: var(--rc-fab-font-weight, revert);\n letter-spacing: var(--rc-fab-letter-spacing, revert);\n white-space: nowrap;\n\n transition: var(--rc-fab-transition, revert);\n }\n\n :host(:has(button:hover)) ::slotted(button) {\n background: var(--rc-fab-bg-hover, revert);\n box-shadow: var(--rc-fab-shadow-hover, revert);\n }\n\n :host(:has(button:active)) ::slotted(button) {\n box-shadow: var(--rc-fab-shadow-active, revert);\n transform: var(--rc-fab-active-transform, revert);\n }\n\n :host(:has(button:focus-visible)) ::slotted(button) {\n outline: var(--rc-fab-focus-ring, revert);\n outline-offset: var(--rc-fab-focus-ring-offset, revert);\n }\n\n :host(:has(button:disabled)) ::slotted(button) {\n opacity: var(--rc-fab-disabled-opacity, revert);\n pointer-events: none;\n box-shadow: var(--rc-fab-disabled-shadow, revert);\n }\n\n @keyframes rc-fab-scroll-reveal {\n from {\n opacity: 0;\n visibility: hidden;\n }\n 50% {\n visibility: visible;\n }\n to {\n opacity: 1;\n visibility: visible;\n }\n }\n\n @supports (animation-timeline: scroll()) {\n :host([scroll-reveal]) {\n /*\n * Override for embedded/demo contexts where root does not scroll:\n * style=\"--rc-fab-scroll-timeline: scroll(nearest block)\"\n */\n --rc-fab-scroll-timeline: scroll(root block);\n\n animation-name: rc-fab-scroll-reveal;\n animation-duration: 1ms; /* required; scroll position drives progress, not time */\n animation-timing-function: linear;\n animation-fill-mode: both;\n animation-timeline: var(--rc-fab-scroll-timeline);\n animation-range: calc(var(--rc-fab-scroll-threshold, 300px) - 100px)\n var(--rc-fab-scroll-threshold, 300px);\n }\n\n /* Keyboard escape hatch: always visible when the button has focus */\n :host([scroll-reveal]):has(button:focus-visible) {\n animation: none;\n opacity: 1;\n visibility: visible;\n }\n\n /*\n * Effects motion (opacity/visibility, no translate or scale), so the\n * range narrows to a brief scroll window instead of collapsing to zero\n * width: a scroll-linked timeline has no duration to shorten, so a\n * narrow range is this animation's equivalent of a shortened duration.\n */\n @media (prefers-reduced-motion: reduce) {\n :host([scroll-reveal]) {\n animation-range: calc(var(--rc-fab-scroll-threshold, 300px) - 8px)\n var(--rc-fab-scroll-threshold, 300px);\n }\n }\n }\n\n /*\n * JS fallback for browsers without scroll-driven animation support.\n * [scroll-below-threshold] is toggled by ScrollObserverController;\n * transitions replicate the CSS animation's show/hide behavior.\n */\n @supports not (animation-timeline: scroll()) {\n /* visible state: visibility snaps immediately, opacity fades in */\n :host([scroll-reveal]) {\n transition:\n opacity 200ms linear,\n visibility 0s linear;\n }\n\n /* hidden state: opacity fades first, visibility snaps off after delay */\n :host([scroll-reveal][scroll-below-threshold]) {\n opacity: 0;\n visibility: hidden;\n transition:\n opacity 200ms linear,\n visibility 0s linear 200ms;\n }\n\n /* Keyboard escape hatch */\n :host([scroll-reveal][scroll-below-threshold]):has(button:focus-visible) {\n opacity: 1;\n visibility: visible;\n transition: none;\n }\n\n /* Effects motion (opacity/visibility): shortened rather than removed. */\n @media (prefers-reduced-motion: reduce) {\n :host([scroll-reveal]) {\n transition:\n opacity 50ms linear,\n visibility 0s linear;\n }\n\n :host([scroll-reveal][scroll-below-threshold]) {\n transition:\n opacity 50ms linear,\n visibility 0s linear 50ms;\n }\n }\n }\n`;\n\nexport default fabStyles;\n","import { LitElement, html } from 'lit';\nimport type { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport {\n NativeChildController,\n ScrollObserverController,\n findNearestScrollAncestor,\n warnMissingDirectChild,\n} from '@rcarls/rc-common';\n\nimport fabStyles from './rc-fab.styles.js';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-fab': RCFab;\n }\n}\n\n/**\n * Sticky floating action button modeled after the Material 3 Floating action button,\n * wrapping a consumer-supplied button with scroll-aware visibility.\n *\n * Adapted from the Material Design FAB component, use this for \"back to top\",\n * sticky CTAs, chat launchers, and of course as FABs in your Material Design PWA.\n *\n * Place a native `<button>` as the direct child. The button's own accessible\n * name (text content or `aria-label`) becomes the FAB's accessible name.\n *\n * Icons go inside the button alongside or instead of visible text.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-fab rc-fab docs}\n * @see {@link https://m3.material.io/components/floating-action-button/overview Material 3 Floating action button}\n *\n * @example Back to top (scroll-triggered)\n * ```html\n * <rc-fab scroll-reveal>\n * <button type=\"button\" aria-label=\"Back to top\" onclick=\"scrollTo({top:0,behavior:'smooth'})\">\n * <span aria-hidden=\"true\">↑</span>\n * </button>\n * </rc-fab>\n * ```\n *\n * @example Icon-only FAB (always visible)\n * ```html\n * <rc-fab>\n * <button type=\"button\" aria-label=\"Create\">\n * <span class=\"material-symbols-outlined\" aria-hidden=\"true\">add</span>\n * </button>\n * </rc-fab>\n * ```\n *\n * @example Extended FAB (icon + visible label)\n * ```html\n * <rc-fab>\n * <button type=\"button\">\n * <span class=\"material-symbols-outlined\" aria-hidden=\"true\">edit</span>\n * Compose\n * </button>\n * </rc-fab>\n * ```\n *\n * @slot default - The native `<button>` element. The button's own accessible\n * name (text content or `aria-label`) serves as the FAB's accessible name.\n *\n * @attr position - Logical viewport corner where the FAB is anchored: `block-end-inline-end`\n * (default), `block-end-inline-start`, `block-start-inline-end`, or `block-start-inline-start`.\n * @attr scroll-reveal - Reveal the FAB only after the page scrolls past\n * `--rc-fab-scroll-threshold`.\n * @attr [scroll-below-threshold] - Present when `scroll-reveal` is active and the page hasn't\n * scrolled past `--rc-fab-scroll-threshold` yet. Only used by the JS scroll-driven-animation\n * fallback; unused when the browser supports `animation-timeline: scroll()`.\n *\n * @cssprop [--rc-fab-position=fixed] - CSS position value. Override to `absolute` for layout-relative placement or `sticky` for scroll-snapping.\n * @cssprop [--rc-fab-inset-block=1.5rem] - Distance from the block-axis edge.\n * @cssprop [--rc-fab-inset-inline=1.5rem] - Distance from the inline-axis edge.\n * @cssprop [--rc-fab-z-index=10] - Stacking order.\n * @cssprop [--rc-fab-gap] - Gap between icon and label text. Unset by default; packaged themes\n * such as `rc-theme-material` set `0.5rem`.\n * @cssprop [--rc-fab-size] - Minimum inline size and block size of the button. Unset by\n * default (native button sizing applies); packaged themes such as `rc-theme-material` set\n * `3.5rem`.\n * @cssprop [--rc-fab-padding-block] - Button block-axis padding (defers to native button\n * padding when unset).\n * @cssprop [--rc-fab-padding-inline] - Button inline-axis padding (defers to native button\n * padding when unset).\n * @cssprop [--rc-fab-bg] - Button background (defers to native button background when unset).\n * @cssprop [--rc-fab-color] - Button foreground color (defers to native button color when\n * unset).\n * @cssprop [--rc-fab-border] - Button border (defers to native button border when unset).\n * @cssprop [--rc-fab-radius] - Button border-radius (defers to native button radius when\n * unset). Override to `9999px` for pill-shaped, `50%` for a circle (icon-only), `1rem` for\n * Material rounded-square, etc.\n * @cssprop [--rc-fab-shadow] - Elevation shadow (defers to native button shadow when unset).\n * @cssprop [--rc-fab-font-family] - Font family for label text (defers to native button font\n * when unset).\n * @cssprop [--rc-fab-font-size] - Font size for label text (defers to native button font when\n * unset).\n * @cssprop [--rc-fab-font-weight] - Font weight for label text (defers to native button font\n * when unset).\n * @cssprop [--rc-fab-letter-spacing] - Letter spacing for label text (defers to native button\n * styling when unset).\n * @cssprop [--rc-fab-transition] - Transition shorthand for hover/active state changes (defers\n * to native button transition when unset).\n * @cssprop [--rc-fab-bg-hover] - Hover background (defers to native `:hover` styling when\n * unset).\n * @cssprop [--rc-fab-shadow-hover] - Hover shadow (defers to native `:hover` styling when\n * unset).\n * @cssprop [--rc-fab-shadow-active] - Pressed shadow (defers to native `:active` styling when\n * unset).\n * @cssprop [--rc-fab-active-transform] - Transform applied while pressed, e.g. `scale(0.96)`\n * (defers to native `:active` styling when unset).\n * @cssprop [--rc-fab-focus-ring] - Focus ring style (defers to native `:focus-visible` styling\n * when unset).\n * @cssprop [--rc-fab-focus-ring-offset] - Focus ring offset (defers to native `:focus-visible`\n * styling when unset).\n * @cssprop [--rc-fab-disabled-opacity] - Opacity applied when the button is disabled (defers to\n * native `:disabled` styling when unset).\n * @cssprop [--rc-fab-disabled-shadow] - Shadow applied when the button is disabled (defers to\n * native `:disabled` styling when unset).\n * @cssprop [--rc-fab-scroll-threshold=300px] - Scroll distance at which the FAB becomes fully visible. Requires the `scroll-reveal` attribute. The JS fallback reads this value once on connect; px units only.\n * @cssprop [--rc-fab-scroll-timeline=scroll(root block)] - The `animation-timeline` value used for scroll-reveal. Override to target a different scroller, e.g. `scroll(nearest block)` for embedded contexts. CSS path only; the JS fallback discovers the nearest scrollable ancestor automatically.\n */\nexport class RCFab extends LitElement {\n static override styles = fabStyles;\n\n private _scrollCtrl?: ScrollObserverController;\n\n /** Viewport corner where the FAB is anchored. Uses logical inline/block directions. */\n @property({ type: String, reflect: true })\n position:\n | 'block-end-inline-end'\n | 'block-end-inline-start'\n | 'block-start-inline-end'\n | 'block-start-inline-start' = 'block-end-inline-end';\n\n /** Reveal the FAB only after the page scrolls past `--rc-fab-scroll-threshold` (default 300 px). Uses CSS scroll-driven animations; falls back to a passive scroll listener in unsupported browsers. */\n @property({ type: Boolean, attribute: 'scroll-reveal', reflect: true })\n scrollReveal = false;\n\n constructor() {\n super();\n\n new NativeChildController<HTMLButtonElement>(this, {\n selector: ':scope > button',\n observe: true,\n onMissing: () => {\n if (import.meta.env.DEV) {\n warnMissingDirectChild(this, {\n selector: ':scope > button',\n message:\n '[rc-fab] No direct child <button> found. Place a native <button> inside <rc-fab>.',\n });\n }\n },\n });\n }\n\n protected override updated(changed: PropertyValues): void {\n super.updated(changed);\n\n if (changed.has('scrollReveal') || (this.scrollReveal && !this._scrollCtrl)) {\n this._syncScrollFallback();\n }\n }\n\n private _syncScrollFallback(): void {\n if (this._scrollCtrl) {\n this._scrollCtrl.dispose();\n this._scrollCtrl = undefined;\n\n this.removeAttribute('scroll-below-threshold');\n }\n\n if (!this.scrollReveal || CSS.supports('animation-timeline: scroll()')) {\n return;\n }\n\n const threshold = this._getThreshold();\n\n this._scrollCtrl = new ScrollObserverController(this, {\n target: () => findNearestScrollAncestor(this),\n threshold,\n initialEvaluation: 'frame',\n onScroll: (scrollTop) => {\n this.toggleAttribute('scroll-below-threshold', scrollTop < threshold);\n },\n });\n }\n\n private _getThreshold(): number {\n const raw = getComputedStyle(this).getPropertyValue('--rc-fab-scroll-threshold').trim();\n const n = parseFloat(raw);\n\n return Number.isFinite(n) ? n : 300;\n }\n\n protected override render() {\n return html`<slot></slot>`;\n }\n}\n\nexport default RCFab;\n"],"names":["fabStyles","css","_RCFab","LitElement","NativeChildController","changed","threshold","ScrollObserverController","findNearestScrollAncestor","scrollTop","raw","n","html","RCFab","__decorateClass","property"],"mappings":";;;AAEO,MAAMA,IAAYC;;;;;;ACyHlB,MAAMC,IAAN,MAAMA,UAAcC,EAAW;AAAA,EAiBpC,cAAc;AACZ,UAAA,GAXF,KAAA,WAIiC,wBAIjC,KAAA,eAAe,IAKb,IAAIC,EAAyC,MAAM;AAAA,MACjD,UAAU;AAAA,MACV,SAAS;AAAA,MACT,WAAW,MAAM;AAAA,MAQjB;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEmB,QAAQC,GAA+B;AACxD,UAAM,QAAQA,CAAO,IAEjBA,EAAQ,IAAI,cAAc,KAAM,KAAK,gBAAgB,CAAC,KAAK,gBAC7D,KAAK,oBAAA;AAAA,EAET;AAAA,EAEQ,sBAA4B;AAQlC,QAPI,KAAK,gBACP,KAAK,YAAY,QAAA,GACjB,KAAK,cAAc,QAEnB,KAAK,gBAAgB,wBAAwB,IAG3C,CAAC,KAAK,gBAAgB,IAAI,SAAS,8BAA8B;AACnE;AAGF,UAAMC,IAAY,KAAK,cAAA;AAEvB,SAAK,cAAc,IAAIC,EAAyB,MAAM;AAAA,MACpD,QAAQ,MAAMC,EAA0B,IAAI;AAAA,MAC5C,WAAAF;AAAA,MACA,mBAAmB;AAAA,MACnB,UAAU,CAACG,MAAc;AACvB,aAAK,gBAAgB,0BAA0BA,IAAYH,CAAS;AAAA,MACtE;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEQ,gBAAwB;AAC9B,UAAMI,IAAM,iBAAiB,IAAI,EAAE,iBAAiB,2BAA2B,EAAE,KAAA,GAC3EC,IAAI,WAAWD,CAAG;AAExB,WAAO,OAAO,SAASC,CAAC,IAAIA,IAAI;AAAA,EAClC;AAAA,EAEmB,SAAS;AAC1B,WAAOC;AAAA,EACT;AACF;AA5EEV,EAAgB,SAASF;AADpB,IAAMa,IAANX;AAOLY,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GAN9BF,EAOX,WAAA,UAAA;AAQAC,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,WAAW,iBAAiB,SAAS,IAAM;AAAA,GAd3DF,EAeX,WAAA,cAAA;"}
|
package/dist/rc-fab-define.js
CHANGED
package/dist/rc-fab.js
CHANGED
|
@@ -50,7 +50,8 @@ declare global {
|
|
|
50
50
|
* @slot default - The native `<button>` element. The button's own accessible
|
|
51
51
|
* name (text content or `aria-label`) serves as the FAB's accessible name.
|
|
52
52
|
*
|
|
53
|
-
* @attr position -
|
|
53
|
+
* @attr position - Logical viewport corner where the FAB is anchored: `block-end-inline-end`
|
|
54
|
+
* (default), `block-end-inline-start`, `block-start-inline-end`, or `block-start-inline-start`.
|
|
54
55
|
* @attr scroll-reveal - Reveal the FAB only after the page scrolls past
|
|
55
56
|
* `--rc-fab-scroll-threshold`.
|
|
56
57
|
* @attr [scroll-below-threshold] - Present when `scroll-reveal` is active and the page hasn't
|
|
@@ -111,13 +112,13 @@ export declare class RCFab extends LitElement {
|
|
|
111
112
|
static styles: import('lit').CSSResult;
|
|
112
113
|
private _scrollCtrl?;
|
|
113
114
|
/** Viewport corner where the FAB is anchored. Uses logical inline/block directions. */
|
|
114
|
-
position: '
|
|
115
|
+
position: 'block-end-inline-end' | 'block-end-inline-start' | 'block-start-inline-end' | 'block-start-inline-start';
|
|
115
116
|
/** Reveal the FAB only after the page scrolls past `--rc-fab-scroll-threshold` (default 300 px). Uses CSS scroll-driven animations; falls back to a passive scroll listener in unsupported browsers. */
|
|
116
117
|
scrollReveal: boolean;
|
|
117
118
|
constructor();
|
|
118
119
|
protected updated(changed: PropertyValues): void;
|
|
119
120
|
private _syncScrollFallback;
|
|
120
121
|
private _getThreshold;
|
|
121
|
-
protected render(): import('lit').TemplateResult<1>;
|
|
122
|
+
protected render(): import('lit-html').TemplateResult<1>;
|
|
122
123
|
}
|
|
123
124
|
export default RCFab;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.7.0",
|
|
7
7
|
"description": "Sticky floating action button modeled after the Material 3 Floating action button, wrapping a consumer-supplied button with scroll-aware visibility.",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"test:browser:firefox": "vitest --run --project=firefox"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@rcarls/rc-common": "0.
|
|
48
|
+
"@rcarls/rc-common": "0.7.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@custom-elements-manifest/analyzer": "0.11.0",
|
package/dist/rc-fab-CdJ-peVY.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { css as b, LitElement as v, html as d } from "lit";
|
|
2
|
-
import { property as n } from "lit/decorators.js";
|
|
3
|
-
import { NativeChildController as h, ScrollObserverController as f, findNearestScrollAncestor as p } from "@rcarls/rc-common";
|
|
4
|
-
const m = b`
|
|
5
|
-
:host {
|
|
6
|
-
position: var(--rc-fab-position, fixed);
|
|
7
|
-
inset-block-end: var(--rc-fab-inset-block, 1.5rem);
|
|
8
|
-
inset-inline-end: var(--rc-fab-inset-inline, 1.5rem);
|
|
9
|
-
z-index: var(--rc-fab-z-index, 10);
|
|
10
|
-
display: inline-flex;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
:host([hidden]) {
|
|
14
|
-
display: none;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
:host([position='bottom-start']) {
|
|
18
|
-
inset-inline-end: unset;
|
|
19
|
-
inset-inline-start: var(--rc-fab-inset-inline, 1.5rem);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
:host([position='top-end']) {
|
|
23
|
-
inset-block-end: unset;
|
|
24
|
-
inset-block-start: var(--rc-fab-inset-block, 1.5rem);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
:host([position='top-start']) {
|
|
28
|
-
inset-block-end: unset;
|
|
29
|
-
inset-block-start: var(--rc-fab-inset-block, 1.5rem);
|
|
30
|
-
inset-inline-end: unset;
|
|
31
|
-
inset-inline-start: var(--rc-fab-inset-inline, 1.5rem);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
::slotted(button) {
|
|
35
|
-
display: inline-flex;
|
|
36
|
-
align-items: center;
|
|
37
|
-
justify-content: center;
|
|
38
|
-
gap: var(--rc-fab-gap);
|
|
39
|
-
|
|
40
|
-
min-width: var(--rc-fab-size);
|
|
41
|
-
height: var(--rc-fab-size);
|
|
42
|
-
padding-block: var(--rc-fab-padding-block, revert);
|
|
43
|
-
padding-inline: var(--rc-fab-padding-inline, revert);
|
|
44
|
-
|
|
45
|
-
background: var(--rc-fab-bg, revert);
|
|
46
|
-
color: var(--rc-fab-color, revert);
|
|
47
|
-
|
|
48
|
-
border: var(--rc-fab-border, revert);
|
|
49
|
-
border-radius: var(--rc-fab-radius, revert);
|
|
50
|
-
box-shadow: var(--rc-fab-shadow, revert);
|
|
51
|
-
|
|
52
|
-
font-family: var(--rc-fab-font-family, revert);
|
|
53
|
-
font-size: var(--rc-fab-font-size, revert);
|
|
54
|
-
font-weight: var(--rc-fab-font-weight, revert);
|
|
55
|
-
letter-spacing: var(--rc-fab-letter-spacing, revert);
|
|
56
|
-
white-space: nowrap;
|
|
57
|
-
|
|
58
|
-
transition: var(--rc-fab-transition, revert);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
:host(:has(button:hover)) ::slotted(button) {
|
|
62
|
-
background: var(--rc-fab-bg-hover, revert);
|
|
63
|
-
box-shadow: var(--rc-fab-shadow-hover, revert);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
:host(:has(button:active)) ::slotted(button) {
|
|
67
|
-
box-shadow: var(--rc-fab-shadow-active, revert);
|
|
68
|
-
transform: var(--rc-fab-active-transform, revert);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
:host(:has(button:focus-visible)) ::slotted(button) {
|
|
72
|
-
outline: var(--rc-fab-focus-ring, revert);
|
|
73
|
-
outline-offset: var(--rc-fab-focus-ring-offset, revert);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
:host(:has(button:disabled)) ::slotted(button) {
|
|
77
|
-
opacity: var(--rc-fab-disabled-opacity, revert);
|
|
78
|
-
pointer-events: none;
|
|
79
|
-
box-shadow: var(--rc-fab-disabled-shadow, revert);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@keyframes rc-fab-scroll-reveal {
|
|
83
|
-
from {
|
|
84
|
-
opacity: 0;
|
|
85
|
-
visibility: hidden;
|
|
86
|
-
}
|
|
87
|
-
50% {
|
|
88
|
-
visibility: visible;
|
|
89
|
-
}
|
|
90
|
-
to {
|
|
91
|
-
opacity: 1;
|
|
92
|
-
visibility: visible;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@supports (animation-timeline: scroll()) {
|
|
97
|
-
:host([scroll-reveal]) {
|
|
98
|
-
/*
|
|
99
|
-
* Override for embedded/demo contexts where root does not scroll:
|
|
100
|
-
* style="--rc-fab-scroll-timeline: scroll(nearest block)"
|
|
101
|
-
*/
|
|
102
|
-
--rc-fab-scroll-timeline: scroll(root block);
|
|
103
|
-
|
|
104
|
-
animation-name: rc-fab-scroll-reveal;
|
|
105
|
-
animation-duration: 1ms; /* required; scroll position drives progress, not time */
|
|
106
|
-
animation-timing-function: linear;
|
|
107
|
-
animation-fill-mode: both;
|
|
108
|
-
animation-timeline: var(--rc-fab-scroll-timeline);
|
|
109
|
-
animation-range: calc(var(--rc-fab-scroll-threshold, 300px) - 100px)
|
|
110
|
-
var(--rc-fab-scroll-threshold, 300px);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/* Keyboard escape hatch: always visible when the button has focus */
|
|
114
|
-
:host([scroll-reveal]):has(button:focus-visible) {
|
|
115
|
-
animation: none;
|
|
116
|
-
opacity: 1;
|
|
117
|
-
visibility: visible;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
@media (prefers-reduced-motion: reduce) {
|
|
121
|
-
:host([scroll-reveal]) {
|
|
122
|
-
animation-range: var(--rc-fab-scroll-threshold, 300px) var(--rc-fab-scroll-threshold, 300px);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/*
|
|
128
|
-
* JS fallback for browsers without scroll-driven animation support.
|
|
129
|
-
* [scroll-below-threshold] is toggled by ScrollObserverController;
|
|
130
|
-
* transitions replicate the CSS animation's show/hide behavior.
|
|
131
|
-
*/
|
|
132
|
-
@supports not (animation-timeline: scroll()) {
|
|
133
|
-
/* visible state: visibility snaps immediately, opacity fades in */
|
|
134
|
-
:host([scroll-reveal]) {
|
|
135
|
-
transition:
|
|
136
|
-
opacity 200ms linear,
|
|
137
|
-
visibility 0s linear;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/* hidden state: opacity fades first, visibility snaps off after delay */
|
|
141
|
-
:host([scroll-reveal][scroll-below-threshold]) {
|
|
142
|
-
opacity: 0;
|
|
143
|
-
visibility: hidden;
|
|
144
|
-
transition:
|
|
145
|
-
opacity 200ms linear,
|
|
146
|
-
visibility 0s linear 200ms;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/* Keyboard escape hatch */
|
|
150
|
-
:host([scroll-reveal][scroll-below-threshold]):has(button:focus-visible) {
|
|
151
|
-
opacity: 1;
|
|
152
|
-
visibility: visible;
|
|
153
|
-
transition: none;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
@media (prefers-reduced-motion: reduce) {
|
|
157
|
-
:host([scroll-reveal]),
|
|
158
|
-
:host([scroll-reveal][scroll-below-threshold]) {
|
|
159
|
-
transition: none;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
`;
|
|
164
|
-
var u = Object.defineProperty, c = (l, e, r, y) => {
|
|
165
|
-
for (var t = void 0, i = l.length - 1, a; i >= 0; i--)
|
|
166
|
-
(a = l[i]) && (t = a(e, r, t) || t);
|
|
167
|
-
return t && u(e, r, t), t;
|
|
168
|
-
};
|
|
169
|
-
const s = class s extends v {
|
|
170
|
-
constructor() {
|
|
171
|
-
super(), this.position = "bottom-end", this.scrollReveal = !1, new h(this, {
|
|
172
|
-
selector: ":scope > button",
|
|
173
|
-
observe: !0,
|
|
174
|
-
onMissing: () => {
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
updated(e) {
|
|
179
|
-
super.updated(e), (e.has("scrollReveal") || this.scrollReveal && !this._scrollCtrl) && this._syncScrollFallback();
|
|
180
|
-
}
|
|
181
|
-
_syncScrollFallback() {
|
|
182
|
-
if (this._scrollCtrl && (this._scrollCtrl.setOptions({ disabled: !0 }), this.removeController(this._scrollCtrl), this._scrollCtrl = void 0, this.removeAttribute("scroll-below-threshold")), !this.scrollReveal || CSS.supports("animation-timeline: scroll()"))
|
|
183
|
-
return;
|
|
184
|
-
const e = this._getThreshold();
|
|
185
|
-
this._scrollCtrl = new f(this, {
|
|
186
|
-
target: () => p(this),
|
|
187
|
-
threshold: e,
|
|
188
|
-
onScroll: (r) => {
|
|
189
|
-
this.toggleAttribute("scroll-below-threshold", r < e);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
_getThreshold() {
|
|
194
|
-
const e = getComputedStyle(this).getPropertyValue("--rc-fab-scroll-threshold").trim(), r = parseFloat(e);
|
|
195
|
-
return Number.isFinite(r) ? r : 300;
|
|
196
|
-
}
|
|
197
|
-
render() {
|
|
198
|
-
return d`<slot></slot>`;
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
s.styles = m;
|
|
202
|
-
let o = s;
|
|
203
|
-
c([
|
|
204
|
-
n({ type: String, reflect: !0 })
|
|
205
|
-
], o.prototype, "position");
|
|
206
|
-
c([
|
|
207
|
-
n({ type: Boolean, attribute: "scroll-reveal", reflect: !0 })
|
|
208
|
-
], o.prototype, "scrollReveal");
|
|
209
|
-
export {
|
|
210
|
-
o as R
|
|
211
|
-
};
|
|
212
|
-
//# sourceMappingURL=rc-fab-CdJ-peVY.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rc-fab-CdJ-peVY.js","sources":["../src/rc-fab.styles.ts","../src/rc-fab.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const fabStyles = css`\n :host {\n position: var(--rc-fab-position, fixed);\n inset-block-end: var(--rc-fab-inset-block, 1.5rem);\n inset-inline-end: var(--rc-fab-inset-inline, 1.5rem);\n z-index: var(--rc-fab-z-index, 10);\n display: inline-flex;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n :host([position='bottom-start']) {\n inset-inline-end: unset;\n inset-inline-start: var(--rc-fab-inset-inline, 1.5rem);\n }\n\n :host([position='top-end']) {\n inset-block-end: unset;\n inset-block-start: var(--rc-fab-inset-block, 1.5rem);\n }\n\n :host([position='top-start']) {\n inset-block-end: unset;\n inset-block-start: var(--rc-fab-inset-block, 1.5rem);\n inset-inline-end: unset;\n inset-inline-start: var(--rc-fab-inset-inline, 1.5rem);\n }\n\n ::slotted(button) {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--rc-fab-gap);\n\n min-width: var(--rc-fab-size);\n height: var(--rc-fab-size);\n padding-block: var(--rc-fab-padding-block, revert);\n padding-inline: var(--rc-fab-padding-inline, revert);\n\n background: var(--rc-fab-bg, revert);\n color: var(--rc-fab-color, revert);\n\n border: var(--rc-fab-border, revert);\n border-radius: var(--rc-fab-radius, revert);\n box-shadow: var(--rc-fab-shadow, revert);\n\n font-family: var(--rc-fab-font-family, revert);\n font-size: var(--rc-fab-font-size, revert);\n font-weight: var(--rc-fab-font-weight, revert);\n letter-spacing: var(--rc-fab-letter-spacing, revert);\n white-space: nowrap;\n\n transition: var(--rc-fab-transition, revert);\n }\n\n :host(:has(button:hover)) ::slotted(button) {\n background: var(--rc-fab-bg-hover, revert);\n box-shadow: var(--rc-fab-shadow-hover, revert);\n }\n\n :host(:has(button:active)) ::slotted(button) {\n box-shadow: var(--rc-fab-shadow-active, revert);\n transform: var(--rc-fab-active-transform, revert);\n }\n\n :host(:has(button:focus-visible)) ::slotted(button) {\n outline: var(--rc-fab-focus-ring, revert);\n outline-offset: var(--rc-fab-focus-ring-offset, revert);\n }\n\n :host(:has(button:disabled)) ::slotted(button) {\n opacity: var(--rc-fab-disabled-opacity, revert);\n pointer-events: none;\n box-shadow: var(--rc-fab-disabled-shadow, revert);\n }\n\n @keyframes rc-fab-scroll-reveal {\n from {\n opacity: 0;\n visibility: hidden;\n }\n 50% {\n visibility: visible;\n }\n to {\n opacity: 1;\n visibility: visible;\n }\n }\n\n @supports (animation-timeline: scroll()) {\n :host([scroll-reveal]) {\n /*\n * Override for embedded/demo contexts where root does not scroll:\n * style=\"--rc-fab-scroll-timeline: scroll(nearest block)\"\n */\n --rc-fab-scroll-timeline: scroll(root block);\n\n animation-name: rc-fab-scroll-reveal;\n animation-duration: 1ms; /* required; scroll position drives progress, not time */\n animation-timing-function: linear;\n animation-fill-mode: both;\n animation-timeline: var(--rc-fab-scroll-timeline);\n animation-range: calc(var(--rc-fab-scroll-threshold, 300px) - 100px)\n var(--rc-fab-scroll-threshold, 300px);\n }\n\n /* Keyboard escape hatch: always visible when the button has focus */\n :host([scroll-reveal]):has(button:focus-visible) {\n animation: none;\n opacity: 1;\n visibility: visible;\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([scroll-reveal]) {\n animation-range: var(--rc-fab-scroll-threshold, 300px) var(--rc-fab-scroll-threshold, 300px);\n }\n }\n }\n\n /*\n * JS fallback for browsers without scroll-driven animation support.\n * [scroll-below-threshold] is toggled by ScrollObserverController;\n * transitions replicate the CSS animation's show/hide behavior.\n */\n @supports not (animation-timeline: scroll()) {\n /* visible state: visibility snaps immediately, opacity fades in */\n :host([scroll-reveal]) {\n transition:\n opacity 200ms linear,\n visibility 0s linear;\n }\n\n /* hidden state: opacity fades first, visibility snaps off after delay */\n :host([scroll-reveal][scroll-below-threshold]) {\n opacity: 0;\n visibility: hidden;\n transition:\n opacity 200ms linear,\n visibility 0s linear 200ms;\n }\n\n /* Keyboard escape hatch */\n :host([scroll-reveal][scroll-below-threshold]):has(button:focus-visible) {\n opacity: 1;\n visibility: visible;\n transition: none;\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([scroll-reveal]),\n :host([scroll-reveal][scroll-below-threshold]) {\n transition: none;\n }\n }\n }\n`;\n\nexport default fabStyles;\n","import { LitElement, html } from 'lit';\nimport type { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport {\n NativeChildController,\n ScrollObserverController,\n findNearestScrollAncestor,\n warnMissingDirectChild,\n} from '@rcarls/rc-common';\n\nimport fabStyles from './rc-fab.styles.js';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-fab': RCFab;\n }\n}\n\n/**\n * Sticky floating action button modeled after the Material 3 Floating action button,\n * wrapping a consumer-supplied button with scroll-aware visibility.\n *\n * Adapted from the Material Design FAB component, use this for \"back to top\",\n * sticky CTAs, chat launchers, and of course as FABs in your Material Design PWA.\n *\n * Place a native `<button>` as the direct child. The button's own accessible\n * name (text content or `aria-label`) becomes the FAB's accessible name.\n *\n * Icons go inside the button alongside or instead of visible text.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-fab rc-fab docs}\n * @see {@link https://m3.material.io/components/floating-action-button/overview Material 3 Floating action button}\n *\n * @example Back to top (scroll-triggered)\n * ```html\n * <rc-fab scroll-reveal>\n * <button type=\"button\" aria-label=\"Back to top\" onclick=\"scrollTo({top:0,behavior:'smooth'})\">\n * <span aria-hidden=\"true\">↑</span>\n * </button>\n * </rc-fab>\n * ```\n *\n * @example Icon-only FAB (always visible)\n * ```html\n * <rc-fab>\n * <button type=\"button\" aria-label=\"Create\">\n * <span class=\"material-symbols-outlined\" aria-hidden=\"true\">add</span>\n * </button>\n * </rc-fab>\n * ```\n *\n * @example Extended FAB (icon + visible label)\n * ```html\n * <rc-fab>\n * <button type=\"button\">\n * <span class=\"material-symbols-outlined\" aria-hidden=\"true\">edit</span>\n * Compose\n * </button>\n * </rc-fab>\n * ```\n *\n * @slot default - The native `<button>` element. The button's own accessible\n * name (text content or `aria-label`) serves as the FAB's accessible name.\n *\n * @attr position - Viewport corner where the FAB is anchored.\n * @attr scroll-reveal - Reveal the FAB only after the page scrolls past\n * `--rc-fab-scroll-threshold`.\n * @attr [scroll-below-threshold] - Present when `scroll-reveal` is active and the page hasn't\n * scrolled past `--rc-fab-scroll-threshold` yet. Only used by the JS scroll-driven-animation\n * fallback; unused when the browser supports `animation-timeline: scroll()`.\n *\n * @cssprop [--rc-fab-position=fixed] - CSS position value. Override to `absolute` for layout-relative placement or `sticky` for scroll-snapping.\n * @cssprop [--rc-fab-inset-block=1.5rem] - Distance from the block-axis edge.\n * @cssprop [--rc-fab-inset-inline=1.5rem] - Distance from the inline-axis edge.\n * @cssprop [--rc-fab-z-index=10] - Stacking order.\n * @cssprop [--rc-fab-gap] - Gap between icon and label text. Unset by default; packaged themes\n * such as `rc-theme-material` set `0.5rem`.\n * @cssprop [--rc-fab-size] - Minimum inline size and block size of the button. Unset by\n * default (native button sizing applies); packaged themes such as `rc-theme-material` set\n * `3.5rem`.\n * @cssprop [--rc-fab-padding-block] - Button block-axis padding (defers to native button\n * padding when unset).\n * @cssprop [--rc-fab-padding-inline] - Button inline-axis padding (defers to native button\n * padding when unset).\n * @cssprop [--rc-fab-bg] - Button background (defers to native button background when unset).\n * @cssprop [--rc-fab-color] - Button foreground color (defers to native button color when\n * unset).\n * @cssprop [--rc-fab-border] - Button border (defers to native button border when unset).\n * @cssprop [--rc-fab-radius] - Button border-radius (defers to native button radius when\n * unset). Override to `9999px` for pill-shaped, `50%` for a circle (icon-only), `1rem` for\n * Material rounded-square, etc.\n * @cssprop [--rc-fab-shadow] - Elevation shadow (defers to native button shadow when unset).\n * @cssprop [--rc-fab-font-family] - Font family for label text (defers to native button font\n * when unset).\n * @cssprop [--rc-fab-font-size] - Font size for label text (defers to native button font when\n * unset).\n * @cssprop [--rc-fab-font-weight] - Font weight for label text (defers to native button font\n * when unset).\n * @cssprop [--rc-fab-letter-spacing] - Letter spacing for label text (defers to native button\n * styling when unset).\n * @cssprop [--rc-fab-transition] - Transition shorthand for hover/active state changes (defers\n * to native button transition when unset).\n * @cssprop [--rc-fab-bg-hover] - Hover background (defers to native `:hover` styling when\n * unset).\n * @cssprop [--rc-fab-shadow-hover] - Hover shadow (defers to native `:hover` styling when\n * unset).\n * @cssprop [--rc-fab-shadow-active] - Pressed shadow (defers to native `:active` styling when\n * unset).\n * @cssprop [--rc-fab-active-transform] - Transform applied while pressed, e.g. `scale(0.96)`\n * (defers to native `:active` styling when unset).\n * @cssprop [--rc-fab-focus-ring] - Focus ring style (defers to native `:focus-visible` styling\n * when unset).\n * @cssprop [--rc-fab-focus-ring-offset] - Focus ring offset (defers to native `:focus-visible`\n * styling when unset).\n * @cssprop [--rc-fab-disabled-opacity] - Opacity applied when the button is disabled (defers to\n * native `:disabled` styling when unset).\n * @cssprop [--rc-fab-disabled-shadow] - Shadow applied when the button is disabled (defers to\n * native `:disabled` styling when unset).\n * @cssprop [--rc-fab-scroll-threshold=300px] - Scroll distance at which the FAB becomes fully visible. Requires the `scroll-reveal` attribute. The JS fallback reads this value once on connect; px units only.\n * @cssprop [--rc-fab-scroll-timeline=scroll(root block)] - The `animation-timeline` value used for scroll-reveal. Override to target a different scroller, e.g. `scroll(nearest block)` for embedded contexts. CSS path only; the JS fallback discovers the nearest scrollable ancestor automatically.\n */\nexport class RCFab extends LitElement {\n static override styles = fabStyles;\n\n private _scrollCtrl?: ScrollObserverController;\n\n /** Viewport corner where the FAB is anchored. Uses logical inline/block directions. */\n @property({ type: String, reflect: true })\n position: 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start' = 'bottom-end';\n\n /** Reveal the FAB only after the page scrolls past `--rc-fab-scroll-threshold` (default 300 px). Uses CSS scroll-driven animations; falls back to a passive scroll listener in unsupported browsers. */\n @property({ type: Boolean, attribute: 'scroll-reveal', reflect: true })\n scrollReveal = false;\n\n constructor() {\n super();\n\n new NativeChildController<HTMLButtonElement>(this, {\n selector: ':scope > button',\n observe: true,\n onMissing: () => {\n if (import.meta.env.DEV) {\n warnMissingDirectChild(this, {\n selector: ':scope > button',\n message:\n '[rc-fab] No direct child <button> found. Place a native <button> inside <rc-fab>.',\n });\n }\n },\n });\n }\n\n protected override updated(changed: PropertyValues): void {\n super.updated(changed);\n\n if (changed.has('scrollReveal') || (this.scrollReveal && !this._scrollCtrl)) {\n this._syncScrollFallback();\n }\n }\n\n private _syncScrollFallback(): void {\n if (this._scrollCtrl) {\n this._scrollCtrl.setOptions({ disabled: true });\n this.removeController(this._scrollCtrl);\n this._scrollCtrl = undefined;\n\n this.removeAttribute('scroll-below-threshold');\n }\n\n if (!this.scrollReveal || CSS.supports('animation-timeline: scroll()')) {\n return;\n }\n\n const threshold = this._getThreshold();\n\n this._scrollCtrl = new ScrollObserverController(this, {\n target: () => findNearestScrollAncestor(this),\n threshold,\n onScroll: (scrollTop) => {\n this.toggleAttribute('scroll-below-threshold', scrollTop < threshold);\n },\n });\n }\n\n private _getThreshold(): number {\n const raw = getComputedStyle(this).getPropertyValue('--rc-fab-scroll-threshold').trim();\n const n = parseFloat(raw);\n\n return Number.isFinite(n) ? n : 300;\n }\n\n protected override render() {\n return html`<slot></slot>`;\n }\n}\n\nexport default RCFab;\n"],"names":["fabStyles","css","_RCFab","LitElement","NativeChildController","changed","threshold","ScrollObserverController","findNearestScrollAncestor","scrollTop","raw","n","html","RCFab","__decorateClass","property"],"mappings":";;;AAEO,MAAMA,IAAYC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;ACwHlB,MAAMC,IAAN,MAAMA,UAAcC,EAAW;AAAA,EAapC,cAAc;AACZ,UAAA,GAPF,KAAA,WAAoE,cAIpE,KAAA,eAAe,IAKb,IAAIC,EAAyC,MAAM;AAAA,MACjD,UAAU;AAAA,MACV,SAAS;AAAA,MACT,WAAW,MAAM;AAAA,MAQjB;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEmB,QAAQC,GAA+B;AACxD,UAAM,QAAQA,CAAO,IAEjBA,EAAQ,IAAI,cAAc,KAAM,KAAK,gBAAgB,CAAC,KAAK,gBAC7D,KAAK,oBAAA;AAAA,EAET;AAAA,EAEQ,sBAA4B;AASlC,QARI,KAAK,gBACP,KAAK,YAAY,WAAW,EAAE,UAAU,IAAM,GAC9C,KAAK,iBAAiB,KAAK,WAAW,GACtC,KAAK,cAAc,QAEnB,KAAK,gBAAgB,wBAAwB,IAG3C,CAAC,KAAK,gBAAgB,IAAI,SAAS,8BAA8B;AACnE;AAGF,UAAMC,IAAY,KAAK,cAAA;AAEvB,SAAK,cAAc,IAAIC,EAAyB,MAAM;AAAA,MACpD,QAAQ,MAAMC,EAA0B,IAAI;AAAA,MAC5C,WAAAF;AAAA,MACA,UAAU,CAACG,MAAc;AACvB,aAAK,gBAAgB,0BAA0BA,IAAYH,CAAS;AAAA,MACtE;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEQ,gBAAwB;AAC9B,UAAMI,IAAM,iBAAiB,IAAI,EAAE,iBAAiB,2BAA2B,EAAE,KAAA,GAC3EC,IAAI,WAAWD,CAAG;AAExB,WAAO,OAAO,SAASC,CAAC,IAAIA,IAAI;AAAA,EAClC;AAAA,EAEmB,SAAS;AAC1B,WAAOC;AAAA,EACT;AACF;AAxEEV,EAAgB,SAASF;AADpB,IAAMa,IAANX;AAOLY,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GAN9BF,EAOX,WAAA,UAAA;AAIAC,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,WAAW,iBAAiB,SAAS,IAAM;AAAA,GAV3DF,EAWX,WAAA,cAAA;"}
|