noph-ui 0.44.0 → 0.45.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.
Files changed (135) hide show
  1. package/README.md +10 -1
  2. package/dist/animation.d.ts +17 -0
  3. package/dist/animation.js +48 -0
  4. package/dist/app-bar/AppBar.svelte +280 -0
  5. package/dist/app-bar/AppBar.svelte.d.ts +4 -0
  6. package/dist/app-bar/index.d.ts +1 -0
  7. package/dist/app-bar/index.js +1 -0
  8. package/dist/app-bar/types.d.ts +35 -0
  9. package/dist/app-bar/types.js +1 -0
  10. package/dist/autocomplete/AutoComplete.svelte +10 -11
  11. package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
  12. package/dist/autocomplete/types.d.ts +1 -1
  13. package/dist/badge/Badge.svelte +6 -1
  14. package/dist/badge/types.d.ts +0 -1
  15. package/dist/button/Button.svelte +5 -15
  16. package/dist/button/IconButton.svelte +5 -15
  17. package/dist/button/SegmentedButton.svelte +15 -1
  18. package/dist/button/SplitButton.svelte +92 -0
  19. package/dist/button/SplitButton.svelte.d.ts +4 -0
  20. package/dist/button/index.d.ts +1 -0
  21. package/dist/button/index.js +1 -0
  22. package/dist/button/types.d.ts +23 -4
  23. package/dist/card/types.d.ts +1 -1
  24. package/dist/checkbox/Checkbox.svelte +49 -58
  25. package/dist/chip/AssistChip.svelte +5 -5
  26. package/dist/chip/ChipSet.svelte +3 -11
  27. package/dist/chip/FilterChip.svelte +35 -27
  28. package/dist/chip/InputChip.svelte +13 -2
  29. package/dist/chip/SuggestionChip.svelte +29 -0
  30. package/dist/chip/SuggestionChip.svelte.d.ts +4 -0
  31. package/dist/chip/index.d.ts +1 -0
  32. package/dist/chip/index.js +1 -0
  33. package/dist/chip/types.d.ts +24 -9
  34. package/dist/date-picker/Calendar.svelte +1 -1
  35. package/dist/date-picker/DatePickerDialog.svelte +30 -8
  36. package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -1
  37. package/dist/date-picker/DateRangePicker.svelte +45 -10
  38. package/dist/date-picker/DateRangePicker.svelte.d.ts +4 -1
  39. package/dist/date-picker/DockedDatePicker.svelte +18 -4
  40. package/dist/date-picker/DockedDatePicker.svelte.d.ts +4 -1
  41. package/dist/date-picker/DockedDateTimePicker.svelte +18 -4
  42. package/dist/date-picker/DockedDateTimePicker.svelte.d.ts +4 -1
  43. package/dist/date-picker/SelectionList.svelte +6 -32
  44. package/dist/date-picker/SelectionList.svelte.d.ts +2 -12
  45. package/dist/date-picker/TimeColumn.svelte +4 -29
  46. package/dist/date-picker/exitVisibility.svelte.d.ts +10 -0
  47. package/dist/date-picker/exitVisibility.svelte.js +30 -0
  48. package/dist/date-picker/optionList.d.ts +16 -0
  49. package/dist/date-picker/optionList.js +43 -0
  50. package/dist/date-picker/types.d.ts +11 -4
  51. package/dist/dialog/Dialog.svelte +77 -68
  52. package/dist/dialog/Dialog.svelte.d.ts +3 -3
  53. package/dist/dialog/types.d.ts +5 -3
  54. package/dist/fab/ExtendedFab.svelte +281 -0
  55. package/dist/fab/ExtendedFab.svelte.d.ts +5 -0
  56. package/dist/fab/Fab.svelte +239 -0
  57. package/dist/fab/Fab.svelte.d.ts +5 -0
  58. package/dist/fab/FabMenu.svelte +255 -0
  59. package/dist/fab/FabMenu.svelte.d.ts +7 -0
  60. package/dist/fab/index.d.ts +3 -0
  61. package/dist/fab/index.js +3 -0
  62. package/dist/fab/types.d.ts +58 -0
  63. package/dist/fab/types.js +1 -0
  64. package/dist/icons/ArrowBackIcon.svelte +13 -0
  65. package/dist/icons/ArrowBackIcon.svelte.d.ts +7 -0
  66. package/dist/icons/ChevronDownIcon.svelte +3 -0
  67. package/dist/icons/ChevronDownIcon.svelte.d.ts +26 -0
  68. package/dist/icons/SearchIcon.svelte +15 -0
  69. package/dist/icons/SearchIcon.svelte.d.ts +7 -0
  70. package/dist/icons/index.d.ts +3 -0
  71. package/dist/icons/index.js +3 -0
  72. package/dist/index.d.ts +6 -0
  73. package/dist/index.js +6 -0
  74. package/dist/internal/focus-ring.css +12 -0
  75. package/dist/menu/Menu.svelte +12 -7
  76. package/dist/menu/Menu.svelte.d.ts +2 -2
  77. package/dist/menu/types.d.ts +0 -2
  78. package/dist/navigation-bar/NavigationBar.svelte +66 -0
  79. package/dist/navigation-bar/NavigationBar.svelte.d.ts +4 -0
  80. package/dist/navigation-bar/NavigationBarItem.svelte +261 -0
  81. package/dist/navigation-bar/NavigationBarItem.svelte.d.ts +5 -0
  82. package/dist/navigation-bar/index.d.ts +2 -0
  83. package/dist/navigation-bar/index.js +2 -0
  84. package/dist/navigation-bar/types.d.ts +29 -0
  85. package/dist/navigation-bar/types.js +1 -0
  86. package/dist/navigation-drawer/NavigationDrawer.svelte +128 -76
  87. package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +4 -1
  88. package/dist/navigation-drawer/NavigationDrawerItem.svelte +10 -3
  89. package/dist/navigation-drawer/types.d.ts +6 -1
  90. package/dist/navigation-rail/NavigationRailItem.svelte +16 -0
  91. package/dist/navigation-rail/types.d.ts +3 -0
  92. package/dist/popover.svelte.d.ts +14 -0
  93. package/dist/popover.svelte.js +31 -0
  94. package/dist/press.svelte.d.ts +19 -0
  95. package/dist/press.svelte.js +45 -0
  96. package/dist/radio/Radio.svelte +26 -7
  97. package/dist/radio/types.d.ts +3 -0
  98. package/dist/search/Search.svelte +472 -0
  99. package/dist/search/Search.svelte.d.ts +5 -0
  100. package/dist/search/index.d.ts +1 -0
  101. package/dist/search/index.js +1 -0
  102. package/dist/search/types.d.ts +39 -0
  103. package/dist/search/types.js +1 -0
  104. package/dist/select/Select.svelte +48 -87
  105. package/dist/sheet/Sheet.svelte +260 -0
  106. package/dist/sheet/Sheet.svelte.d.ts +7 -0
  107. package/dist/sheet/index.d.ts +1 -0
  108. package/dist/sheet/index.js +1 -0
  109. package/dist/sheet/types.d.ts +19 -0
  110. package/dist/sheet/types.js +1 -0
  111. package/dist/slider/Slider.svelte +154 -30
  112. package/dist/slider/types.d.ts +3 -0
  113. package/dist/snackbar/Snackbar.svelte +48 -37
  114. package/dist/snackbar/Snackbar.svelte.d.ts +2 -2
  115. package/dist/snackbar/types.d.ts +3 -2
  116. package/dist/switch/Switch.svelte +51 -20
  117. package/dist/switch/types.d.ts +3 -0
  118. package/dist/tabs/Tab.svelte +8 -12
  119. package/dist/tabs/Tabs.svelte +3 -3
  120. package/dist/text-field/TextField.svelte +2 -2
  121. package/dist/toolbar/Toolbar.svelte +133 -0
  122. package/dist/toolbar/Toolbar.svelte.d.ts +4 -0
  123. package/dist/toolbar/index.d.ts +1 -0
  124. package/dist/toolbar/index.js +1 -0
  125. package/dist/toolbar/types.d.ts +15 -0
  126. package/dist/toolbar/types.js +1 -0
  127. package/dist/tooltip/RichTooltip.svelte +25 -12
  128. package/dist/tooltip/RichTooltip.svelte.d.ts +2 -2
  129. package/dist/tooltip/Tooltip.svelte +22 -11
  130. package/dist/tooltip/Tooltip.svelte.d.ts +4 -1
  131. package/dist/types.d.ts +6 -0
  132. package/dist/types.js +6 -0
  133. package/package.json +13 -13
  134. package/dist/date-picker/enterAnimation.d.ts +0 -1
  135. package/dist/date-picker/enterAnimation.js +0 -10
