@noxlovette/material 0.1.19 → 0.1.21

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
  */
@@ -19,7 +19,6 @@ and `ContextMenu`, which owns its own item list and is right-click triggered.
19
19
  import Icon from '../../../utils/icon/Icon.svelte';
20
20
  import Layer from '../../../utils/Layer.svelte';
21
21
  import { DropdownMenu } from 'bits-ui';
22
- import clsx from 'clsx';
23
22
  import { tv } from 'tailwind-variants';
24
23
 
25
24
  let {
@@ -49,6 +49,7 @@ SupportingPane provides a layout with a main content area and a supporting side
49
49
  <div class="top-3 right-3 z-10 hidden h-12 w-12 md:absolute md:block">
50
50
  <ButtonIcon
51
51
  triggerClass=""
52
+ type="button"
52
53
  variant="text"
53
54
  tooltipContent={supportingVisible ? 'Скрыть панель' : 'Показать панель'}
54
55
  size="sm"
@@ -15,7 +15,7 @@ Side sheets are supplementary surfaces which are anchored to the edge of the scr
15
15
  <div class="flex items-center justify-between p-6">
16
16
  <span class="md-sys-typescale-title-large text-md-sys-color-on-surface-variant">{headline}</span
17
17
  >
18
- <ButtonIcon variant="text" iconProps={{ name: 'close' }} onclick={close} />
18
+ <ButtonIcon type="button" variant="text" iconProps={{ name: 'close' }} onclick={close} />
19
19
  </div>
20
20
  {@render children()}
21
21
  </div>
@@ -104,11 +104,11 @@ DateField is a text field that allows users to enter a date or pick it from a ca
104
104
  class="text-md-sys-color-on-surface-variant z-10 flex w-full items-center justify-between pb-7.5"
105
105
  >
106
106
  <DatePicker.PrevButton>
107
- <ButtonIcon iconProps={{ name: 'chevron_left' }} />
107
+ <ButtonIcon type="button" iconProps={{ name: 'chevron_left' }} />
108
108
  </DatePicker.PrevButton>
109
109
  <DatePicker.Heading class="md-sys-typescale-label-large" />
110
110
  <DatePicker.NextButton>
111
- <ButtonIcon iconProps={{ name: 'chevron_right' }} />
111
+ <ButtonIcon type="button" iconProps={{ name: 'chevron_right' }} />
112
112
  </DatePicker.NextButton>
113
113
  </DatePicker.Header>
114
114
 
@@ -153,11 +153,11 @@ segment inputs and a shared calendar popover.
153
153
  class="text-md-sys-color-on-surface-variant z-10 flex w-full items-center justify-between pb-7.5"
154
154
  >
155
155
  <DateRangePicker.PrevButton>
156
- <ButtonIcon iconProps={{ name: 'chevron_left' }} />
156
+ <ButtonIcon type="button" iconProps={{ name: 'chevron_left' }} />
157
157
  </DateRangePicker.PrevButton>
158
158
  <DateRangePicker.Heading class="md-sys-typescale-label-large" />
159
159
  <DateRangePicker.NextButton>
160
- <ButtonIcon iconProps={{ name: 'chevron_right' }} />
160
+ <ButtonIcon type="button" iconProps={{ name: 'chevron_right' }} />
161
161
  </DateRangePicker.NextButton>
162
162
  </DateRangePicker.Header>
163
163
 
@@ -68,7 +68,11 @@ Text fields allow users to enter and edit text.
68
68
  {#if trailingIcon}
69
69
  {@render trailingIcon()}
70
70
  {:else if trailingIconProps}
71
- <ButtonIcon onclick={trailingOnClick} class={cls.trailingIcon()} iconProps={trailingIconProps}
71
+ <ButtonIcon
72
+ type="button"
73
+ onclick={trailingOnClick}
74
+ class={cls.trailingIcon()}
75
+ iconProps={trailingIconProps}
72
76
  ></ButtonIcon>
73
77
  {/if}
74
78
  </div>
@@ -58,7 +58,7 @@ It follows the Material 3 design for time input pickers.
58
58
  </TimeField.Root>
59
59
 
60
60
  <div class={cls.buttonDiv()}>
61
- <ButtonIcon iconProps={{ name: 'schedule' }} />
61
+ <ButtonIcon type="button" iconProps={{ name: 'schedule' }} />
62
62
  <div>
63
63
  <Button variant="text" type="button" onclick={() => close()}>Отмена</Button>
64
64
  <Button
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noxlovette/material",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {