@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 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 [Material 3 Floating action button](https://m3.material.io/components/floating-action-button/overview), wrapping a consumer-supplied button with scroll-aware visibility.
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", sticky CTAs, chat launchers, and of course as FABs in your
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: `bottom-end` (default), `bottom-start`, `top-end`, `top-start` |
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 (e.g. `scroll(nearest block)`) |
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 behaviour
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 behaviour.
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": "'bottom-end' | 'bottom-start' | 'top-end' | 'top-start'"
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": "'bottom-end'",
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": "'bottom-end' | 'bottom-start' | 'top-end' | 'top-start'"
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": "'bottom-end'",
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;"}
@@ -1,4 +1,4 @@
1
- import { R as e } from "./rc-fab-CdJ-peVY.js";
1
+ import { R as e } from "./rc-fab-CoU-hPfL.js";
2
2
  customElements.get("rc-fab") || customElements.define("rc-fab", e);
3
3
  export {
4
4
  e as RCFab
package/dist/rc-fab.js CHANGED
@@ -1,4 +1,4 @@
1
- import { R as r } from "./rc-fab-CdJ-peVY.js";
1
+ import { R as r } from "./rc-fab-CoU-hPfL.js";
2
2
  export {
3
3
  r as RCFab
4
4
  };
@@ -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 - Viewport corner where the FAB is anchored.
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: 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start';
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.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.6.0"
48
+ "@rcarls/rc-common": "0.7.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@custom-elements-manifest/analyzer": "0.11.0",
@@ -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;"}