@@ -0,0 +1,239 @@
1
+ <script lang="ts">
2
+ import '../internal/focus-ring.css'
3
+ import { pressMorph } from '../press.svelte.js'
4
+ import Ripple from '../ripple/Ripple.svelte'
5
+ import Tooltip from '../tooltip/Tooltip.svelte'
6
+ import type { HTMLButtonAttributes } from 'svelte/elements'
7
+ import type { FabProps } from './types.js'
8
+
9
+ let {
10
+ variant = 'primary-container',
11
+ size = 's',
12
+ shape = 'round',
13
+ lowered = false,
14
+ disabled = false,
15
+ icon,
16
+ label,
17
+ element = $bindable(),
18
+ ...attributes
19
+ }: FabProps = $props()
20
+
21
+ const uid = $props.id()
22
+
23
+ const isLink = $derived(attributes.href != null && !disabled)
24
+ const tooltipId = $derived(label && !disabled ? uid : undefined)
25
+
26
+ const morph = pressMorph()
27
+ morph.supersededBy(() => shape)
28
+
29
+ const handlePress = () => {
30
+ if (disabled) return
31
+ morph.press()
32
+ }
33
+
34
+ const classes = $derived([
35
+ 'np-fab',
36
+ 'np-focus-ring',
37
+ `np-fab-${variant}`,
38
+ size,
39
+ shape,
40
+ lowered && 'np-fab-lowered',
41
+ morph.pressed && 'pressed',
42
+ attributes.class,
43
+ ])
44
+ </script>
45
+
46
+ {#snippet content()}
47
+ {#if !disabled}
48
+ <Ripple forElement={element} />
49
+ {/if}
50
+ {#if icon}
51
+ <span class="np-fab-icon">{@render icon()}</span>
52
+ {/if}
53
+ {/snippet}
54
+
55
+ {#if isLink}
56
+ <a
57
+ {...attributes}
58
+ onclick={(event) => {
59
+ handlePress()
60
+ attributes.onclick?.(event)
61
+ }}
62
+ bind:this={element}
63
+ aria-describedby={tooltipId ?? attributes['aria-describedby']}
64
+ interestfor={tooltipId ?? attributes['interestfor']}
65
+ aria-label={label || attributes['aria-label']}
66
+ class={classes}
67
+ >
68
+ {@render content()}
69
+ </a>
70
+ {:else}
71
+ <button
72
+ {...attributes as HTMLButtonAttributes}
73
+ onclick={(event) => {
74
+ handlePress()
75
+ attributes.onclick?.(event)
76
+ }}
77
+ bind:this={element}
78
+ aria-describedby={tooltipId ?? attributes['aria-describedby']}
79
+ interestfor={tooltipId ?? attributes['interestfor']}
80
+ aria-label={label || attributes['aria-label']}
81
+ type={(attributes['type'] as HTMLButtonAttributes['type']) ?? 'button'}
82
+ {disabled}
83
+ class={classes}
84
+ >
85
+ {@render content()}
86
+ </button>
87
+ {/if}
88
+
89
+ {#if tooltipId}
90
+ <Tooltip id={tooltipId}>{label}</Tooltip>
91
+ {/if}
92
+
93
+ <style>
94
+ .np-fab {
95
+ position: relative;
96
+ display: inline-flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ box-sizing: border-box;
100
+ border-width: 0;
101
+ cursor: pointer;
102
+ font: inherit;
103
+ text-decoration: none;
104
+ -webkit-tap-highlight-color: transparent;
105
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-3));
106
+ background-color: var(--np-fab-container-color, var(--_container-color));
107
+ color: var(--np-fab-icon-color, var(--_icon-color));
108
+ --np-ripple-hover-color: var(--np-fab-icon-color, var(--_icon-color));
109
+ --np-ripple-pressed-color: var(--np-fab-icon-color, var(--_icon-color));
110
+ }
111
+
112
+ /*
113
+ * `round` is fully rounded and `square` is the smaller radius, the same way Button reads.
114
+ * The FAB box is always square, so `50%` renders identically to `--np-shape-corner-full`
115
+ * (9999px) at rest. It matters for the pressed transition below: animating `border-radius`
116
+ * from a huge fixed value spends nearly the whole transition on radii that still render as
117
+ * fully round, so the morph never becomes visible during a normal, brief click.
118
+ */
119
+ .round {
120
+ border-radius: var(--np-fab-shape, 50%);
121
+ }
122
+ .square {
123
+ border-radius: var(--np-fab-shape, var(--_square-radius));
124
+ }
125
+
126
+ /*
127
+ * A press morphs the corner towards the other shape. `:active` alone drops the moment the
128
+ * pointer lifts, which on a normal click is long before the morph has played, so the short
129
+ * `pressed` class holds the shape until the whole round trip is visible, the way Button does
130
+ * it. Both are gated on the motion preference, since without the transition below this is
131
+ * only an instant snap.
132
+ */
133
+ @media (prefers-reduced-motion: no-preference) {
134
+ .round:not(:disabled):is(:active, .pressed) {
135
+ border-radius: var(--np-fab-pressed-shape, var(--_square-radius));
136
+ }
137
+ .square:not(:disabled):is(:active, .pressed) {
138
+ border-radius: var(--np-fab-pressed-shape, 50%);
139
+ }
140
+ }
141
+
142
+ @media (prefers-reduced-motion: no-preference) {
143
+ .np-fab {
144
+ transition:
145
+ border-radius var(--np-motion-expressive-default-spatial),
146
+ box-shadow var(--np-motion-expressive-fast-effects),
147
+ background-color var(--np-motion-expressive-fast-effects);
148
+ }
149
+ }
150
+
151
+ /*
152
+ * M3's three recommended sizes, matching the extended FAB's. The 40px FAB it used to also
153
+ * define is gone: M3 Expressive no longer recommends it. Each square radius is the shape token
154
+ * that goes with the size, large, large-increased and extra-large.
155
+ */
156
+ .s {
157
+ width: 3.5rem;
158
+ height: 3.5rem;
159
+ --_square-radius: 1rem;
160
+ --np-icon-size: 1.5rem;
161
+ }
162
+ .m {
163
+ width: 5rem;
164
+ height: 5rem;
165
+ --_square-radius: 1.25rem;
166
+ --np-icon-size: 1.75rem;
167
+ }
168
+ .l {
169
+ width: 6rem;
170
+ height: 6rem;
171
+ --_square-radius: 1.75rem;
172
+ --np-icon-size: 2.25rem;
173
+ }
174
+
175
+ /*
176
+ * M3's colour styles, named after the tokens they map to. The tone styles are the stronger
177
+ * pair, the container styles the softer one. The surface style M3 Expressive dropped is gone.
178
+ */
179
+ .np-fab-primary {
180
+ --_container-color: var(--np-color-primary);
181
+ --_icon-color: var(--np-color-on-primary);
182
+ }
183
+ .np-fab-secondary {
184
+ --_container-color: var(--np-color-secondary);
185
+ --_icon-color: var(--np-color-on-secondary);
186
+ }
187
+ .np-fab-tertiary {
188
+ --_container-color: var(--np-color-tertiary);
189
+ --_icon-color: var(--np-color-on-tertiary);
190
+ }
191
+ .np-fab-primary-container {
192
+ --_container-color: var(--np-color-primary-container);
193
+ --_icon-color: var(--np-color-on-primary-container);
194
+ }
195
+ .np-fab-secondary-container {
196
+ --_container-color: var(--np-color-secondary-container);
197
+ --_icon-color: var(--np-color-on-secondary-container);
198
+ }
199
+ .np-fab-tertiary-container {
200
+ --_container-color: var(--np-color-tertiary-container);
201
+ --_icon-color: var(--np-color-on-tertiary-container);
202
+ }
203
+
204
+ .np-fab-lowered {
205
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-1));
206
+ }
207
+
208
+ @media (hover: hover) {
209
+ .np-fab:not(:disabled):hover {
210
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-3));
211
+ background-color: color-mix(
212
+ in srgb,
213
+ var(--np-fab-icon-color, var(--_icon-color)) 8%,
214
+ var(--np-fab-container-color, var(--_container-color))
215
+ );
216
+ }
217
+ .np-fab-lowered:not(:disabled):hover {
218
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-2));
219
+ }
220
+ }
221
+
222
+ .np-fab:disabled {
223
+ cursor: default;
224
+ box-shadow: none;
225
+ background-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
226
+ color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
227
+ }
228
+
229
+ /*
230
+ * The icon is never a hit target, matching `Button`'s. Otherwise swapping it out from under
231
+ * the pointer, which `FabMenu`'s trigger does when it opens, moves the pointer to a different
232
+ * element; the browser hands the button fresh interest on the way and reopens its tooltip.
233
+ */
234
+ .np-fab-icon {
235
+ display: flex;
236
+ fill: currentColor;
237
+ pointer-events: none;
238
+ }
239
+ </style>
@@ -0,0 +1,5 @@
1
+ import '../internal/focus-ring.css';
2
+ import type { FabProps } from './types.ts';
3
+ declare const Fab: import("svelte").Component<FabProps, {}, "element">;
4
+ type Fab = ReturnType<typeof Fab>;
5
+ export default Fab;
@@ -0,0 +1,255 @@
1
+ <script lang="ts">
2
+ import CloseIcon from '../icons/CloseIcon.svelte'
3
+ import { arrowKeyNav, rovingTabindex } from '../keyboard-nav.js'
4
+ import { popoverController, syncOpenEffect } from '../popover.svelte.js'
5
+ import { PRESS_DURATION } from '../press.svelte.js'
6
+ import { onMount } from 'svelte'
7
+ import { MediaQuery } from 'svelte/reactivity'
8
+ import Fab from './Fab.svelte'
9
+ import type { FabMenuProps } from './types.js'
10
+
11
+ let {
12
+ label = 'Actions',
13
+ icon,
14
+ closeIcon,
15
+ children,
16
+ variant = 'primary-container',
17
+ size = 's',
18
+ placement = 'block-start',
19
+ closeOnSelect = true,
20
+ open = $bindable(false),
21
+ element = $bindable(),
22
+ ...attributes
23
+ }: FabMenuProps = $props()
24
+
25
+ const uid = $props.id()
26
+ const menuId = `np-fab-menu-${uid}`
27
+ /*
28
+ * One anchor name per instance. A fixed name in the stylesheet is shared by every FabMenu on
29
+ * the page, and a duplicated anchor name resolves to the last one in tree order, so every menu
30
+ * would open on the last trigger.
31
+ */
32
+ const anchorName = `--np-fab-menu-${uid}`
33
+
34
+ let menuElement: HTMLDivElement | undefined = $state()
35
+ const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)', false)
36
+
37
+ // Declaring `role="menu"` means owing the keyboard model that goes with it: one tab stop, the
38
+ // arrows moving between actions. The same attachments the toolbar and the bars use.
39
+ const ITEMS = 'button, a[href], [role="menuitem"]'
40
+ const attach = rovingTabindex(ITEMS)
41
+ const arrowHandler = $derived(
42
+ arrowKeyNav(ITEMS, placement.startsWith('inline') ? 'horizontal' : 'vertical'),
43
+ )
44
+
45
+ const controller = popoverController(() => menuElement)
46
+
47
+ export const show = () => controller.show()
48
+
49
+ export const close = () => {
50
+ clearTimeout(closeTimeout)
51
+ controller.close()
52
+ }
53
+
54
+ /*
55
+ * An item runs its own press animation on click, the corner morph a Button or a FAB does. The
56
+ * menu is a popover, so closing it means `display: none`, which would cut that off before a
57
+ * frame of it is seen. Holding the close for the length of the press lets the item finish.
58
+ * Matched to Button's own timer, and skipped entirely when there is no animation to wait for.
59
+ */
60
+ let closeTimeout: ReturnType<typeof setTimeout>
61
+
62
+ const closeAfterPress = () => {
63
+ if (reducedMotion.current) {
64
+ close()
65
+ return
66
+ }
67
+ clearTimeout(closeTimeout)
68
+ closeTimeout = setTimeout(close, PRESS_DURATION)
69
+ }
70
+
71
+ onMount(() => {
72
+ return () => {
73
+ clearTimeout(closeTimeout)
74
+ }
75
+ })
76
+
77
+ syncOpenEffect(
78
+ () => menuElement,
79
+ () => open,
80
+ show,
81
+ close,
82
+ )
83
+ </script>
84
+
85
+ <!--
86
+ Both icons stay mounted and turn into each other. Swapping one for the other cannot animate:
87
+ the icon arriving has no previous state to move from, so it can only appear.
88
+ -->
89
+ {#snippet triggerIcon()}
90
+ <span class="np-fab-menu-icons" aria-hidden="true">
91
+ <span class="np-fab-menu-icon" class:np-fab-menu-icon-away={open}>
92
+ {#if icon}{@render icon()}{/if}
93
+ </span>
94
+ <span class="np-fab-menu-icon" class:np-fab-menu-icon-away={!open}>
95
+ {#if closeIcon}{@render closeIcon()}{:else}<CloseIcon />{/if}
96
+ </span>
97
+ </span>
98
+ {/snippet}
99
+
100
+ <div
101
+ {...attributes}
102
+ bind:this={element}
103
+ style="anchor-name: {anchorName}; {attributes.style ?? ''}"
104
+ class={['np-fab-menu', `np-fab-menu-${placement}`, attributes.class]}
105
+ >
106
+ <!--
107
+ The tooltip is dropped while the menu is open, and the name given directly instead. A
108
+ tooltip is a `hint` popover, which the browser closes when the menu's `auto` popover opens,
109
+ and the trigger still holds interest at that point, so it would come straight back and sit
110
+ over the open menu. There is nothing to hint at anyway once the actions are on screen.
111
+ -->
112
+ <Fab
113
+ {variant}
114
+ {size}
115
+ {label}
116
+ shape={open ? 'square' : 'round'}
117
+ command="toggle-popover"
118
+ commandfor={menuId}
119
+ aria-haspopup="menu"
120
+ aria-expanded={open}
121
+ class="np-fab-menu-trigger"
122
+ icon={triggerIcon}
123
+ />
124
+
125
+ <div
126
+ bind:this={menuElement}
127
+ id={menuId}
128
+ style="position-anchor: {anchorName}"
129
+ popover="auto"
130
+ role="menu"
131
+ tabindex="-1"
132
+ aria-label={label}
133
+ class="np-fab-menu-list"
134
+ {@attach attach}
135
+ onkeydown={arrowHandler}
136
+ onclick={(event) => {
137
+ // Picking an action closes the menu. `closeOnSelect={false}` keeps it open for a menu
138
+ // whose items toggle something instead of navigating away.
139
+ if (!closeOnSelect) return
140
+ const target = event.target as HTMLElement
141
+ if (target.closest(ITEMS)) closeAfterPress()
142
+ }}
143
+ ontoggle={(event) => {
144
+ open = event.newState === 'open'
145
+ }}
146
+ >
147
+ {@render children?.()}
148
+ </div>
149
+ </div>
150
+
151
+ <style>
152
+ .np-fab-menu {
153
+ position: relative;
154
+ display: inline-flex;
155
+ }
156
+
157
+ /* One grid cell holds both icons, so they sit on top of each other rather than side by side. */
158
+ .np-fab-menu-icons {
159
+ display: grid;
160
+ }
161
+ .np-fab-menu-icon {
162
+ grid-area: 1 / 1;
163
+ display: flex;
164
+ }
165
+ .np-fab-menu-icon-away {
166
+ opacity: 0;
167
+ }
168
+
169
+ @media (prefers-reduced-motion: no-preference) {
170
+ .np-fab-menu-icon {
171
+ transition:
172
+ opacity var(--np-motion-expressive-fast-effects),
173
+ rotate var(--np-motion-expressive-default-spatial);
174
+ }
175
+ /*
176
+ * Both turn the same way, so the pair reads as one icon rotating rather than two crossing
177
+ * over. The close icon spends its hidden time at -45deg, where an X sits square like the
178
+ * plus it replaces, so opening rotates the plus away and the X into place behind it.
179
+ */
180
+ .np-fab-menu-icon-away {
181
+ rotate: -45deg;
182
+ }
183
+ }
184
+
185
+ .np-fab-menu-list {
186
+ position: absolute;
187
+ margin: 0;
188
+ padding: 0;
189
+ border: none;
190
+ background: none;
191
+ flex-direction: column;
192
+ align-items: flex-end;
193
+ gap: 0.5rem;
194
+ overflow: visible;
195
+ }
196
+
197
+ /*
198
+ * `display` only while open. Setting it on the base rule would beat the user agent's
199
+ * `display: none` for a closed popover, because author styles win over user agent styles
200
+ * whatever the specificity, leaving an invisible sheet over the page that swallows clicks.
201
+ */
202
+ .np-fab-menu-list:popover-open {
203
+ display: flex;
204
+ }
205
+
206
+ /* Which side the items fan out to, and which end they stack from. */
207
+ .np-fab-menu-block-start .np-fab-menu-list {
208
+ position-area: block-start;
209
+ margin-block-end: 0.75rem;
210
+ flex-direction: column-reverse;
211
+ }
212
+ .np-fab-menu-block-end .np-fab-menu-list {
213
+ position-area: block-end;
214
+ margin-block-start: 0.75rem;
215
+ }
216
+ .np-fab-menu-inline-start .np-fab-menu-list {
217
+ position-area: inline-start;
218
+ margin-inline-end: 0.75rem;
219
+ flex-direction: row-reverse;
220
+ align-items: center;
221
+ }
222
+ .np-fab-menu-inline-end .np-fab-menu-list {
223
+ position-area: inline-end;
224
+ margin-inline-start: 0.75rem;
225
+ flex-direction: row;
226
+ align-items: center;
227
+ }
228
+
229
+ /*
230
+ * The items rise into place one after another. Keyframes rather than a transition: a popover
231
+ * goes from `display: none` to shown in one step, and a transition has no start value to run
232
+ * from, so the items would simply appear. An animation runs when the element becomes displayed.
233
+ *
234
+ * The whole stagger fits one window, `--_stagger-window`, however many actions there are. That
235
+ * is what `sibling-count()` buys over a fixed step: eight actions land in the same time two do,
236
+ * each one just following the last a little sooner, rather than the menu taking four times as
237
+ * long to finish arriving. Item one starts at 0ms, so the action nearest the trigger moves
238
+ * first, and the last one starts as the window closes.
239
+ */
240
+ @media (prefers-reduced-motion: no-preference) {
241
+ .np-fab-menu-list:popover-open > :global(*) {
242
+ --_stagger-window: 210ms;
243
+ --_delay: calc((sibling-index() - 1) / sibling-count() * var(--_stagger-window));
244
+ animation: np-fab-menu-item-in var(--np-motion-expressive-default-spatial) both;
245
+ animation-delay: var(--_delay);
246
+ }
247
+ }
248
+
249
+ @keyframes np-fab-menu-item-in {
250
+ from {
251
+ opacity: 0;
252
+ translate: 0 0.5rem;
253
+ }
254
+ }
255
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { FabMenuProps } from './types.ts';
2
+ declare const FabMenu: import("svelte").Component<FabMenuProps, {
3
+ show: () => void;
4
+ close: () => void;
5
+ }, "element" | "open">;
6
+ type FabMenu = ReturnType<typeof FabMenu>;
7
+ export default FabMenu;
@@ -0,0 +1,3 @@
1
+ export { default as ExtendedFab } from './ExtendedFab.svelte';
2
+ export { default as Fab } from './Fab.svelte';
3
+ export { default as FabMenu } from './FabMenu.svelte';
@@ -0,0 +1,3 @@
1
+ export { default as ExtendedFab } from './ExtendedFab.svelte';
2
+ export { default as Fab } from './Fab.svelte';
3
+ export { default as FabMenu } from './FabMenu.svelte';
@@ -0,0 +1,58 @@
1
+ import type { ButtonElement } from '../button/types.js';
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
4
+ export type FabElement = ButtonElement;
5
+ interface BaseFabProps extends HTMLAttributes<FabElement>, Omit<HTMLButtonAttributes, keyof HTMLAttributes<HTMLButtonElement> | 'type'>, Omit<HTMLAnchorAttributes, keyof HTMLAttributes<HTMLAnchorElement> | 'type'> {
6
+ element?: FabElement;
7
+ /**
8
+ * Colour style, named after the tokens it maps to. The three tone styles are the stronger
9
+ * pair, the three container styles the softer one. M3 Expressive no longer recommends a
10
+ * surface-coloured FAB, so there is no `surface`.
11
+ */
12
+ variant?: 'primary' | 'secondary' | 'tertiary' | 'primary-container' | 'secondary-container' | 'tertiary-container';
13
+ /**
14
+ * 56, 80 and 96 pixels, the same three `Fab` and `ExtendedFab` share. `s` is M3's baseline FAB
15
+ * and the default. The 40 pixel FAB is not here: M3 Expressive no longer recommends it.
16
+ */
17
+ size?: 's' | 'm' | 'l';
18
+ shape?: 'round' | 'square';
19
+ lowered?: boolean;
20
+ disabled?: boolean | null;
21
+ type?: 'submit' | 'reset' | 'button' | (string & {}) | null;
22
+ }
23
+ export interface FabProps extends BaseFabProps {
24
+ icon?: Snippet;
25
+ /**
26
+ * A FAB shows no text, so its accessible name has to come from somewhere. Sets both
27
+ * `aria-label` and a plain tooltip, the way `Button` treats `title`.
28
+ */
29
+ label?: string;
30
+ }
31
+ export interface ExtendedFabProps extends BaseFabProps {
32
+ icon?: Snippet;
33
+ /** Visible text. An extended FAB is named by it, so it needs no `aria-label`. */
34
+ label?: string;
35
+ /**
36
+ * Collapses to an icon-only FAB, animating the width. Drive it from a scroll or a media query
37
+ * to get M3's collapsing FAB.
38
+ */
39
+ collapsed?: boolean;
40
+ }
41
+ export interface FabMenuProps extends HTMLAttributes<HTMLDivElement> {
42
+ /** Names the trigger and the menu. The trigger shows no text. */
43
+ label?: string;
44
+ /** Trigger icon while the menu is closed. */
45
+ icon?: Snippet;
46
+ /** Trigger icon while the menu is open. Defaults to a close icon. */
47
+ closeIcon?: Snippet;
48
+ variant?: FabProps['variant'];
49
+ size?: FabProps['size'];
50
+ /** Which side the items fan out to. */
51
+ placement?: 'block-start' | 'block-end' | 'inline-start' | 'inline-end';
52
+ /** Bindable. Whether the menu is open. */
53
+ open?: boolean;
54
+ /** Closes the menu when an action is picked, which is what M3 does. */
55
+ closeOnSelect?: boolean;
56
+ element?: HTMLDivElement;
57
+ }
58
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ let {
3
+ height = 24,
4
+ width = 24,
5
+ }: {
6
+ height?: number
7
+ width?: number
8
+ } = $props()
9
+ </script>
10
+
11
+ <svg xmlns="http://www.w3.org/2000/svg" {height} viewBox="0 -960 960 960" {width}
12
+ ><path d="m313-440 224 224-57 56-320-320 320-320 57 56-224 224h487v80H313Z" /></svg
13
+ >
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ height?: number;
3
+ width?: number;
4
+ };
5
+ declare const ArrowBackIcon: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type ArrowBackIcon = ReturnType<typeof ArrowBackIcon>;
7
+ export default ArrowBackIcon;
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"
2
+ ><path d="M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z" /></svg
3
+ >
@@ -0,0 +1,26 @@
1
+ export default ChevronDownIcon;
2
+ type ChevronDownIcon = SvelteComponent<{
3
+ [x: string]: never;
4
+ }, {
5
+ [evt: string]: CustomEvent<any>;
6
+ }, {}> & {
7
+ $$bindings?: string | undefined;
8
+ };
9
+ declare const ChevronDownIcon: $$__sveltets_2_IsomorphicComponent<{
10
+ [x: string]: never;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}, {}, string>;
14
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
+ new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
16
+ $$bindings?: Bindings;
17
+ } & Exports;
18
+ (internal: unknown, props: {
19
+ $$events?: Events;
20
+ $$slots?: Slots;
21
+ }): Exports & {
22
+ $set?: any;
23
+ $on?: any;
24
+ };
25
+ z_$$bindings?: Bindings;
26
+ }
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ let {
3
+ height = 24,
4
+ width = 24,
5
+ }: {
6
+ height?: number
7
+ width?: number
8
+ } = $props()
9
+ </script>
10
+
11
+ <svg xmlns="http://www.w3.org/2000/svg" {height} viewBox="0 -960 960 960" {width}
12
+ ><path
13
+ d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"
14
+ /></svg
15
+ >
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ height?: number;
3
+ width?: number;
4
+ };
5
+ declare const SearchIcon: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SearchIcon = ReturnType<typeof SearchIcon>;
7
+ export default SearchIcon;
@@ -1,8 +1,11 @@
1
+ export { default as ArrowBackIcon } from './ArrowBackIcon.svelte';
1
2
  export { default as ArrowDropDownIcon } from './ArrowDropDownIcon.svelte';
2
3
  export { default as CalendarToday } from './CalendarToday.svelte';
3
4
  export { default as CheckIcon } from './CheckIcon.svelte';
5
+ export { default as ChevronDownIcon } from './ChevronDownIcon.svelte';
4
6
  export { default as ChevronLeftIcon } from './ChevronLeftIcon.svelte';
5
7
  export { default as ChevronRightIcon } from './ChevronRightIcon.svelte';
6
8
  export { default as CloseIcon } from './CloseIcon.svelte';
7
9
  export { default as EditCalendarIcon } from './EditCalendarIcon.svelte';
8
10
  export { default as Icon } from './Icon.svelte';
11
+ export { default as SearchIcon } from './SearchIcon.svelte';
@@ -1,8 +1,11 @@
1
+ export { default as ArrowBackIcon } from './ArrowBackIcon.svelte';
1
2
  export { default as ArrowDropDownIcon } from './ArrowDropDownIcon.svelte';
2
3
  export { default as CalendarToday } from './CalendarToday.svelte';
3
4
  export { default as CheckIcon } from './CheckIcon.svelte';
5
+ export { default as ChevronDownIcon } from './ChevronDownIcon.svelte';
4
6
  export { default as ChevronLeftIcon } from './ChevronLeftIcon.svelte';
5
7
  export { default as ChevronRightIcon } from './ChevronRightIcon.svelte';
6
8
  export { default as CloseIcon } from './CloseIcon.svelte';
7
9
  export { default as EditCalendarIcon } from './EditCalendarIcon.svelte';
8
10
  export { default as Icon } from './Icon.svelte';
11
+ export { default as SearchIcon } from './SearchIcon.svelte';