@noxlovette/material 0.1.19 → 0.1.20

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.
@@ -1,4 +1,5 @@
1
1
  export * from './clickOutside.js';
2
2
  export * from './floating.js';
3
3
  export * from './keyboard.js';
4
+ export * from './portal.js';
4
5
  export * from './positionFloating.js';
@@ -1,4 +1,5 @@
1
1
  export * from './clickOutside.js';
2
2
  export * from './floating.js';
3
3
  export * from './keyboard.js';
4
+ export * from './portal.js';
4
5
  export * from './positionFloating.js';
@@ -0,0 +1,3 @@
1
+ export declare function portal(node: HTMLElement, target?: HTMLElement): {
2
+ destroy(): void;
3
+ };
@@ -0,0 +1,8 @@
1
+ export function portal(node, target = document.body) {
2
+ target.appendChild(node);
3
+ return {
4
+ destroy() {
5
+ node.remove();
6
+ }
7
+ };
8
+ }
@@ -35,6 +35,7 @@ Floating action buttons (FABs) help people take primary actions.
35
35
  const { base, icon, label: labelClass } = $derived(fab({ size, config, expanded }));
36
36
 
37
37
  let showMenu = $state(false);
38
+ let wrapperEl = $state<HTMLElement | null>(null);
38
39
 
