noph-ui 0.44.0 → 0.45.1

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 (136) hide show
  1. package/README.md +10 -1
  2. package/dist/animation.d.ts +2 -0
  3. package/dist/animation.js +33 -0
  4. package/dist/app-bar/AppBar.svelte +230 -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 +13 -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 +75 -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 +17 -4
  23. package/dist/card/types.d.ts +1 -1
  24. package/dist/checkbox/Checkbox.svelte +47 -58
  25. package/dist/chip/AssistChip.svelte +5 -5
  26. package/dist/chip/ChipSet.svelte +3 -11
  27. package/dist/chip/FilterChip.svelte +33 -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 +16 -9
  34. package/dist/date-picker/Calendar.svelte +1 -1
  35. package/dist/date-picker/DatePickerDialog.svelte +23 -11
  36. package/dist/date-picker/DatePickerDialog.svelte.d.ts +4 -1
  37. package/dist/date-picker/DateRangePicker.svelte +40 -10
  38. package/dist/date-picker/DateRangePicker.svelte.d.ts +4 -1
  39. package/dist/date-picker/DockedDatePicker.svelte +13 -10
  40. package/dist/date-picker/DockedDatePicker.svelte.d.ts +4 -1
  41. package/dist/date-picker/DockedDateTimePicker.svelte +13 -7
  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 +5 -0
  47. package/dist/date-picker/exitVisibility.svelte.js +23 -0
  48. package/dist/date-picker/optionList.d.ts +4 -0
  49. package/dist/date-picker/optionList.js +31 -0
  50. package/dist/date-picker/types.d.ts +11 -4
  51. package/dist/dialog/Dialog.svelte +55 -68
  52. package/dist/dialog/Dialog.svelte.d.ts +3 -3
  53. package/dist/dialog/types.d.ts +4 -3
  54. package/dist/fab/ExtendedFab.svelte +255 -0
  55. package/dist/fab/ExtendedFab.svelte.d.ts +5 -0
  56. package/dist/fab/Fab.svelte +211 -0
  57. package/dist/fab/Fab.svelte.d.ts +5 -0
  58. package/dist/fab/FabMenu.svelte +207 -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 +34 -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/loading-indicator/shapes.js +0 -7
  76. package/dist/menu/Menu.svelte +12 -7
  77. package/dist/menu/Menu.svelte.d.ts +2 -2
  78. package/dist/menu/types.d.ts +0 -2
  79. package/dist/navigation-bar/NavigationBar.svelte +59 -0
  80. package/dist/navigation-bar/NavigationBar.svelte.d.ts +4 -0
  81. package/dist/navigation-bar/NavigationBarItem.svelte +239 -0
  82. package/dist/navigation-bar/NavigationBarItem.svelte.d.ts +5 -0
  83. package/dist/navigation-bar/index.d.ts +2 -0
  84. package/dist/navigation-bar/index.js +2 -0
  85. package/dist/navigation-bar/types.d.ts +19 -0
  86. package/dist/navigation-bar/types.js +1 -0
  87. package/dist/navigation-drawer/NavigationDrawer.svelte +107 -76
  88. package/dist/navigation-drawer/NavigationDrawer.svelte.d.ts +4 -1
  89. package/dist/navigation-drawer/NavigationDrawerItem.svelte +10 -3
  90. package/dist/navigation-drawer/types.d.ts +3 -1
  91. package/dist/navigation-rail/NavigationRailItem.svelte +16 -0
  92. package/dist/navigation-rail/types.d.ts +3 -0
  93. package/dist/popover.svelte.d.ts +5 -0
  94. package/dist/popover.svelte.js +22 -0
  95. package/dist/press.svelte.d.ts +6 -0
  96. package/dist/press.svelte.js +32 -0
  97. package/dist/radio/Radio.svelte +26 -7
  98. package/dist/radio/types.d.ts +3 -0
  99. package/dist/search/Search.svelte +391 -0
  100. package/dist/search/Search.svelte.d.ts +5 -0
  101. package/dist/search/index.d.ts +1 -0
  102. package/dist/search/index.js +1 -0
  103. package/dist/search/types.d.ts +19 -0
  104. package/dist/search/types.js +1 -0
  105. package/dist/select/Select.svelte +48 -87
  106. package/dist/sheet/Sheet.svelte +235 -0
  107. package/dist/sheet/Sheet.svelte.d.ts +7 -0
  108. package/dist/sheet/index.d.ts +1 -0
  109. package/dist/sheet/index.js +1 -0
  110. package/dist/sheet/types.d.ts +11 -0
  111. package/dist/sheet/types.js +1 -0
  112. package/dist/slider/Slider.svelte +118 -30
  113. package/dist/slider/types.d.ts +3 -0
  114. package/dist/snackbar/Snackbar.svelte +32 -35
  115. package/dist/snackbar/Snackbar.svelte.d.ts +2 -2
  116. package/dist/snackbar/types.d.ts +3 -2
  117. package/dist/switch/Switch.svelte +51 -20
  118. package/dist/switch/types.d.ts +3 -0
  119. package/dist/tabs/Tab.svelte +4 -12
  120. package/dist/tabs/Tabs.svelte +5 -5
  121. package/dist/text-field/TextField.svelte +2 -4
  122. package/dist/toolbar/Toolbar.svelte +121 -0
  123. package/dist/toolbar/Toolbar.svelte.d.ts +4 -0
  124. package/dist/toolbar/index.d.ts +1 -0
  125. package/dist/toolbar/index.js +1 -0
  126. package/dist/toolbar/types.d.ts +8 -0
  127. package/dist/toolbar/types.js +1 -0
  128. package/dist/tooltip/RichTooltip.svelte +18 -12
  129. package/dist/tooltip/RichTooltip.svelte.d.ts +2 -2
  130. package/dist/tooltip/Tooltip.svelte +22 -11
  131. package/dist/tooltip/Tooltip.svelte.d.ts +4 -1
  132. package/dist/types.d.ts +6 -0
  133. package/dist/types.js +6 -0
  134. package/package.json +13 -13
  135. package/dist/date-picker/enterAnimation.d.ts +0 -1
  136. package/dist/date-picker/enterAnimation.js +0 -10
@@ -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,47 @@
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
+ export const show = () => {
24
+ if (element && !element.open) element.showModal()
34
25
  }
35
26
 
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()
27
+ export const close = () => {
28
+ element?.close()
50
29
  }
51
30
 
52
- const releaseFocus = () => {
53
- for (const node of inerted) node.inert = false
54
- inerted = []
55
- previouslyFocused?.focus?.()
56
- previouslyFocused = undefined
57
- }
31
+ syncOpenEffect(
32
+ () => element,
33
+ () => open,
34
+ show,
35
+ close,
36
+ )
58
37
  </script>
59
38
 
60
- <div
39
+ <dialog
61
40
  bind:this={element}
62
41
  {...attributes}
63
- popover="auto"
42
+ tabindex="-1"
43
+ closedby="any"
44
+ aria-labelledby={ariaLabelledby ?? (headline ? `${uid}-dialog-headline` : undefined)}
45
+ aria-describedby={supportingText ? `${uid}-dialog-supporting-text` : undefined}
46
+ class={['np-dialog-container', !quick && 'np-animate', attributes.class]}
64
47
  ontoggle={(event) => {
65
- if (event.newState === 'open') {
66
- trapFocus()
67
- } else {
68
- releaseFocus()
69
- }
48
+ open = event.newState === 'open'
49
+ if (event.newState === 'open') element?.focus()
70
50
  ontoggle?.(event)
71
51
  }}
72
- class={['np-dialog-container', !quick && 'np-animate', attributes.class]}
52
+ onclick={(event) => {
53
+ attributes.onclick?.(event)
54
+ if (event.target === element) close()
55
+ }}
73
56
  >
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
- >
57
+ <div class="np-dialog">
91
58
  {#if icon}
92
59
  <div class="np-dialog-icon">
93
60
  {@render icon()}
@@ -119,19 +86,30 @@
119
86
  </div>
120
87
  {/if}
121
88
  </div>
122
- </div>
89
+ </dialog>
123
90
 
124
91
  <style>
125
92
  .np-dialog-container {
126
93
  background: transparent;
127
94
  border: none;
95
+ outline: none;
128
96
  margin: auto;
129
97
  padding: var(--np-dialog-inset, 2rem 1rem);
130
98
  box-sizing: border-box;
131
99
  min-width: var(--np-dialog-container-min-width, 19.5rem);
132
100
  width: var(--np-dialog-container-width, 37rem);
133
101
  max-width: 100%;
102
+ max-height: none;
103
+ overflow: visible;
104
+ color: var(--np-color-on-surface);
105
+ }
106
+ .np-dialog-container:not([open]) {
107
+ display: none;
134
108
  }
109
+ .np-animate:not([open]) {
110
+ pointer-events: none;
111
+ }
112
+
135
113
  .np-dialog {
136
114
  border: 0;
137
115
  background-color: var(--np-dialog-container-color, var(--np-color-surface));
@@ -154,27 +132,36 @@
154
132
  flex-direction: column;
155
133
  }
156
134
 
157
- .np-animate[popover] {
135
+ .np-dialog-container::backdrop {
136
+ background-color: var(--np-color-scrim);
137
+ opacity: 0.32;
138
+ }
139
+
140
+ .np-animate {
158
141
  transition:
159
142
  opacity var(--np-motion-expressive-slow-effects),
160
143
  display var(--np-motion-expressive-slow-effects) allow-discrete,
161
144
  overlay var(--np-motion-expressive-slow-effects) allow-discrete;
162
145
  opacity: 0;
163
146
  }
164
- .np-animate[popover]:popover-open {
147
+ .np-animate[open] {
165
148
  opacity: 1;
166
149
  @starting-style {
167
150
  opacity: 0;
168
151
  }
169
152
  }
170
- .np-dialog-container[popover] .np-backdrop {
171
- inset: 0;
172
- position: fixed;
173
- background-color: var(--np-color-scrim);
174
- opacity: 0.32;
153
+ .np-animate::backdrop {
154
+ transition:
155
+ opacity var(--np-motion-expressive-slow-effects),
156
+ display var(--np-motion-expressive-slow-effects) allow-discrete,
157
+ overlay var(--np-motion-expressive-slow-effects) allow-discrete;
158
+ opacity: 0;
175
159
  }
176
- .np-dialog:focus-visible {
177
- outline: none;
160
+ .np-animate[open]::backdrop {
161
+ opacity: 0.32;
162
+ @starting-style {
163
+ opacity: 0;
164
+ }
178
165
  }
179
166
 
180
167
  .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,12 @@
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
+ open?: boolean;
11
12
  }
@@ -0,0 +1,255 @@
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
+ const tooltipId = $derived(collapsed && label && !disabled ? uid : undefined)
26
+
27
+ const morph = pressMorph()
28
+ morph.supersededBy(() => shape)
29
+
30
+ const handlePress = () => {
31
+ if (disabled) return
32
+ morph.press()
33
+ }
34
+
35
+ const classes = $derived([
36
+ 'np-extended-fab',
37
+ 'np-focus-ring',
38
+ `np-fab-${variant}`,
39
+ size,
40
+ shape,
41
+ lowered && 'np-fab-lowered',
42
+ collapsed && 'np-fab-collapsed',
43
+ morph.pressed && 'pressed',
44
+ attributes.class,
45
+ ])
46
+ </script>
47
+
48
+ {#snippet content()}
49
+ {#if !disabled}
50
+ <Ripple forElement={element} />
51
+ {/if}
52
+ {#if icon}
53
+ <span class="np-fab-icon">{@render icon()}</span>
54
+ {/if}
55
+ {#if label}
56
+ <span class="np-fab-label">{label}</span>
57
+ {/if}
58
+ {/snippet}
59
+
60
+ {#if isLink}
61
+ <a
62
+ {...attributes}
63
+ onclick={(event) => {
64
+ handlePress()
65
+ attributes.onclick?.(event)
66
+ }}
67
+ bind:this={element}
68
+ aria-describedby={tooltipId ?? attributes['aria-describedby']}
69
+ interestfor={tooltipId ?? attributes['interestfor']}
70
+ aria-label={collapsed ? label || attributes['aria-label'] : attributes['aria-label']}
71
+ class={classes}
72
+ >
73
+ {@render content()}
74
+ </a>
75
+ {:else}
76
+ <button
77
+ {...attributes as HTMLButtonAttributes}
78
+ onclick={(event) => {
79
+ handlePress()
80
+ attributes.onclick?.(event)
81
+ }}
82
+ bind:this={element}
83
+ aria-describedby={tooltipId ?? attributes['aria-describedby']}
84
+ interestfor={tooltipId ?? attributes['interestfor']}
85
+ aria-label={collapsed ? label || attributes['aria-label'] : attributes['aria-label']}
86
+ type={(attributes['type'] as HTMLButtonAttributes['type']) ?? 'button'}
87
+ {disabled}
88
+ class={classes}
89
+ >
90
+ {@render content()}
91
+ </button>
92
+ {/if}
93
+
94
+ {#if tooltipId}
95
+ <Tooltip id={tooltipId}>{label}</Tooltip>
96
+ {/if}
97
+
98
+ <style>
99
+ .np-extended-fab {
100
+ interpolate-size: allow-keywords;
101
+ position: relative;
102
+ display: inline-flex;
103
+ align-items: center;
104
+ justify-content: flex-start;
105
+ box-sizing: border-box;
106
+ border-width: 0;
107
+ cursor: pointer;
108
+ font: inherit;
109
+ font-weight: 500;
110
+ text-decoration: none;
111
+ white-space: nowrap;
112
+ overflow: hidden;
113
+ -webkit-tap-highlight-color: transparent;
114
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-3));
115
+ background-color: var(--np-fab-container-color, var(--_container-color));
116
+ color: var(--np-fab-icon-color, var(--_icon-color));
117
+ --np-ripple-hover-color: var(--np-fab-icon-color, var(--_icon-color));
118
+ --np-ripple-pressed-color: var(--np-fab-icon-color, var(--_icon-color));
119
+ width: auto;
120
+ min-width: var(--_height);
121
+ height: var(--_height);
122
+ gap: var(--_gap);
123
+ padding-inline: var(--_padding);
124
+ }
125
+
126
+ .round {
127
+ border-radius: var(--np-fab-shape, calc(var(--_height) / 2));
128
+ }
129
+ .square {
130
+ border-radius: var(--np-fab-shape, var(--_square-radius));
131
+ }
132
+ @media (prefers-reduced-motion: no-preference) {
133
+ .round:not(:disabled):is(:active, .pressed) {
134
+ border-radius: var(--np-fab-pressed-shape, var(--_square-radius));
135
+ }
136
+ .square:not(:disabled):is(:active, .pressed) {
137
+ border-radius: var(--np-fab-pressed-shape, calc(var(--_height) / 2));
138
+ }
139
+ }
140
+
141
+ .s {
142
+ --_height: 3.5rem;
143
+ --_square-radius: 1rem;
144
+ --_gap: 0.75rem;
145
+ --_padding: 1rem;
146
+ font-size: 1rem;
147
+ --np-icon-size: 1.5rem;
148
+ }
149
+ .m {
150
+ --_height: 5rem;
151
+ --_square-radius: 1.25rem;
152
+ --_gap: 1rem;
153
+ --_padding: 1.5rem;
154
+ font-size: 1.375rem;
155
+ --np-icon-size: 1.75rem;
156
+ }
157
+ .l {
158
+ --_height: 6rem;
159
+ --_square-radius: 1.75rem;
160
+ --_gap: 1rem;
161
+ --_padding: 2rem;
162
+ font-size: 1.5rem;
163
+ --np-icon-size: 2.25rem;
164
+ }
165
+
166
+ .np-fab-collapsed {
167
+ width: var(--_height);
168
+ padding-inline: 0;
169
+ justify-content: center;
170
+ gap: 0;
171
+ }
172
+ .np-fab-collapsed .np-fab-label {
173
+ opacity: 0;
174
+ max-width: 0;
175
+ }
176
+
177
+ .np-fab-label {
178
+ max-width: 20rem;
179
+ overflow: hidden;
180
+ text-overflow: ellipsis;
181
+ }
182
+
183
+ @media (prefers-reduced-motion: no-preference) {
184
+ .np-extended-fab {
185
+ transition:
186
+ width var(--np-motion-expressive-default-spatial),
187
+ gap var(--np-motion-expressive-default-spatial),
188
+ padding-inline var(--np-motion-expressive-default-spatial),
189
+ border-radius var(--np-motion-expressive-default-spatial),
190
+ box-shadow var(--np-motion-expressive-fast-effects),
191
+ background-color var(--np-motion-expressive-fast-effects);
192
+ }
193
+ .np-fab-label {
194
+ transition:
195
+ opacity var(--np-motion-expressive-fast-effects),
196
+ max-width var(--np-motion-expressive-default-spatial);
197
+ }
198
+ }
199
+
200
+ .np-fab-primary {
201
+ --_container-color: var(--np-color-primary);
202
+ --_icon-color: var(--np-color-on-primary);
203
+ }
204
+ .np-fab-secondary {
205
+ --_container-color: var(--np-color-secondary);
206
+ --_icon-color: var(--np-color-on-secondary);
207
+ }
208
+ .np-fab-tertiary {
209
+ --_container-color: var(--np-color-tertiary);
210
+ --_icon-color: var(--np-color-on-tertiary);
211
+ }
212
+ .np-fab-primary-container {
213
+ --_container-color: var(--np-color-primary-container);
214
+ --_icon-color: var(--np-color-on-primary-container);
215
+ }
216
+ .np-fab-secondary-container {
217
+ --_container-color: var(--np-color-secondary-container);
218
+ --_icon-color: var(--np-color-on-secondary-container);
219
+ }
220
+ .np-fab-tertiary-container {
221
+ --_container-color: var(--np-color-tertiary-container);
222
+ --_icon-color: var(--np-color-on-tertiary-container);
223
+ }
224
+
225
+ .np-fab-lowered {
226
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-1));
227
+ }
228
+
229
+ @media (hover: hover) {
230
+ .np-extended-fab:not(:disabled):hover {
231
+ background-color: color-mix(
232
+ in srgb,
233
+ var(--np-fab-icon-color, var(--_icon-color)) 8%,
234
+ var(--np-fab-container-color, var(--_container-color))
235
+ );
236
+ }
237
+ .np-fab-lowered:not(:disabled):hover {
238
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-2));
239
+ }
240
+ }
241
+
242
+ .np-extended-fab:disabled {
243
+ cursor: default;
244
+ box-shadow: none;
245
+ background-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
246
+ color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
247
+ }
248
+
249
+ .np-fab-icon {
250
+ display: flex;
251
+ flex: none;
252
+ fill: currentColor;
253
+ pointer-events: none;
254
+ }
255
+ </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;
@@ -0,0 +1,211 @@
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
+ .round {
113
+ border-radius: var(--np-fab-shape, 50%);
114
+ }
115
+ .square {
116
+ border-radius: var(--np-fab-shape, var(--_square-radius));
117
+ }
118
+
119
+ @media (prefers-reduced-motion: no-preference) {
120
+ .round:not(:disabled):is(:active, .pressed) {
121
+ border-radius: var(--np-fab-pressed-shape, var(--_square-radius));
122
+ }
123
+ .square:not(:disabled):is(:active, .pressed) {
124
+ border-radius: var(--np-fab-pressed-shape, 50%);
125
+ }
126
+ }
127
+
128
+ @media (prefers-reduced-motion: no-preference) {
129
+ .np-fab {
130
+ transition:
131
+ border-radius var(--np-motion-expressive-default-spatial),
132
+ box-shadow var(--np-motion-expressive-fast-effects),
133
+ background-color var(--np-motion-expressive-fast-effects);
134
+ }
135
+ }
136
+
137
+ .s {
138
+ width: 3.5rem;
139
+ height: 3.5rem;
140
+ --_square-radius: 1rem;
141
+ --np-icon-size: 1.5rem;
142
+ }
143
+ .m {
144
+ width: 5rem;
145
+ height: 5rem;
146
+ --_square-radius: 1.25rem;
147
+ --np-icon-size: 1.75rem;
148
+ }
149
+ .l {
150
+ width: 6rem;
151
+ height: 6rem;
152
+ --_square-radius: 1.75rem;
153
+ --np-icon-size: 2.25rem;
154
+ }
155
+
156
+ .np-fab-primary {
157
+ --_container-color: var(--np-color-primary);
158
+ --_icon-color: var(--np-color-on-primary);
159
+ }
160
+ .np-fab-secondary {
161
+ --_container-color: var(--np-color-secondary);
162
+ --_icon-color: var(--np-color-on-secondary);
163
+ }
164
+ .np-fab-tertiary {
165
+ --_container-color: var(--np-color-tertiary);
166
+ --_icon-color: var(--np-color-on-tertiary);
167
+ }
168
+ .np-fab-primary-container {
169
+ --_container-color: var(--np-color-primary-container);
170
+ --_icon-color: var(--np-color-on-primary-container);
171
+ }
172
+ .np-fab-secondary-container {
173
+ --_container-color: var(--np-color-secondary-container);
174
+ --_icon-color: var(--np-color-on-secondary-container);
175
+ }
176
+ .np-fab-tertiary-container {
177
+ --_container-color: var(--np-color-tertiary-container);
178
+ --_icon-color: var(--np-color-on-tertiary-container);
179
+ }
180
+
181
+ .np-fab-lowered {
182
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-1));
183
+ }
184
+
185
+ @media (hover: hover) {
186
+ .np-fab:not(:disabled):hover {
187
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-3));
188
+ background-color: color-mix(
189
+ in srgb,
190
+ var(--np-fab-icon-color, var(--_icon-color)) 8%,
191
+ var(--np-fab-container-color, var(--_container-color))
192
+ );
193
+ }
194
+ .np-fab-lowered:not(:disabled):hover {
195
+ box-shadow: var(--np-fab-elevation, var(--np-elevation-2));
196
+ }
197
+ }
198
+
199
+ .np-fab:disabled {
200
+ cursor: default;
201
+ box-shadow: none;
202
+ background-color: color-mix(in srgb, var(--np-color-on-surface) 12%, transparent);
203
+ color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
204
+ }
205
+
206
+ .np-fab-icon {
207
+ display: flex;
208
+ fill: currentColor;
209
+ pointer-events: none;
210
+ }
211
+ </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;