Skip to content

Commit 5ca1c05

Browse files
committed
feat: updated Chips for MDC v10
1 parent a970335 commit 5ca1c05

24 files changed

+1492
-1121
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ Update Progress Checklist:
220220
- [x] Floating Action Buttons
221221
- [x] Icon Buttons
222222
- [x] Cards
223-
- [ ] Chips
223+
- [x] Chips
224224
- [ ] Data Tables
225225
- [x] Dialogs
226226
- [x] Drawers

packages/button/Button.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656
classMap,
5757
exclude,
5858
} from '@smui/common/internal.js';
59+
import Ripple from '@smui/ripple/bare.js';
5960
import A from '@smui/common/A.svelte';
6061
import Button from '@smui/common/Button.svelte';
61-
import Ripple from '@smui/ripple/bare.js';
6262
6363
const forwardEvents = forwardEventsBuilder(get_current_component());
6464

packages/chips/Chip.svelte

+236-36
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,88 @@
11
<svelte:component
22
this={component}
33
bind:this={element}
4-
use={[forwardEvents, ...use]}
5-
forwardEvents={forwardedEvents}
4+
use={[
5+
[
6+
Ripple,
7+
{
8+
ripple,
9+
unbounded: false,
10+
addClass,
11+
removeClass,
12+
addStyle,
13+
},
14+
],
15+
forwardEvents,
16+
...use,
17+
]}
18+
forwardEvents={[
19+
'MDCChipTrailingAction:interaction',
20+
'MDCChipTrailingAction:navigation',
21+
'SMUI:chip:primary-action:mount',
22+
'SMUI:chip:primary-action:unmount',
23+
'SMUI:chip:trailing-action:mount',
24+
...forwardedEvents,
25+
]}
626
class={classMap({
727
[className]: true,
828
'mdc-chip': true,
929
'mdc-chip--selected': selected,
1030
'mdc-chip--touch': touch,
31+
...internalClasses,
1132
})}
33+
style={Object.entries(internalStyles)
34+
.map(([name, value]) => `${name}: ${value};`)
35+
.concat([style])
36+
.join(' ')}
1237
role="row"
13-
on:MDCChip:selection={handleSelection}
38+
on:transitionend={(event) => instance && instance.handleTransitionEnd(event)}
39+
on:click={() => instance && instance.handleClick()}
40+
on:keydown={(event) => instance && instance.handleKeydown(event)}
41+
on:focusin={(event) => instance && instance.handleFocusIn(event)}
42+
on:focusout={(event) => instance && instance.handleFocusOut(event)}
43+
on:MDCChipTrailingAction:interaction={() =>
44+
instance && instance.handleTrailingActionInteraction()}
45+
on:MDCChipTrailingAction:navigation={(event) =>
46+
instance && instance.handleTrailingActionNavigation(event)}
47+
on:SMUI:chip:primary-action:mount={(event) =>
48+
(primaryActionAccessor = event.detail)}
49+
on:SMUI:chip:primary-action:unmount={() =>
50+
(primaryActionAccessor = undefined)}
51+
on:SMUI:chip:trailing-action:mount={(event) =>
52+
(trailingActionAccessor = event.detail)}
53+
on:SMUI:chip:trailing-action:unmount={() =>
54+
(trailingActionAccessor = undefined)}
1455
{...exclude($$props, [
1556
'use',
1657
'class',
17-
'component',
58+
'style',
59+
'chip',
1860
'ripple',
1961
'touch',
20-
'selected',
2162
'shouldRemoveOnTrailingIconClick',
63+
'shouldFocusPrimaryActionOnClick',
64+
'component',
2265
])}
2366
>
24-
{#if ripple}
25-
<div class="mdc-chip__ripple" />
26-
{/if}
67+
<div class="mdc-chip__ripple" />
2768
<slot />
2869
{#if touch}
2970
<div class="mdc-chip__touch" />
3071
{/if}
3172
</svelte:component>
3273

3374
<script>
34-
import { MDCChip } from '@material/chips';
35-
import { onMount, setContext, getContext } from 'svelte';
75+
import { MDCChipFoundation } from '@material/chips';
76+
import { onMount, setContext, getContext, tick } from 'svelte';
3677
import { writable } from 'svelte/store';
3778
import { get_current_component } from 'svelte/internal';
3879
import {
3980
forwardEventsBuilder,
4081
classMap,
4182
exclude,
83+
dispatch,
4284
} from '@smui/common/internal.js';
85+
import Ripple from '@smui/ripple/bare.js';
4386
import Div from '@smui/common/Div.svelte';
4487
4588
const forwardedEvents = [
@@ -48,6 +91,8 @@
4891
'MDCChip:removal',
4992
'MDCChip:trailingIconInteraction',
5093
'MDCChip:navigation',
94+
'SMUI:chip:mount',
95+
'SMUI:chip:unmount',
5196
];
5297
const forwardEvents = forwardEventsBuilder(
5398
get_current_component(),
@@ -57,13 +102,33 @@
57102
export let use = [];
58103
let className = '';
59104
export { className as class };
105+
export let style = '';
106+
let chipId;
107+
export { chipId as chip };
60108
export let ripple = true;
61109
export let touch = false;
62-
export let selected = false;
63110
export let shouldRemoveOnTrailingIconClick = true;
111+
export let shouldFocusPrimaryActionOnClick = true;
64112
65113
let element;
66-
let chip;
114+
let instance;
115+
let internalClasses = {};
116+
let leadingIconClasses = {};
117+
let internalStyles = {};
118+
const initialSelectedStore = getContext('SMUI:chip:initialSelected');
119+
let selected = $initialSelectedStore;
120+
let primaryActionAccessor;
121+
let trailingActionAccessor;
122+
let accessor = {
123+
chipId,
124+
get selected() {
125+
return selected;
126+
},
127+
focusPrimaryAction,
128+
focusTrailingAction,
129+
removeFocus,
130+
setSelectedFromChipSet,
131+
};
67132
68133
export let component = Div;
69134
@@ -78,43 +143,178 @@
78143
const isSelectedStore = writable(selected);
79144
$: $isSelectedStore = selected;
80145
setContext('SMUI:chip:isSelected', isSelectedStore);
146+
const leadingIconClassesStore = writable(leadingIconClasses);
147+
$: $leadingIconClassesStore = leadingIconClasses;
148+
setContext('SMUI:chip:leadingIconClasses', leadingIconClassesStore);
81149
82-
const selectedStore = getContext('SMUI:chip:selected');
83-
let previousSelected = selected;
84-
$: if (chip && ($selectedStore || previousSelected !== selected)) {
85-
if (selected !== chip.selected) {
86-
if (previousSelected !== selected) {
87-
// Update MDC on Svelte selected change.
88-
chip.selected = selected;
89-
} else {
90-
// Update selected on MDC selection change.
91-
selected = chip.selected;
92-
}
93-
}
94-
previousSelected = selected;
150+
if (!chipId) {
151+
throw new Error(
152+
'The chip property is required! It should be passed down from the Set to the Chip.'
153+
);
95154
}
96155
97156
$: if (
98-
chip &&
99-
chip.shouldRemoveOnTrailingIconClick !== shouldRemoveOnTrailingIconClick
157+
instance &&
158+
instance.getShouldRemoveOnTrailingIconClick() !==
159+
shouldRemoveOnTrailingIconClick
100160
) {
101-
chip.shouldRemoveOnTrailingIconClick = shouldRemoveOnTrailingIconClick;
161+
instance.setShouldRemoveOnTrailingIconClick(
162+
shouldRemoveOnTrailingIconClick
163+
);
164+
}
165+
166+
$: if (instance) {
167+
instance.setShouldFocusPrimaryActionOnClick(
168+
shouldFocusPrimaryActionOnClick
169+
);
102170
}
103171
104172
onMount(() => {
105-
element.setChip = setChip;
173+
instance = new MDCChipFoundation({
174+
addClass,
175+
addClassToLeadingIcon: addLeadingIconClass,
176+
eventTargetHasClass: (target, className) =>
177+
target ? target.classList.contains(className) : false,
178+
focusPrimaryAction: () => {
179+
if (primaryActionAccessor) {
180+
primaryActionAccessor.focus();
181+
}
182+
},
183+
focusTrailingAction: () => {
184+
if (trailingActionAccessor) {
185+
trailingActionAccessor.focus();
186+
}
187+
},
188+
getAttribute: (attr) => getElement().getAttribute(attr),
189+
getCheckmarkBoundingClientRect: () => {
190+
const target = getElement().querySelector('.mdc-chip__checkmark');
191+
if (target) {
192+
return target.getBoundingClientRect();
193+
}
194+
return null;
195+
},
196+
getComputedStyleValue: getStyle,
197+
getRootBoundingClientRect: () => getElement().getBoundingClientRect(),
198+
hasClass,
199+
hasLeadingIcon: () => {
200+
const target = getElement().querySelector('.mdc-chip__icon--leading');
201+
return !!target;
202+
},
203+
isRTL: () =>
204+
getComputedStyle(getElement()).getPropertyValue('direction') === 'rtl',
205+
isTrailingActionNavigable: () => {
206+
if (trailingActionAccessor) {
207+
return trailingActionAccessor.isNavigable();
208+
}
209+
return false;
210+
},
211+
notifyInteraction: () =>
212+
dispatch(getElement(), 'MDCChip:interaction', { chipId }),
213+
notifyNavigation: (key, source) =>
214+
dispatch(getElement(), 'MDCChip:navigation', { chipId, key, source }),
215+
notifyRemoval: (removedAnnouncement) => {
216+
dispatch(getElement(), 'MDCChip:removal', {
217+
chipId,
218+
removedAnnouncement,
219+
});
220+
},
221+
notifySelection: (selected, shouldIgnore) =>
222+
dispatch(getElement(), 'MDCChip:selection', {
223+
chipId,
224+
selected,
225+
shouldIgnore,
226+
}),
227+
notifyTrailingIconInteraction: () =>
228+
dispatch(getElement(), 'MDCChip:trailingIconInteraction', { chipId }),
229+
notifyEditStart: () => {
230+
/* Not Implemented. */
231+
},
232+
notifyEditFinish: () => {
233+
/* Not Implemented. */
234+
},
235+
removeClass,
236+
removeClassFromLeadingIcon: removeLeadingIconClass,
237+
removeTrailingActionFocus: () => {
238+
if (trailingActionAccessor) {
239+
trailingActionAccessor.removeFocus();
240+
}
241+
},
242+
setPrimaryActionAttr: (attr, value) => {
243+
if (primaryActionAccessor) {
244+
primaryActionAccessor.addAttr(attr, value);
245+
}
246+
},
247+
setStyleProperty: addStyle,
248+
});
249+
250+
dispatch(getElement(), 'SMUI:chip:mount', accessor);
251+
252+
instance.init();
253+
254+
return () => {
255+
dispatch(getElement(), 'SMUI:chip:unmount', accessor);
256+
257+
instance.destroy();
258+
};
106259
});
107260
108-
function setChip(component) {
109-
chip = component;
110-
if (!ripple) {
111-
chip.ripple && chip.ripple.destroy();
261+
function hasClass(className) {
262+
return className in internalClasses
263+
? internalClasses[className]
264+
: getElement().classList.contains(className);
265+
}
266+
267+
function addClass(className) {
268+
if (!internalClasses[className]) {
269+
internalClasses[className] = true;
270+
}
271+
}
272+
273+
function removeClass(className) {
274+
if (!(className in internalClasses) || internalClasses[className]) {
275+
internalClasses[className] = false;
276+
}
277+
}
278+
279+
function addLeadingIconClass(className) {
280+
if (!leadingIconClasses[className]) {
281+
leadingIconClasses[className] = true;
282+
}
283+
}
284+
285+
function removeLeadingIconClass(className) {
286+
if (!(className in leadingIconClasses) || leadingIconClasses[className]) {
287+
leadingIconClasses[className] = false;
288+
}
289+
}
290+
291+
function addStyle(name, value) {
292+
if (internalStyles[name] !== value) {
293+
internalStyles[name] = value;
112294
}
113-
selected = chip.selected;
114295
}
115296
116-
function handleSelection(e) {
117-
selected = e.detail.selected;
297+
function getStyle(name) {
298+
return name in internalStyles
299+
? internalStyles[name]
300+
: getComputedStyle(getElement()).getPropertyValue(name);
301+
}
302+
303+
function setSelectedFromChipSet(value, shouldNotifyClients) {
304+
selected = value;
305+
instance.setSelectedFromChipSet(selected, shouldNotifyClients);
306+
}
307+
308+
function focusPrimaryAction() {
309+
instance.focusPrimaryAction();
310+
}
311+
312+
function focusTrailingAction() {
313+
instance.focusTrailingAction();
314+
}
315+
316+
function removeFocus() {
317+
instance.removeFocus();
118318
}
119319
120320
export function getElement() {

0 commit comments

Comments
 (0)