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
@@ -101,11 +101,11 @@ export interface TimeColumnProps extends Omit<HTMLAttributes<HTMLDivElement>, 'o
101
101
  value?: number;
102
102
  onselect?: (value: number) => void;
103
103
  }
104
- export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps, MonthStepperLabelProps {
104
+ export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps, MonthStepperLabelProps {
105
105
  value?: ISODate | null;
106
106
  displayMonth?: ISODate;
107
107
  open?: boolean;
108
- element?: HTMLDivElement;
108
+ element?: HTMLDialogElement;
109
109
  name?: string;
110
110
  form?: string;
111
111
  title?: string;
@@ -113,6 +113,9 @@ export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDivElemen
113
113
  modeToggle?: boolean;
114
114
  label?: string;
115
115
  supportingText?: string;
116
+ issues?: {
117
+ message: string;
118
+ }[];
116
119
  selectYearLabel?: string;
117
120
  calendarModeLabel?: string;
118
121
  inputModeLabel?: string;
@@ -120,11 +123,15 @@ export interface DatePickerDialogProps extends Omit<HTMLAttributes<HTMLDivElemen
120
123
  onconfirm?: (value: ISODate | undefined) => void;
121
124
  oncancel?: () => void;
122
125
  }
123
- export interface DateRangePickerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps {
126
+ export interface DateRangePickerProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'onchange'>, DatePickerLocaleProps, DatePickerRangeProps, DatePickerCommonLabelProps {
124
127
  value?: DateRange;
125
128
  open?: boolean;
126
- element?: HTMLDivElement;
129
+ element?: HTMLDialogElement;
127
130
  title?: string;
131
+ headline?: string;
132
+ name?: string;
133
+ endName?: string;
134
+ form?: string;
128
135
  startLabel?: string;
129
136
  endLabel?: string;
130
137
  onchange?: (value: DateRange) => void;
@@ -1,9 +1,11 @@
1
1
  <script lang="ts">
2
2
  import Divider from '../divider/Divider.svelte'
3
+ import { syncOpenEffect } from '../popover.svelte.js'
3
4
  import type { DialogProps } from './types.js'
4
5
 
5
6
  let {
6
7
  element = $bindable(),
8
+ open = $bindable(),
7
9
  quick = false,
8
10
  children,
9
11
  headline,
@@ -12,82 +14,63 @@
12
14
  actions,
13
15
  divider,
14
16
  ontoggle,
15
- // Naming attributes belong on the element that carries role="dialog", not on the popover
16
- // container the rest of the attributes are spread onto.
17
- 'aria-label': ariaLabel,
18
17
  'aria-labelledby': ariaLabelledby,
19
18
  ...attributes
20
19
  }: DialogProps = $props()
21
20
 
22
21
  const uid = $props.id()
23
22
 
24
- let dialogEl: HTMLDivElement | undefined = $state()
25
- let previouslyFocused: HTMLElement | undefined
26
- let inerted: HTMLElement[] = []
27
-
28
- export const showPopover = () => {
29
- element?.showPopover()
30
- }
31
-
32
- export const hidePopover = () => {
33
- element?.hidePopover()
23
+ /*
24
+ * A modal dialog can only be opened from script or from a `command` button; `<dialog open>` on
25
+ * its own is the non-modal kind, with no focus trap and no backdrop.
26
+ */
27
+ export const show = () => {
28
+ if (element && !element.open) element.showModal()
34
29
  }
35
30
 
36
- const trapFocus = () => {
37
- if (!element) return
38
- previouslyFocused = document.activeElement as HTMLElement | undefined
39
- for (let node: HTMLElement | null = element; node && node !== document.body;) {
40
- const parent: HTMLElement | null = node.parentElement
41
- if (!parent) break
42
- for (const sibling of parent.children) {
43
- if (sibling === node || !(sibling instanceof HTMLElement) || sibling.inert) continue
44
- sibling.inert = true
45
- inerted.push(sibling)
46
- }
47
- node = parent
48
- }
49
- dialogEl?.focus()
31
+ export const close = () => {
32
+ element?.close()
50
33
  }
51
34
 
52
- const releaseFocus = () => {
53
- for (const node of inerted) node.inert = false
54
- inerted = []
55
- previouslyFocused?.focus?.()
56
- previouslyFocused = undefined
57
- }
35
+ syncOpenEffect(
36
+ () => element,
37
+ () => open,
38
+ show,
39
+ close,
40
+ )
58
41
  </script>
59
42
 
60
- <div
43
+ <!--
44
+ A `dialog`, not a popover. `showModal()` keeps everything outside inert for as long as the dialog
45
+ is open, including anything added to the page while it is open, and hands focus back on close.
46
+ `::backdrop` is the scrim and `closedby="any"` the click-outside-to-close. Where `closedby` is
47
+ unsupported the click handler below covers it: a click on the backdrop is reported against the
48
+ dialog itself.
49
+ -->
50
+ <dialog
61
51
  bind:this={element}
62
52
  {...attributes}
63
- popover="auto"
53
+ tabindex="-1"
54
+ closedby="any"
55
+ aria-labelledby={ariaLabelledby ?? (headline ? `${uid}-dialog-headline` : undefined)}
56
+ aria-describedby={supportingText ? `${uid}-dialog-supporting-text` : undefined}
57
+ class={['np-dialog-container', !quick && 'np-animate', attributes.class]}
64
58
  ontoggle={(event) => {
65
- if (event.newState === 'open') {
66
- trapFocus()
67
- } else {
68
- releaseFocus()
69
- }
59
+ open = event.newState === 'open'
60
+ /*
61
+ * Without this the browser autofocuses the first focusable descendant, one of the actions,
62
+ * which can be a destructive one. Focus the dialog itself instead, so nothing is focused
63
+ * that the user didn't choose to interact with.
64
+ */
65
+ if (event.newState === 'open') element?.focus()
70
66
  ontoggle?.(event)
71
67
  }}
72
- class={['np-dialog-container', !quick && 'np-animate', attributes.class]}
68
+ onclick={(event) => {
69
+ attributes.onclick?.(event)
70
+ if (event.target === element) close()
71
+ }}
73
72
  >
74
- <div
75
- role="none"
76
- class="np-backdrop"
77
- onclick={() => {
78
- hidePopover()
79
- }}
80
- ></div>
81
- <div
82
- bind:this={dialogEl}
83
- class="np-dialog"
84
- role="dialog"
85
- tabindex="-1"
86
- aria-modal="true"
87
- aria-label={ariaLabel}
88
- aria-labelledby={ariaLabelledby ?? (headline ? `${uid}-dialog-headline` : undefined)}
89
- aria-describedby={supportingText ? `${uid}-dialog-supporting-text` : undefined}
90
- >
73
+ <div class="np-dialog">
91
74
  {#if icon}
92
75
  <div class="np-dialog-icon">
93
76
  {@render icon()}
@@ -119,19 +102,36 @@
119
102
  </div>
120
103
  {/if}
121
104
  </div>
122
- </div>
105
+ </dialog>
123
106
 
124
107
  <style>
125
108
  .np-dialog-container {
126
109
  background: transparent;
127
110
  border: none;
111
+ /* Focus lands here on open only to stay off the actions, not as something to show. */
112
+ outline: none;
128
113
  margin: auto;
129
114
  padding: var(--np-dialog-inset, 2rem 1rem);
130
115
  box-sizing: border-box;
131
116
  min-width: var(--np-dialog-container-min-width, 19.5rem);
132
117
  width: var(--np-dialog-container-width, 37rem);
133
118
  max-width: 100%;
119
+ max-height: none;
120
+ overflow: visible;
121
+ color: var(--np-color-on-surface);
122
+ }
123
+ /* A closed dialog is `display: none` from the user agent, which the enter transition needs. */
124
+ .np-dialog-container:not([open]) {
125
+ display: none;
134
126
  }
127
+ /*
128
+ * The exit transition holds the dialog in the top layer after it has closed, where it would go on
129
+ * taking the clicks meant for the page behind it.
130
+ */
131
+ .np-animate:not([open]) {
132
+ pointer-events: none;
133
+ }
134
+
135
135
  .np-dialog {
136
136
  border: 0;
137
137
  background-color: var(--np-dialog-container-color, var(--np-color-surface));
@@ -154,27 +154,36 @@
154
154
  flex-direction: column;
155
155
  }
156
156
 
157
- .np-animate[popover] {
157
+ .np-dialog-container::backdrop {
158
+ background-color: var(--np-color-scrim);
159
+ opacity: 0.32;
160
+ }
161
+
162
+ .np-animate {
158
163
  transition:
159
164
  opacity var(--np-motion-expressive-slow-effects),
160
165
  display var(--np-motion-expressive-slow-effects) allow-discrete,
161
166
  overlay var(--np-motion-expressive-slow-effects) allow-discrete;
162
167
  opacity: 0;
163
168
  }
164
- .np-animate[popover]:popover-open {
169
+ .np-animate[open] {
165
170
  opacity: 1;
166
171
  @starting-style {
167
172
  opacity: 0;
168
173
  }
169
174
  }
170
- .np-dialog-container[popover] .np-backdrop {
171
- inset: 0;
172
- position: fixed;
173
- background-color: var(--np-color-scrim);
174
- opacity: 0.32;
175
+ .np-animate::backdrop {
176
+ transition:
177
+ opacity var(--np-motion-expressive-slow-effects),
178
+ display var(--np-motion-expressive-slow-effects) allow-discrete,
179
+ overlay var(--np-motion-expressive-slow-effects) allow-discrete;
180
+ opacity: 0;
175
181
  }
176
- .np-dialog:focus-visible {
177
- outline: none;
182
+ .np-animate[open]::backdrop {
183
+ opacity: 0.32;
184
+ @starting-style {
185
+ opacity: 0;
186
+ }
178
187
  }
179
188
 
180
189
  .np-dialog-icon {
@@ -1,7 +1,7 @@
1
1
  import type { DialogProps } from './types.ts';
2
2
  declare const Dialog: import("svelte").Component<DialogProps, {
3
- showPopover: () => void;
4
- hidePopover: () => void;
5
- }, "element">;
3
+ show: () => void;
4
+ close: () => void;
5
+ }, "element" | "open">;
6
6
  type Dialog = ReturnType<typeof Dialog>;
7
7
  export default Dialog;
@@ -1,11 +1,13 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { HTMLAttributes } from 'svelte/elements';
3
- export interface DialogProps extends Omit<HTMLAttributes<HTMLDivElement>, 'popover'> {
2
+ import type { HTMLDialogAttributes } from 'svelte/elements';
3
+ export interface DialogProps extends Omit<HTMLDialogAttributes, 'open'> {
4
4
  icon?: Snippet;
5
5
  headline?: string;
6
6
  supportingText?: string;
7
7
  divider?: boolean;
8
8
  actions?: Snippet;
9
9
  quick?: boolean;
10
- element?: HTMLElement;
10
+ element?: HTMLDialogElement;
11
+ /** Bindable. Whether the dialog is showing. */
12
+ open?: boolean;
11
13
  }
@@ -0,0 +1,281 @@
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 { ExtendedFabProps } from './types.js'
8
+
9
+ let {
10
+ variant = 'primary-container',
11
+ size = 's',
12
+ shape = 'round',
13
+ lowered = false,
14
+ disabled = false,
15
+ collapsed = false,
16
+ icon,
17
+ label,
18
+ element = $bindable(),
19
+ ...attributes
20
+ }: ExtendedFabProps = $props()
21
+
22
+ const uid = $props.id()
23
+
24
+ const isLink = $derived(attributes.href != null && !disabled)
25
+ // Collapsed, the text is hidden, so the label has to be announced some other way.
26
+ const tooltipId = $derived(collapsed && label && !disabled ? uid : undefined)
27
+
28
+ const morph = pressMorph()
29
+ morph.supersededBy(() => shape)
30
+
31
+ const handlePress = () => {
32
+ if (disabled) return
33
+ morph.press()
34
+ }
35
+
36
+ const classes = $derived([
37
+ 'np-extended-fab',
38
+ 'np-focus-ring',
39
+ `np-fab-${variant}`,
40
+ size,
41
+ shape,
42
+ lowered && 'np-fab-lowered',
43
+ collapsed && 'np-fab-collapsed',
44
+ morph.pressed && 'pressed',
45
+ attributes.class,
46
+ ])
47
+ </script>
48
+
49
+ {#snippet content()}
50
+ {#if !disabled}
51
+ <Ripple forElement={element} />
52
+ {/if}
53
+ {#if icon}
54
+ <span class="np-fab-icon">{@render icon()}</span>
55
+ {/if}
56
+ {#if label}
57
+ <span class="np-fab-label">{label}</span>
58
+ {/if}
59
+ {/snippet}
60
+
61
+ {#if isLink}
62
+ <a
63
+ {...attributes}
64
+ onclick={(event) => {
65
+ handlePress()
66
+ attributes.onclick?.(event)
67
+ }}
68
+ bind:this={element}
69
+ aria-describedby={tooltipId ?? attributes['aria-describedby']}
70
+ interestfor={tooltipId ?? attributes['interestfor']}
71
+ aria-label={collapsed ? label || attributes['aria-label'] : attributes['aria-label']}
72
+ class={classes}
73
+ >
74
+ {@render content()}
75
+ </a>
76
+ {:else}
77
+ <button
78
+ {...attributes as HTMLButtonAttributes}
79
+ onclick={(event) => {
80
+ handlePress()
81
+ attributes.onclick?.(event)
82
+ }}
83
+ bind:this={element}
84
+ aria-describedby={tooltipId ?? attributes['aria-describedby']}
85
+ interestfor={tooltipId ?? attributes['interestfor']}
86
+ aria-label={collapsed ? label || attributes['aria-label'] : attributes['aria-label']}
87
+ type={(attributes['type'] as HTMLButtonAttributes['type']) ?? 'button'}
88
+ {disabled}
89
+ class={classes}
90
+ >
91
+ {@render content()}
92
+ </button>
93
+ {/if}
94
+
95
+ {#if tooltipId}
96
+ <Tooltip id={tooltipId}>{label}</Tooltip>
97
+ {/if}
98
+
99
+ <style>
100
+ /*
101
+ * `interpolate-size` lets the intrinsic width animate, so collapsing is a plain CSS
102
+ * transition on `width` rather than a measured pixel value.
103
+ */
104
+ .np-extended-fab {
105
+ interpolate-size: allow-keywords;
106
+ position: relative;
107
+ display: inline-flex;
108
+ align-items: center;
109
+ justify-content: flex-start;
110
+ box-sizing: border-box;
111
+ border-width: 0;
112
+ cursor: pointer;
113
+ font: inherit;
114
+ font-weight: 500;
115
+ text-decoration: none;
116
+ white-space: nowrap;
117
+ overflow: hidden;
118
+ -webkit-tap-highlight-color: transparent;
119
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-3));
120
+ background-color: var(--np-fab-container-color, var(--_container-color));
121
+ color: var(--np-fab-icon-color, var(--_icon-color));
122
+ --np-ripple-hover-color: var(--np-fab-icon-color, var(--_icon-color));
123
+ --np-ripple-pressed-color: var(--np-fab-icon-color, var(--_icon-color));
124
+ width: auto;
125
+ min-width: var(--_height);
126
+ height: var(--_height);
127
+ gap: var(--_gap);
128
+ padding-inline: var(--_padding);
129
+ }
130
+
131
+ /*
132
+ * `calc(var(--_height) / 2)` renders identically to `--np-shape-corner-full` (9999px) at
133
+ * rest, since border-radius clamps an oversized radius down to exactly half the height
134
+ * anyway. It matters for the pressed transition: animating from a huge fixed value spends
135
+ * nearly the whole transition on radii that still render as fully round, so the morph never
136
+ * becomes visible during a normal, brief click. `50%` isn't a substitute here, since the box
137
+ * isn't square, it would resolve to an elliptical corner rather than a round pill end.
138
+ */
139
+ .round {
140
+ border-radius: var(--np-fab-shape, calc(var(--_height) / 2));
141
+ }
142
+ .square {
143
+ border-radius: var(--np-fab-shape, var(--_square-radius));
144
+ }
145
+ /*
146
+ * A press morphs the corner towards the other shape. `:active` alone drops the moment the
147
+ * pointer lifts, which on a normal click is long before the morph has played, so the short
148
+ * `pressed` class holds the shape until the whole round trip is visible, the way Button does
149
+ * it. Both are gated on the motion preference, since without the transition below this is
150
+ * only an instant snap.
151
+ */
152
+ @media (prefers-reduced-motion: no-preference) {
153
+ .round:not(:disabled):is(:active, .pressed) {
154
+ border-radius: var(--np-fab-pressed-shape, var(--_square-radius));
155
+ }
156
+ .square:not(:disabled):is(:active, .pressed) {
157
+ border-radius: var(--np-fab-pressed-shape, calc(var(--_height) / 2));
158
+ }
159
+ }
160
+
161
+ /* The same three sizes as `Fab`, which is what M3 Expressive aligned the two on. */
162
+ .s {
163
+ --_height: 3.5rem;
164
+ --_square-radius: 1rem;
165
+ --_gap: 0.75rem;
166
+ --_padding: 1rem;
167
+ font-size: 1rem;
168
+ --np-icon-size: 1.5rem;
169
+ }
170
+ .m {
171
+ --_height: 5rem;
172
+ --_square-radius: 1.25rem;
173
+ --_gap: 1rem;
174
+ --_padding: 1.5rem;
175
+ font-size: 1.375rem;
176
+ --np-icon-size: 1.75rem;
177
+ }
178
+ .l {
179
+ --_height: 6rem;
180
+ --_square-radius: 1.75rem;
181
+ --_gap: 1rem;
182
+ --_padding: 2rem;
183
+ font-size: 1.5rem;
184
+ --np-icon-size: 2.25rem;
185
+ }
186
+
187
+ /* Collapsed is the icon-only FAB: square box, no text, no padding around the label. */
188
+ .np-fab-collapsed {
189
+ width: var(--_height);
190
+ padding-inline: 0;
191
+ justify-content: center;
192
+ gap: 0;
193
+ }
194
+ .np-fab-collapsed .np-fab-label {
195
+ opacity: 0;
196
+ max-width: 0;
197
+ }
198
+
199
+ .np-fab-label {
200
+ max-width: 20rem;
201
+ overflow: hidden;
202
+ text-overflow: ellipsis;
203
+ }
204
+
205
+ @media (prefers-reduced-motion: no-preference) {
206
+ .np-extended-fab {
207
+ transition:
208
+ width var(--np-motion-expressive-default-spatial),
209
+ gap var(--np-motion-expressive-default-spatial),
210
+ padding-inline var(--np-motion-expressive-default-spatial),
211
+ border-radius var(--np-motion-expressive-default-spatial),
212
+ box-shadow var(--np-motion-expressive-fast-effects),
213
+ background-color var(--np-motion-expressive-fast-effects);
214
+ }
215
+ .np-fab-label {
216
+ transition:
217
+ opacity var(--np-motion-expressive-fast-effects),
218
+ max-width var(--np-motion-expressive-default-spatial);
219
+ }
220
+ }
221
+
222
+ /*
223
+ * M3's colour styles, named after the tokens they map to. The tone styles are the stronger
224
+ * pair, the container styles the softer one. The surface style M3 Expressive dropped is gone.
225
+ */
226
+ .np-fab-primary {
227
+ --_container-color: var(--np-color-primary);
228
+ --_icon-color: var(--np-color-on-primary);
229
+ }
230
+ .np-fab-secondary {
231
+ --_container-color: var(--np-color-secondary);
232
+ --_icon-color: var(--np-color-on-secondary);
233
+ }
234
+ .np-fab-tertiary {
235
+ --_container-color: var(--np-color-tertiary);
236
+ --_icon-color: var(--np-color-on-tertiary);
237
+ }
238
+ .np-fab-primary-container {
239
+ --_container-color: var(--np-color-primary-container);
240
+ --_icon-color: var(--np-color-on-primary-container);
241
+ }
242
+ .np-fab-secondary-container {
243
+ --_container-color: var(--np-color-secondary-container);
244
+ --_icon-color: var(--np-color-on-secondary-container);
245
+ }
246
+ .np-fab-tertiary-container {
247
+ --_container-color: var(--np-color-tertiary-container);
248
+ --_icon-color: var(--np-color-on-tertiary-container);
249
+ }
250
+
251
+ .np-fab-lowered {
252
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-1));
253
+ }
254
+
255
+ @media (hover: hover) {
256
+ .np-extended-fab:not(:disabled):hover {
257
+ background-color: color-mix(
258
+ in srgb,
259
+ var(--np-fab-icon-color, var(--_icon-color)) 8%,
260
+ var(--np-fab-container-color, var(--_container-color))
261
+ );
262
+ }
263
+ .np-fab-lowered:not(:disabled):hover {
264
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-2));
265
+ }
266
+ }
267
+
268
+ .np-extended-fab:disabled {
269
+ cursor: default;
270
+ box-shadow: none;
271
+ background-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
272
+ color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
273
+ }
274
+
275
+ .np-fab-icon {
276
+ display: flex;
277
+ flex: none;
278
+ fill: currentColor;
279
+ pointer-events: none;
280
+ }
281
+ </style>
@@ -0,0 +1,5 @@
1
+ import '../internal/focus-ring.css';
2
+ import type { ExtendedFabProps } from './types.ts';
3
+ declare const ExtendedFab: import("svelte").Component<ExtendedFabProps, {}, "element">;
4
+ type ExtendedFab = ReturnType<typeof ExtendedFab>;
5
+ export default ExtendedFab;