@rcarls/rc-fab 0.0.0-next-20260921045401

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 ADDED
@@ -0,0 +1,115 @@
1
+ # @rcarls/rc-fab
2
+
3
+ ## 0.0.0-next-20260921045401
4
+
5
+ ### Minor Changes
6
+
7
+ - 6e548d9: Several components handle `prefers-reduced-motion: reduce` in their own
8
+ shadow styles, outside a theme's reach, and refine it the same way the theme
9
+ layer already does: spatial motion (`translate`, `scale`, `transform`,
10
+ `inline-size`, `block-size`) goes fully to zero, while effects motion (color,
11
+ opacity) shortens instead of disappearing.
12
+
13
+ `rc-app-bar` zeros its host's hide/show `translate` but shortens the
14
+ expanded title's fade-in. `rc-disclosure` keeps its panel's expand/collapse
15
+ zeroed, but its summary/content backstop for a theme-added transition now
16
+ shortens instead of zeroing it outright. `rc-list-item`'s row and
17
+ state-layer transition only color and opacity, so the whole sweep shortens.
18
+ `rc-fab-menu`'s popup mixes `scale` with opacity/overlay/display under one
19
+ shared duration; reduced motion now zeros the scale and shortens the rest.
20
+ `rc-switch`'s thumb mixes background-color with inline-size/block-size/
21
+ transform the same way; the icons and track, which each transition only one
22
+ kind of property, needed only the zero-or-shorten call. `rc-fab`'s
23
+ scroll-reveal is opacity/visibility only with no time-based duration to
24
+ shorten in its scroll-linked path, so reduced motion narrows the scroll
25
+ window instead of collapsing it to zero; its JS-transition fallback path
26
+ shortens the same fade rather than removing it. `rc-progress`'s determinate
27
+ fill transition, previously unguarded under reduced motion, is now zeroed;
28
+ its indeterminate loop, a continuous sweep with no meaningful shortened
29
+ form, stays fully stopped rather than shortened.
30
+
31
+ ### Patch Changes
32
+
33
+ - Updated dependencies [50d5610]
34
+ - Updated dependencies [4341311]
35
+ - @rcarls/rc-common@0.0.0-next-20260921045401
36
+
37
+ ## 0.6.0
38
+
39
+ ### Patch Changes
40
+
41
+ - Updated dependencies [6683eb9]
42
+ - Updated dependencies [ee7ba6c]
43
+ - Updated dependencies [30eb232]
44
+ - @rcarls/rc-common@0.6.0
45
+
46
+ ## 0.5.0
47
+
48
+ ### Patch Changes
49
+
50
+ - Updated dependencies [4ae2ef0]
51
+ - Updated dependencies [689340c]
52
+ - @rcarls/rc-common@0.5.0
53
+
54
+ ## 0.4.2
55
+
56
+ ### Patch Changes
57
+
58
+ - @rcarls/rc-common@0.4.2
59
+
60
+ ## 0.4.1
61
+
62
+ ### Patch Changes
63
+
64
+ - @rcarls/rc-common@0.4.1
65
+
66
+ ## 0.4.0
67
+
68
+ ### Patch Changes
69
+
70
+ - 55e8ab5: Preserve UA-like control and surface styling until an optional theme supplies decorative tokens.
71
+ Restore themed button hover and pressed state layers, including a pointer-origin Material ripple.
72
+ - 9a4aa64: Fix `rc-fab`'s CSS custom property documentation to match the component's
73
+ actual `revert`/unset fallbacks (background, color, radius, shadow, size,
74
+ gap, padding, font, focus-ring, and transition properties all defer to the
75
+ native button or a packaged theme, not the previously documented literal
76
+ defaults), and document `position`, `scroll-reveal`, and the computed
77
+ `scroll-below-threshold` attribute.
78
+ - Updated dependencies [e57277f]
79
+ - Updated dependencies [ccca8e2]
80
+ - Updated dependencies [037b1b3]
81
+ - @rcarls/rc-common@0.4.0
82
+
83
+ ## 0.3.2
84
+
85
+ ### Patch Changes
86
+
87
+ - Updated dependencies [88b4086]
88
+ - @rcarls/rc-common@0.3.2
89
+
90
+ ## 0.3.1
91
+
92
+ ### Patch Changes
93
+
94
+ - @rcarls/rc-common@0.3.1
95
+
96
+ ## 0.3.0
97
+
98
+ ### Added
99
+
100
+ - Add scroll-reveal behavior for back-to-top floating action buttons.
101
+
102
+ ### Changed
103
+
104
+ - Redesign the default behavior around the back-to-top use case.
105
+ - Update package metadata, README intro, and docs links.
106
+
107
+ ### Fixed
108
+
109
+ - Generalize published FAB examples.
110
+
111
+ ### Dependencies
112
+
113
+ - Sync internal dependencies to 0.3.0.
114
+
115
+ ## 0.2.0
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # `@rcarls/rc-fab`
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.
4
+
5
+ Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-fab](https://richardcarls.github.io/rc-webcomponents/components/rc-fab).
6
+
7
+ Use this for "back to top", sticky CTAs, chat launchers, and of course as FABs in your
8
+ Material Design website or PWA.
9
+
10
+ Place a native `<button>` as the direct child. The button's own accessible name (text
11
+ content or `aria-label`) becomes the FAB's accessible name.
12
+
13
+ Icons go inside the button alongside or instead of visible text.
14
+
15
+ ## Installation
16
+
17
+ ```sh
18
+ npm install @rcarls/rc-fab
19
+ ```
20
+
21
+ ```js
22
+ import '@rcarls/rc-fab/define';
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```html
28
+ <!-- Back to top (scroll-triggered) -->
29
+ <rc-fab scroll-reveal>
30
+ <button type="button" aria-label="Back to top" onclick="scrollTo({ top: 0, behavior: 'smooth' })">
31
+ <span aria-hidden="true">↑</span>
32
+ </button>
33
+ </rc-fab>
34
+
35
+ <!-- Always-visible action -->
36
+ <rc-fab>
37
+ <button type="button" aria-label="Create">
38
+ <span class="material-symbols-outlined" aria-hidden="true">add</span>
39
+ </button>
40
+ </rc-fab>
41
+
42
+ <!-- Extended FAB (icon + visible label) -->
43
+ <rc-fab>
44
+ <button type="button">
45
+ <span class="material-symbols-outlined" aria-hidden="true">edit</span>
46
+ Compose
47
+ </button>
48
+ </rc-fab>
49
+ ```
50
+
51
+ ## Key attributes and CSS custom properties
52
+
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` |
57
+
58
+ | CSS custom property | Default | Description |
59
+ | --------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
60
+ | `--rc-fab-position` | `fixed` | CSS position value. Use `absolute` for layout-relative placement |
61
+ | `--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)`) |
63
+ | `--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
+ | `--rc-fab-size` | _(native)_ | Height and minimum width; defers to native button sizing until set (packaged themes set `3.5rem`) |
65
+ | `--rc-fab-bg` | _(native)_ | Button background color; defers to the native button's background until set |
66
+ | `--rc-fab-color` | _(native)_ | Button foreground color; defers to the native button's color until set |
67
+ | `--rc-fab-shadow` | _(native)_ | Elevation shadow; defers to the native button's shadow until set |
68
+ | `--rc-fab-inset-block` | `1.5rem` | Distance from the block-axis edge |
69
+ | `--rc-fab-inset-inline` | `1.5rem` | Distance from the inline-axis edge |
70
+
71
+ ## Scroll-reveal behaviour
72
+
73
+ `scroll-reveal` uses CSS scroll-driven animations (`animation-timeline: scroll()`).
74
+
75
+ In browsers that do not yet support this API, a passive scroll listener provides
76
+ equivalent behaviour.
77
+
78
+ The `[scroll-below-threshold]` attribute is toggled on the host and CSS transitions
79
+ handle the show/hide. The FAB is always visible in any remaining edge cases.
80
+
81
+ When the button receives `focus-visible` while below the threshold the animation is
82
+ overridden and the FAB is shown fully.
83
+
84
+ ## Accessibility
85
+
86
+ - The button's `aria-label` or text content is its accessible name.
87
+ - Decorative icon content should use `aria-hidden="true"`.
88
+ - The component does not choose a semantic landmark role or page position.
89
+ - With `scroll-reveal`, the FAB is removed from the accessibility tree and tab
90
+ order while hidden, matching the expectation that invisible controls are
91
+ unreachable.
92
+ - The `50%` point in the reveal animation lifts `visibility: hidden` early so
93
+ keyboard users can reach the button as it starts fading in.
@@ -0,0 +1,281 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "readme": "",
4
+ "modules": [
5
+ {
6
+ "kind": "javascript-module",
7
+ "path": "src/define.ts",
8
+ "declarations": [],
9
+ "exports": [
10
+ {
11
+ "kind": "custom-element-definition",
12
+ "name": "rc-fab",
13
+ "declaration": {
14
+ "name": "RCFab",
15
+ "module": "/src/index.js"
16
+ }
17
+ },
18
+ {
19
+ "kind": "js",
20
+ "name": "*",
21
+ "declaration": {
22
+ "name": "*",
23
+ "module": "src/index.js"
24
+ }
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "kind": "javascript-module",
30
+ "path": "src/index.ts",
31
+ "declarations": [],
32
+ "exports": [
33
+ {
34
+ "kind": "js",
35
+ "name": "*",
36
+ "declaration": {
37
+ "name": "*",
38
+ "module": "src/rc-fab.js"
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "kind": "javascript-module",
45
+ "path": "src/rc-fab.ts",
46
+ "declarations": [
47
+ {
48
+ "kind": "class",
49
+ "description": "Sticky floating action button modeled after the Material 3 Floating action button,\nwrapping a consumer-supplied button with scroll-aware visibility.\n\nAdapted from the Material Design FAB component, use this for \"back to top\",\nsticky CTAs, chat launchers, and of course as FABs in your Material Design PWA.\n\nPlace a native `<button>` as the direct child. The button's own accessible\nname (text content or `aria-label`) becomes the FAB's accessible name.\n\nIcons go inside the button alongside or instead of visible text.",
50
+ "name": "RCFab",
51
+ "cssProperties": [
52
+ {
53
+ "description": "CSS position value. Override to `absolute` for layout-relative placement or `sticky` for scroll-snapping.",
54
+ "name": "--rc-fab-position",
55
+ "default": "fixed"
56
+ },
57
+ {
58
+ "description": "Distance from the block-axis edge.",
59
+ "name": "--rc-fab-inset-block",
60
+ "default": "1.5rem"
61
+ },
62
+ {
63
+ "description": "Distance from the inline-axis edge.",
64
+ "name": "--rc-fab-inset-inline",
65
+ "default": "1.5rem"
66
+ },
67
+ {
68
+ "description": "Stacking order.",
69
+ "name": "--rc-fab-z-index",
70
+ "default": "10"
71
+ },
72
+ {
73
+ "description": "Gap between icon and label text. Unset by default; packaged themes such as `rc-theme-material` set `0.5rem`.",
74
+ "name": "--rc-fab-gap"
75
+ },
76
+ {
77
+ "description": "Minimum inline size and block size of the button. Unset by default (native button sizing applies); packaged themes such as `rc-theme-material` set `3.5rem`.",
78
+ "name": "--rc-fab-size"
79
+ },
80
+ {
81
+ "description": "Button block-axis padding (defers to native button padding when unset).",
82
+ "name": "--rc-fab-padding-block"
83
+ },
84
+ {
85
+ "description": "Button inline-axis padding (defers to native button padding when unset).",
86
+ "name": "--rc-fab-padding-inline"
87
+ },
88
+ {
89
+ "description": "Button background (defers to native button background when unset).",
90
+ "name": "--rc-fab-bg"
91
+ },
92
+ {
93
+ "description": "Button foreground color (defers to native button color when unset).",
94
+ "name": "--rc-fab-color"
95
+ },
96
+ {
97
+ "description": "Button border (defers to native button border when unset).",
98
+ "name": "--rc-fab-border"
99
+ },
100
+ {
101
+ "description": "Button border-radius (defers to native button radius when unset). Override to `9999px` for pill-shaped, `50%` for a circle (icon-only), `1rem` for Material rounded-square, etc.",
102
+ "name": "--rc-fab-radius"
103
+ },
104
+ {
105
+ "description": "Elevation shadow (defers to native button shadow when unset).",
106
+ "name": "--rc-fab-shadow"
107
+ },
108
+ {
109
+ "description": "Font family for label text (defers to native button font when unset).",
110
+ "name": "--rc-fab-font-family"
111
+ },
112
+ {
113
+ "description": "Font size for label text (defers to native button font when unset).",
114
+ "name": "--rc-fab-font-size"
115
+ },
116
+ {
117
+ "description": "Font weight for label text (defers to native button font when unset).",
118
+ "name": "--rc-fab-font-weight"
119
+ },
120
+ {
121
+ "description": "Letter spacing for label text (defers to native button styling when unset).",
122
+ "name": "--rc-fab-letter-spacing"
123
+ },
124
+ {
125
+ "description": "Transition shorthand for hover/active state changes (defers to native button transition when unset).",
126
+ "name": "--rc-fab-transition"
127
+ },
128
+ {
129
+ "description": "Hover background (defers to native `:hover` styling when unset).",
130
+ "name": "--rc-fab-bg-hover"
131
+ },
132
+ {
133
+ "description": "Hover shadow (defers to native `:hover` styling when unset).",
134
+ "name": "--rc-fab-shadow-hover"
135
+ },
136
+ {
137
+ "description": "Pressed shadow (defers to native `:active` styling when unset).",
138
+ "name": "--rc-fab-shadow-active"
139
+ },
140
+ {
141
+ "description": "Transform applied while pressed, e.g. `scale(0.96)` (defers to native `:active` styling when unset).",
142
+ "name": "--rc-fab-active-transform"
143
+ },
144
+ {
145
+ "description": "Focus ring style (defers to native `:focus-visible` styling when unset).",
146
+ "name": "--rc-fab-focus-ring"
147
+ },
148
+ {
149
+ "description": "Focus ring offset (defers to native `:focus-visible` styling when unset).",
150
+ "name": "--rc-fab-focus-ring-offset"
151
+ },
152
+ {
153
+ "description": "Opacity applied when the button is disabled (defers to native `:disabled` styling when unset).",
154
+ "name": "--rc-fab-disabled-opacity"
155
+ },
156
+ {
157
+ "description": "Shadow applied when the button is disabled (defers to native `:disabled` styling when unset).",
158
+ "name": "--rc-fab-disabled-shadow"
159
+ },
160
+ {
161
+ "description": "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.",
162
+ "name": "--rc-fab-scroll-threshold",
163
+ "default": "300px"
164
+ },
165
+ {
166
+ "description": "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.",
167
+ "name": "--rc-fab-scroll-timeline",
168
+ "default": "scroll(root block)"
169
+ }
170
+ ],
171
+ "slots": [
172
+ {
173
+ "description": "The native `<button>` element. The button's own accessible name (text content or `aria-label`) serves as the FAB's accessible name.",
174
+ "name": "default"
175
+ }
176
+ ],
177
+ "members": [
178
+ {
179
+ "kind": "field",
180
+ "name": "_scrollCtrl",
181
+ "type": {
182
+ "text": "ScrollObserverController | undefined"
183
+ },
184
+ "privacy": "private"
185
+ },
186
+ {
187
+ "kind": "field",
188
+ "name": "position",
189
+ "type": {
190
+ "text": "'bottom-end' | 'bottom-start' | 'top-end' | 'top-start'"
191
+ },
192
+ "default": "'bottom-end'",
193
+ "description": "Viewport corner where the FAB is anchored. Uses logical inline/block directions.",
194
+ "attribute": "position",
195
+ "reflects": true
196
+ },
197
+ {
198
+ "kind": "field",
199
+ "name": "scrollReveal",
200
+ "type": {
201
+ "text": "boolean"
202
+ },
203
+ "default": "false",
204
+ "description": "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.",
205
+ "attribute": "scroll-reveal",
206
+ "reflects": true
207
+ },
208
+ {
209
+ "kind": "method",
210
+ "name": "_syncScrollFallback",
211
+ "privacy": "private",
212
+ "return": {
213
+ "type": {
214
+ "text": "void"
215
+ }
216
+ }
217
+ },
218
+ {
219
+ "kind": "method",
220
+ "name": "_getThreshold",
221
+ "privacy": "private",
222
+ "return": {
223
+ "type": {
224
+ "text": "number"
225
+ }
226
+ }
227
+ }
228
+ ],
229
+ "attributes": [
230
+ {
231
+ "description": "Viewport corner where the FAB is anchored. Uses logical inline/block directions.",
232
+ "name": "position",
233
+ "type": {
234
+ "text": "'bottom-end' | 'bottom-start' | 'top-end' | 'top-start'"
235
+ },
236
+ "default": "'bottom-end'",
237
+ "fieldName": "position"
238
+ },
239
+ {
240
+ "description": "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.",
241
+ "name": "scroll-reveal",
242
+ "type": {
243
+ "text": "boolean"
244
+ },
245
+ "default": "false",
246
+ "fieldName": "scrollReveal"
247
+ },
248
+ {
249
+ "description": "Present when `scroll-reveal` is active and the page hasn't scrolled past `--rc-fab-scroll-threshold` yet. Only used by the JS scroll-driven-animation fallback; unused when the browser supports `animation-timeline: scroll()`.",
250
+ "name": "scroll-below-threshold"
251
+ }
252
+ ],
253
+ "superclass": {
254
+ "name": "LitElement",
255
+ "package": "lit"
256
+ },
257
+ "tagName": "rc-fab",
258
+ "customElement": true
259
+ }
260
+ ],
261
+ "exports": [
262
+ {
263
+ "kind": "js",
264
+ "name": "RCFab",
265
+ "declaration": {
266
+ "name": "RCFab",
267
+ "module": "src/rc-fab.ts"
268
+ }
269
+ },
270
+ {
271
+ "kind": "js",
272
+ "name": "default",
273
+ "declaration": {
274
+ "name": "RCFab",
275
+ "module": "src/rc-fab.ts"
276
+ }
277
+ }
278
+ ]
279
+ }
280
+ ]
281
+ }
@@ -0,0 +1,227 @@
1
+ import { css as b, LitElement as d, html as v } 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
+ /*
121
+ * Effects motion (opacity/visibility, no translate or scale), so the
122
+ * range narrows to a brief scroll window instead of collapsing to zero
123
+ * width: a scroll-linked timeline has no duration to shorten, so a
124
+ * narrow range is this animation's equivalent of a shortened duration.
125
+ */
126
+ @media (prefers-reduced-motion: reduce) {
127
+ :host([scroll-reveal]) {
128
+ animation-range: calc(var(--rc-fab-scroll-threshold, 300px) - 8px)
129
+ var(--rc-fab-scroll-threshold, 300px);
130
+ }
131
+ }
132
+ }
133
+
134
+ /*
135
+ * JS fallback for browsers without scroll-driven animation support.
136
+ * [scroll-below-threshold] is toggled by ScrollObserverController;
137
+ * transitions replicate the CSS animation's show/hide behavior.
138
+ */
139
+ @supports not (animation-timeline: scroll()) {
140
+ /* visible state: visibility snaps immediately, opacity fades in */
141
+ :host([scroll-reveal]) {
142
+ transition:
143
+ opacity 200ms linear,
144
+ visibility 0s linear;
145
+ }
146
+
147
+ /* hidden state: opacity fades first, visibility snaps off after delay */
148
+ :host([scroll-reveal][scroll-below-threshold]) {
149
+ opacity: 0;
150
+ visibility: hidden;
151
+ transition:
152
+ opacity 200ms linear,
153
+ visibility 0s linear 200ms;
154
+ }
155
+
156
+ /* Keyboard escape hatch */
157
+ :host([scroll-reveal][scroll-below-threshold]):has(button:focus-visible) {
158
+ opacity: 1;
159
+ visibility: visible;
160
+ transition: none;
161
+ }
162
+
163
+ /* Effects motion (opacity/visibility): shortened rather than removed. */
164
+ @media (prefers-reduced-motion: reduce) {
165
+ :host([scroll-reveal]) {
166
+ transition:
167
+ opacity 50ms linear,
168
+ visibility 0s linear;
169
+ }
170
+
171
+ :host([scroll-reveal][scroll-below-threshold]) {
172
+ transition:
173
+ opacity 50ms linear,
174
+ visibility 0s linear 50ms;
175
+ }
176
+ }
177
+ }
178
+ `;
179
+ var u = Object.defineProperty, c = (l, e, t, y) => {
180
+ for (var r = void 0, o = l.length - 1, a; o >= 0; o--)
181
+ (a = l[o]) && (r = a(e, t, r) || r);
182
+ return r && u(e, t, r), r;
183
+ };
184
+ const s = class s extends d {
185
+ constructor() {
186
+ super(), this.position = "bottom-end", this.scrollReveal = !1, new h(this, {
187
+ selector: ":scope > button",
188
+ observe: !0,
189
+ onMissing: () => {
190
+ }
191
+ });
192
+ }
193
+ updated(e) {
194
+ super.updated(e), (e.has("scrollReveal") || this.scrollReveal && !this._scrollCtrl) && this._syncScrollFallback();
195
+ }
196
+ _syncScrollFallback() {
197
+ 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()"))
198
+ return;
199
+ const e = this._getThreshold();
200
+ this._scrollCtrl = new f(this, {
201
+ target: () => p(this),
202
+ threshold: e,
203
+ onScroll: (t) => {
204
+ this.toggleAttribute("scroll-below-threshold", t < e);
205
+ }
206
+ });
207
+ }
208
+ _getThreshold() {
209
+ const e = getComputedStyle(this).getPropertyValue("--rc-fab-scroll-threshold").trim(), t = parseFloat(e);
210
+ return Number.isFinite(t) ? t : 300;
211
+ }
212
+ render() {
213
+ return v`<slot></slot>`;
214
+ }
215
+ };
216
+ s.styles = m;
217
+ let i = s;
218
+ c([
219
+ n({ type: String, reflect: !0 })
220
+ ], i.prototype, "position");
221
+ c([
222
+ n({ type: Boolean, attribute: "scroll-reveal", reflect: !0 })
223
+ ], i.prototype, "scrollReveal");
224
+ export {
225
+ i as R
226
+ };
227
+ //# sourceMappingURL=rc-fab-DvYz_XQB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-fab-DvYz_XQB.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 /*\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 - 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;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;"}
@@ -0,0 +1,6 @@
1
+ import { R as e } from "./rc-fab-DvYz_XQB.js";
2
+ customElements.get("rc-fab") || customElements.define("rc-fab", e);
3
+ export {
4
+ e as RCFab
5
+ };
6
+ //# sourceMappingURL=rc-fab-define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-fab-define.js","sources":["../src/define.ts"],"sourcesContent":["import { RCFab } from './index.js';\n\ncustomElements.get('rc-fab') || customElements.define('rc-fab', RCFab);\n\nexport * from './index.js';\n"],"names":["RCFab"],"mappings":";AAEA,eAAe,IAAI,QAAQ,KAAK,eAAe,OAAO,UAAUA,CAAK;"}
package/dist/rc-fab.js ADDED
@@ -0,0 +1,5 @@
1
+ import { R as r } from "./rc-fab-DvYz_XQB.js";
2
+ export {
3
+ r as RCFab
4
+ };
5
+ //# sourceMappingURL=rc-fab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-fab.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -0,0 +1 @@
1
+ export * from './rc-fab.js';
@@ -0,0 +1,123 @@
1
+ import { LitElement, PropertyValues } from 'lit';
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ 'rc-fab': RCFab;
5
+ }
6
+ }
7
+ /**
8
+ * Sticky floating action button modeled after the Material 3 Floating action button,
9
+ * wrapping a consumer-supplied button with scroll-aware visibility.
10
+ *
11
+ * Adapted from the Material Design FAB component, use this for "back to top",
12
+ * sticky CTAs, chat launchers, and of course as FABs in your Material Design PWA.
13
+ *
14
+ * Place a native `<button>` as the direct child. The button's own accessible
15
+ * name (text content or `aria-label`) becomes the FAB's accessible name.
16
+ *
17
+ * Icons go inside the button alongside or instead of visible text.
18
+ *
19
+ * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-fab rc-fab docs}
20
+ * @see {@link https://m3.material.io/components/floating-action-button/overview Material 3 Floating action button}
21
+ *
22
+ * @example Back to top (scroll-triggered)
23
+ * ```html
24
+ * <rc-fab scroll-reveal>
25
+ * <button type="button" aria-label="Back to top" onclick="scrollTo({top:0,behavior:'smooth'})">
26
+ * <span aria-hidden="true">↑</span>
27
+ * </button>
28
+ * </rc-fab>
29
+ * ```
30
+ *
31
+ * @example Icon-only FAB (always visible)
32
+ * ```html
33
+ * <rc-fab>
34
+ * <button type="button" aria-label="Create">
35
+ * <span class="material-symbols-outlined" aria-hidden="true">add</span>
36
+ * </button>
37
+ * </rc-fab>
38
+ * ```
39
+ *
40
+ * @example Extended FAB (icon + visible label)
41
+ * ```html
42
+ * <rc-fab>
43
+ * <button type="button">
44
+ * <span class="material-symbols-outlined" aria-hidden="true">edit</span>
45
+ * Compose
46
+ * </button>
47
+ * </rc-fab>
48
+ * ```
49
+ *
50
+ * @slot default - The native `<button>` element. The button's own accessible
51
+ * name (text content or `aria-label`) serves as the FAB's accessible name.
52
+ *
53
+ * @attr position - Viewport corner where the FAB is anchored.
54
+ * @attr scroll-reveal - Reveal the FAB only after the page scrolls past
55
+ * `--rc-fab-scroll-threshold`.
56
+ * @attr [scroll-below-threshold] - Present when `scroll-reveal` is active and the page hasn't
57
+ * scrolled past `--rc-fab-scroll-threshold` yet. Only used by the JS scroll-driven-animation
58
+ * fallback; unused when the browser supports `animation-timeline: scroll()`.
59
+ *
60
+ * @cssprop [--rc-fab-position=fixed] - CSS position value. Override to `absolute` for layout-relative placement or `sticky` for scroll-snapping.
61
+ * @cssprop [--rc-fab-inset-block=1.5rem] - Distance from the block-axis edge.
62
+ * @cssprop [--rc-fab-inset-inline=1.5rem] - Distance from the inline-axis edge.
63
+ * @cssprop [--rc-fab-z-index=10] - Stacking order.
64
+ * @cssprop [--rc-fab-gap] - Gap between icon and label text. Unset by default; packaged themes
65
+ * such as `rc-theme-material` set `0.5rem`.
66
+ * @cssprop [--rc-fab-size] - Minimum inline size and block size of the button. Unset by
67
+ * default (native button sizing applies); packaged themes such as `rc-theme-material` set
68
+ * `3.5rem`.
69
+ * @cssprop [--rc-fab-padding-block] - Button block-axis padding (defers to native button
70
+ * padding when unset).
71
+ * @cssprop [--rc-fab-padding-inline] - Button inline-axis padding (defers to native button
72
+ * padding when unset).
73
+ * @cssprop [--rc-fab-bg] - Button background (defers to native button background when unset).
74
+ * @cssprop [--rc-fab-color] - Button foreground color (defers to native button color when
75
+ * unset).
76
+ * @cssprop [--rc-fab-border] - Button border (defers to native button border when unset).
77
+ * @cssprop [--rc-fab-radius] - Button border-radius (defers to native button radius when
78
+ * unset). Override to `9999px` for pill-shaped, `50%` for a circle (icon-only), `1rem` for
79
+ * Material rounded-square, etc.
80
+ * @cssprop [--rc-fab-shadow] - Elevation shadow (defers to native button shadow when unset).
81
+ * @cssprop [--rc-fab-font-family] - Font family for label text (defers to native button font
82
+ * when unset).
83
+ * @cssprop [--rc-fab-font-size] - Font size for label text (defers to native button font when
84
+ * unset).
85
+ * @cssprop [--rc-fab-font-weight] - Font weight for label text (defers to native button font
86
+ * when unset).
87
+ * @cssprop [--rc-fab-letter-spacing] - Letter spacing for label text (defers to native button
88
+ * styling when unset).
89
+ * @cssprop [--rc-fab-transition] - Transition shorthand for hover/active state changes (defers
90
+ * to native button transition when unset).
91
+ * @cssprop [--rc-fab-bg-hover] - Hover background (defers to native `:hover` styling when
92
+ * unset).
93
+ * @cssprop [--rc-fab-shadow-hover] - Hover shadow (defers to native `:hover` styling when
94
+ * unset).
95
+ * @cssprop [--rc-fab-shadow-active] - Pressed shadow (defers to native `:active` styling when
96
+ * unset).
97
+ * @cssprop [--rc-fab-active-transform] - Transform applied while pressed, e.g. `scale(0.96)`
98
+ * (defers to native `:active` styling when unset).
99
+ * @cssprop [--rc-fab-focus-ring] - Focus ring style (defers to native `:focus-visible` styling
100
+ * when unset).
101
+ * @cssprop [--rc-fab-focus-ring-offset] - Focus ring offset (defers to native `:focus-visible`
102
+ * styling when unset).
103
+ * @cssprop [--rc-fab-disabled-opacity] - Opacity applied when the button is disabled (defers to
104
+ * native `:disabled` styling when unset).
105
+ * @cssprop [--rc-fab-disabled-shadow] - Shadow applied when the button is disabled (defers to
106
+ * native `:disabled` styling when unset).
107
+ * @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.
108
+ * @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.
109
+ */
110
+ export declare class RCFab extends LitElement {
111
+ static styles: import('lit').CSSResult;
112
+ private _scrollCtrl?;
113
+ /** Viewport corner where the FAB is anchored. Uses logical inline/block directions. */
114
+ position: 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start';
115
+ /** 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
+ scrollReveal: boolean;
117
+ constructor();
118
+ protected updated(changed: PropertyValues): void;
119
+ private _syncScrollFallback;
120
+ private _getThreshold;
121
+ protected render(): import('lit').TemplateResult<1>;
122
+ }
123
+ export default RCFab;
@@ -0,0 +1,2 @@
1
+ export declare const fabStyles: import('lit').CSSResult;
2
+ export default fabStyles;
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@rcarls/rc-fab",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.0.0-next-20260921045401",
7
+ "description": "Sticky floating action button modeled after the Material 3 Floating action button, wrapping a consumer-supplied button with scroll-aware visibility.",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-fab"
12
+ },
13
+ "homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-fab",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-fab.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-fab/src/index.d.ts",
24
+ "default": "./dist/rc-fab.js"
25
+ }
26
+ },
27
+ "./define": {
28
+ "import": {
29
+ "types": "./dist/types/packages/rc-fab/src/define.d.ts",
30
+ "default": "./dist/rc-fab-define.js"
31
+ }
32
+ }
33
+ },
34
+ "types": "./dist/types/packages/rc-fab/src/index.d.ts",
35
+ "sideEffects": [
36
+ "./dist/rc-fab-define.js"
37
+ ],
38
+ "customElements": "dist/custom-elements.json",
39
+ "scripts": {
40
+ "build": "tsc && vite build && cem analyze",
41
+ "cem:analyze": "cem analyze",
42
+ "preview": "vite preview",
43
+ "test:browser": "vitest --run",
44
+ "test:browser:chrome": "vitest --run --project=chromium",
45
+ "test:browser:firefox": "vitest --run --project=firefox"
46
+ },
47
+ "dependencies": {
48
+ "@rcarls/rc-common": "0.0.0-next-20260921045401"
49
+ },
50
+ "devDependencies": {
51
+ "@custom-elements-manifest/analyzer": "0.11.0",
52
+ "@vitest/browser-playwright": "4.1.5",
53
+ "lit": "^3.0.0",
54
+ "playwright": "^1.56.0",
55
+ "rollup": "^4.60.2",
56
+ "typescript": "~5.9.3",
57
+ "vite": "^7.1.7",
58
+ "vite-plugin-dts": "^4.5.4",
59
+ "vitest": "^4.0.6",
60
+ "vitest-browser-lit": "^1.0.1"
61
+ },
62
+ "peerDependencies": {
63
+ "lit": "^3.0.0"
64
+ }
65
+ }