39
40
  const btnCls = $derived(
40
41
  base({
@@ -58,7 +59,7 @@ Floating action buttons (FABs) help people take primary actions.
58
59
  }
59
60
  </script>
60
61
 
61
- <div class="contents" use:clickOutside={() => (showMenu = false)}>
62
+ <div class="contents" bind:this={wrapperEl} use:clickOutside={() => (showMenu = false)}>
62
63
  <Button.Root
63
64
  {disabled}
64
65
  {formaction}
@@ -76,8 +77,8 @@ Floating action buttons (FABs) help people take primary actions.
76
77
  {/if}
77
78
  </Button.Root>
78
79
 
79
- {#if withMenu && showMenu}
80
- <FABMenu>
80
+ {#if withMenu && showMenu && wrapperEl?.firstElementChild}
81
+ <FABMenu anchorEl={wrapperEl.firstElementChild as HTMLElement}>
81
82
  {@render children?.()}
82
83
  </FABMenu>
83
84
  {/if}
@@ -5,69 +5,31 @@ Should be used with `FABMenuItem` as children.
5
5
  -->
6
6
  <script lang="ts">
7
7
  import clsx from 'clsx';
8
+ import { flip } from '@floating-ui/dom';
8
9
  import { fabMenu } from './theme.js';
9
10
  import type { FABMenuProps } from './types.js';
10
11
  import { enterExit } from '../../animation/enterExit.js';
11
- import { easeEmphasizedDecel } from '../../animation/easing.js';
12
+ import { easeEmphasizedAccel, easeEmphasizedDecel } from '../../animation/easing.js';
13
+ import { floating, portal } from '../../actions/index.js';
12
14
 
13
- let { children, class: className, ...restProps }: FABMenuProps = $props();
15
+ let { children, class: className, anchorEl, ...restProps }: FABMenuProps = $props();
14
16
 
15
17
  const { base } = fabMenu();
16
-
17
- const margin = 8;
18
- const gap = 8;
19
- let coords = $state({
20
- x: 0,
21
- y: 0,
22
- originX: 'right',
23
- originY: 'bottom',
24
- maxHeight: 0
25
- });
26
18
  </script>
27
19
 
28
20
  <ul
29
21
  data-cy="m3-fab-menu"
30
22
  {...restProps}
31
- class={base({
32
- class: clsx(className, 'fab-menu overflow-y-auto')
33
- })}
23
+ class={base({ class: clsx(className, 'fab-menu overflow-y-auto') })}
34
24
  in:enterExit={{ duration: 300, easing: easeEmphasizedDecel, mode: 'scale' }}
35
- style:translate={`${coords.x}px ${coords.y}px`}
36
- style:max-height={coords.maxHeight ? `${coords.maxHeight}px` : undefined}
37
- style:--enter-exit-origin={`${coords.originY} ${coords.originX}`}
38
- {@attach (menu) => {
39
- const anchorEl = menu.previousElementSibling as HTMLElement;
40
- if (!anchorEl) return;
41
-
42
- const anchorRect = anchorEl.getBoundingClientRect();
43
- const rect = menu.getBoundingClientRect();
44
-
45
- const spaceAbove = anchorRect.top - gap - margin;
46
- const spaceBelow = window.innerHeight - margin - (anchorRect.bottom + gap);
47
- const placeAbove = spaceAbove >= rect.height || spaceAbove >= Math.max(0, spaceBelow);
48
-
49
- let maxHeight = placeAbove ? spaceAbove : spaceBelow;
50
- maxHeight = Math.max(0, Math.min(rect.height, maxHeight));
51
-
52
- const targetTop = placeAbove ? anchorRect.top - gap - maxHeight : anchorRect.bottom + gap;
53
-
54
- let targetLeft = anchorRect.right - rect.width;
55
- if (targetLeft + rect.width > window.innerWidth - margin) {
56
- targetLeft = window.innerWidth - margin - rect.width;
57
- }
58
- if (targetLeft < margin) targetLeft = margin;
59
-
60
- let clampedTop = targetTop;
61
- if (clampedTop < margin) clampedTop = margin;
62
- if (clampedTop + maxHeight > window.innerHeight - margin) {
63
- clampedTop = window.innerHeight - margin - maxHeight;
64
- }
65
-
66
- const originY = placeAbove ? 'bottom' : 'top';
67
- const originX =
68
- anchorRect.left + anchorRect.width / 2 < targetLeft + rect.width / 2 ? 'left' : 'right';
69
-
70
- coords = { x: targetLeft, y: clampedTop, originX, originY, maxHeight };
25
+ out:enterExit={{ duration: 200, easing: easeEmphasizedAccel, mode: 'scale' }}
26
+ use:portal
27
+ use:floating={{
28
+ reference: anchorEl,
29
+ strategy: 'fixed',
30
+ placement: 'top-end',
31
+ offsetPx: 8,
32
+ middleware: [flip()]
71
33
  }}
72
34
  >
73
35
  {@render children()}
@@ -75,6 +37,6 @@ Should be used with `FABMenuItem` as children.
75
37
 
76
38
  <style>
77
39
  .fab-menu {
78
- transform-origin: var(--enter-exit-origin, bottom right);
40
+ transform-origin: bottom right;
79
41
  }
80
42
  </style>
@@ -27,8 +27,6 @@ Uses Material 3 selection button styling.
27
27
 
28
28
  const { base, icon } = $derived(
29
29
  button({
30
- variant: 'filled',
31
- color: 'default',
32
30
  usage: 'selection',
33
31
  shape,
34
32
  size,
@@ -66,7 +66,7 @@ export const button = tv({
66
66
  {
67
67
  usage: 'selection',
68
68
  selected: false,
69
- class: { base: 'bg-md-sys-color-surface-container' }
69
+ class: { base: 'bg-md-sys-color-surface-container-high text-md-sys-color-on-surface-variant' }
70
70
  },
71
71
  {
72
72
  variant: 'text',
@@ -275,7 +275,7 @@ export const buttonIcon = tv({
275
275
  {
276
276
  variation: 'toggle',
277
277
  selected: false,
278
- class: { base: 'bg-md-sys-color-surface-container' }
278
+ class: { base: 'bg-md-sys-color-surface-container-high text-md-sys-color-on-surface-variant' }
279
279
  },
280
280
  {
281
281
  variant: 'text',
@@ -474,7 +474,7 @@ export const fab = tv({
474
474
  });
475
475
  export const fabMenu = tv({
476
476
  slots: {
477
- base: 'gap-2 pb-2 flex flex-col fixed left-0 top-0 z-[9999] overflow-visible p-1'
477
+ base: 'gap-2 pb-2 flex flex-col z-[9999] overflow-visible p-1'
478
478
  }
479
479
  });
480
480
  export const fabMenuItem = tv({
@@ -158,6 +158,8 @@ export type FABProps = FABVariants & AnchorButtonAttributes & {
158
158
  children?: Snippet;
159
159
  };
160
160
  export type FABMenuProps = HTMLAttributes<HTMLUListElement> & {
161
+ /** The FAB button element to position the menu relative to. */
162
+ anchorEl: HTMLElement;
161
163
  /**
162
164
  * The menu items. Use `FABMenuItem` components.
163
165
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noxlovette/material",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {