Skip to content

Commit 1dece23

Browse files
committed
feat: migrate components in segmented button to runes
1 parent e2a005c commit 1dece23

File tree

9 files changed

+309
-192
lines changed

9 files changed

+309
-192
lines changed

MIGRATING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This doc contains information that will help you migrate your code from an older
1616
- TabBar now expects a `tab` snippet instead of using `let:tab`.
1717
- Chips' Set now expects a `chip` snippet instead of using `let:chip`.
1818
- Chips' Set key function is now required to return `string`, not `string | number`.
19+
- SegmentedButton now expects a `segment` snippet instead of using `let:segment`.
1920

2021
## Changes
2122

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ Svelte 5 Runes mode is being migrated to slowly. This is the todo list of compon
174174
- [x] Line Ripple
175175
- [x] Notched Outline
176176
- [x] Radio Button
177-
- [ ] Segmented Button
177+
- [x] Segmented Button
178178
- [x] Select Menu
179179
- [x] Select Helper Text
180180
- [x] Select Icon

packages/segmented-button/src/Segment.svelte

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<svelte:options runes={false} />
1+
<svelte:options runes />
22

33
<button
44
bind:this={element}
@@ -25,24 +25,26 @@
2525
aria-pressed={!singleSelect ? (selected ? 'true' : 'false') : undefined}
2626
aria-checked={singleSelect ? (selected ? 'true' : 'false') : undefined}
2727
{...internalAttrs}
28-
{...$$restProps}
28+
{...restProps}
2929
onclick={(e) => {
30-
$$restProps.onclick?.(e);
30+
restProps.onclick?.(e);
3131
if (!e.defaultPrevented && instance && !manualSelection) {
3232
instance.handleClick();
3333
}
3434
}}
35-
>{#if ripple}<div class="mdc-segmented-button__ripple"></div>{/if}<slot
36-
/>{#if touch}<div
35+
>{#if ripple}<div
36+
class="mdc-segmented-button__ripple"
37+
></div>{/if}{@render children?.()}{#if touch}<div
3738
class="mdc-segmented-button__segment__touch"
3839
></div>{/if}</button
3940
>
4041

41-
<script lang="ts">
42+
<script lang="ts" generics="SegmentKey extends Object | string | number">
4243
// TODO: Remove this when MDC's segmented button is fixed.
4344
// TODO: Also remove @material/base and @material/ripple from the package.json
4445
// @ts-ignore
4546
import { MDCSegmentedButtonSegmentFoundation } from './mdc-segmented-button/index.js';
47+
import type { Snippet } from 'svelte';
4648
import { onMount, getContext } from 'svelte';
4749
import type { SmuiAttrs } from '@smui/common';
4850
import type { ActionArray } from '@smui/common/internal';
@@ -51,70 +53,91 @@
5153
5254
import type { SMUISegmentedButtonSegmentAccessor } from './Segment.types.js';
5355
56+
interface UninitializedValue extends Function {}
57+
let uninitializedValue: UninitializedValue = () => {};
58+
function isUninitializedValue(value: any): value is UninitializedValue {
59+
return value === uninitializedValue;
60+
}
61+
5462
type OwnProps = {
63+
/**
64+
* An array of Action or [Action, ActionProps] to be applied to the element.
65+
*/
5566
use?: ActionArray;
67+
/**
68+
* A space separated list of CSS classes.
69+
*/
5670
class?: string;
71+
/**
72+
* A list of CSS styles.
73+
*/
5774
style?: string;
58-
segment: any;
75+
/**
76+
* The segment object this segment is for.
77+
*/
78+
segment: SegmentKey;
79+
/**
80+
* Whether to show a ripple animation.
81+
*/
5982
ripple?: boolean;
83+
/**
84+
* Whether to use touch styling
85+
*/
6086
touch?: boolean;
87+
/**
88+
* Whether this segment is selected.
89+
*
90+
* You don't need to set this unless you are manually handling selection.
91+
*/
6192
selected?: boolean;
62-
};
63-
type $$Props = OwnProps & SmuiAttrs<'button', keyof OwnProps>;
6493
65-
interface UninitializedValue extends Function {}
66-
let uninitializedValue: UninitializedValue = () => {};
67-
function isUninitializedValue(value: any): value is UninitializedValue {
68-
return value === uninitializedValue;
69-
}
94+
children?: Snippet;
95+
};
96+
let {
97+
use = [],
98+
class: className = '',
99+
style = '',
100+
segment: segmentId,
101+
ripple = true,
102+
touch = false,
103+
selected = $bindable(uninitializedValue as unknown as boolean),
104+
children,
105+
...restProps
106+
}: OwnProps & SmuiAttrs<'button', keyof OwnProps> = $props();
70107
71-
// Remember to update $$Props if you add/remove/rename props.
72-
export let use: ActionArray = [];
73-
let className = '';
74-
export { className as class };
75-
export let style = '';
76-
let segmentId: any;
77-
export { segmentId as segment };
78-
export let ripple = true;
79-
export let touch = false;
80108
const initialSelectedStore = getContext<SvelteStore<boolean>>(
81109
'SMUI:segmented-button:segment:initialSelected',
82110
);
83-
84111
// Some trickery to detect uninitialized values but also have the right types.
85-
/** You don't need to set this unless you are manually handling selection. */
86-
export let selected: boolean = uninitializedValue as unknown as boolean;
87112
let manualSelection: boolean = !isUninitializedValue(selected);
88113
if (isUninitializedValue(selected)) {
89114
selected = $initialSelectedStore;
90115
}
91116
// Done with the trickery.
92117
93118
let element: HTMLButtonElement;
94-
let instance: MDCSegmentedButtonSegmentFoundation;
95-
let internalClasses: { [k: string]: boolean } = {};
96-
let internalStyles: { [k: string]: string } = {};
97-
let internalAttrs: { [k: string]: string | undefined } = {};
119+
let instance: MDCSegmentedButtonSegmentFoundation | undefined = $state();
120+
let internalClasses: { [k: string]: boolean } = $state({});
121+
let internalStyles: { [k: string]: string } = $state({});
122+
let internalAttrs: { [k: string]: string | undefined } = $state({});
98123
const singleSelect = getContext<SvelteStore<boolean>>(
99124
'SMUI:segmented-button:singleSelect',
100125
);
101126
const index = getContext<SvelteStore<number>>(
102127
'SMUI:segmented-button:segment:index',
103128
);
104129
105-
if (!segmentId) {
106-
throw new Error(
107-
'The segment property is required! It should be passed down from the SegmentedButton to the Segment.',
108-
);
109-
}
110-
111-
$: if (instance && instance.isSelected() && !selected) {
112-
instance.setUnselected();
113-
}
130+
$effect(() => {
131+
if (instance && instance.isSelected() && !selected) {
132+
instance.setUnselected();
133+
}
134+
});
114135
115-
$: if (instance && !instance.isSelected() && selected) {
116-
instance.setSelected();
117-
}
136+
$effect(() => {
137+
if (instance && !instance.isSelected() && selected) {
138+
instance.setSelected();
139+
}
140+
});
118141
119142
const SMUISegmentedButtonSegmentMount = getContext<
120143
((accessor: SMUISegmentedButtonSegmentAccessor) => void) | undefined
@@ -168,7 +191,7 @@
168191
SMUISegmentedButtonSegmentUnmount &&
169192
SMUISegmentedButtonSegmentUnmount(accessor);
170193
171-
instance.destroy();
194+
instance?.destroy();
172195
};
173196
});
174197

0 commit comments

Comments
 (0)