fluentui-svelte 0.5.0 → 0.6.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 (41) hide show
  1. package/dist/components/dialog/dialog-actions.svelte +17 -2
  2. package/dist/components/dialog/dialog-content.svelte +2 -2
  3. package/dist/components/dialog/dialog-content.svelte.d.ts +1 -1
  4. package/dist/components/dialog/dialog-surface.svelte +32 -3
  5. package/dist/components/dialog/dialog-title.svelte +23 -6
  6. package/dist/components/dialog/dialog-trigger.svelte +8 -2
  7. package/dist/components/dialog/dialog-trigger.svelte.d.ts +1 -1
  8. package/dist/components/dialog/docs/llms.md +2 -2
  9. package/dist/components/dialog/types.d.ts +6 -2
  10. package/dist/components/dialog/utils.d.ts +2 -0
  11. package/dist/components/dialog/utils.js +1 -0
  12. package/dist/components/rating/docs/llms.md +110 -0
  13. package/dist/components/rating/rating.svelte +210 -0
  14. package/dist/components/rating/rating.svelte.d.ts +19 -0
  15. package/dist/components/rating/types.d.ts +44 -0
  16. package/dist/components/rating/types.js +1 -0
  17. package/dist/components/tag/docs/llms.md +383 -0
  18. package/dist/components/tag/tag-group.d.ts +2 -0
  19. package/dist/components/tag/tag-group.js +2 -0
  20. package/dist/components/tag/tag-group.svelte +117 -0
  21. package/dist/components/tag/tag-group.svelte.d.ts +20 -0
  22. package/dist/components/tag/tag.svelte +458 -0
  23. package/dist/components/tag/tag.svelte.d.ts +19 -0
  24. package/dist/components/tag/types.d.ts +135 -0
  25. package/dist/components/tag/types.js +1 -0
  26. package/dist/components/tag-picker/docs/llms.md +292 -0
  27. package/dist/components/tag-picker/option-group.svelte +38 -0
  28. package/dist/components/tag-picker/option-group.svelte.d.ts +4 -0
  29. package/dist/components/tag-picker/option.svelte +71 -0
  30. package/dist/components/tag-picker/option.svelte.d.ts +4 -0
  31. package/dist/components/tag-picker/tag-picker.d.ts +2 -0
  32. package/dist/components/tag-picker/tag-picker.js +2 -0
  33. package/dist/components/tag-picker/tag-picker.svelte +644 -0
  34. package/dist/components/tag-picker/tag-picker.svelte.d.ts +21 -0
  35. package/dist/components/tag-picker/types.d.ts +122 -0
  36. package/dist/components/tag-picker/types.js +1 -0
  37. package/dist/index.d.ts +7 -0
  38. package/dist/index.js +12 -0
  39. package/dist/internal/constants.d.ts +0 -4
  40. package/dist/internal/constants.js +0 -4002
  41. package/package.json +2 -2
@@ -2,7 +2,14 @@
2
2
  import { getTabspotAttributes } from 'tabspot';
3
3
  import type { DialogActionsProps } from './types.js';
4
4
 
5
- let { ref = $bindable(), position = 'end', fluid, children }: DialogActionsProps = $props();
5
+ let {
6
+ ref = $bindable(),
7
+ position = 'end',
8
+ fluid,
9
+ class: classes,
10
+ children,
11
+ ...attributes
12
+ }: DialogActionsProps = $props();
6
13
 
7
14
  // The bar is one tab stop holding a row of buttons: without the mover the root would
8
15
  // take the tab stop and then offer no way to reach the rest of the buttons.
@@ -12,7 +19,12 @@
12
19
  });
13
20
  </script>
14
21
 
15
- <div class={['dialog-actions', `justify-${position}`, { fluid }]} bind:this={ref} {...tabspotAttributes}>
22
+ <div
23
+ class={['dialog-actions', `justify-${position}`, { fluid }, classes]}
24
+ bind:this={ref}
25
+ {...tabspotAttributes}
26
+ {...attributes}
27
+ >
16
28
  {@render children?.()}
17
29
  </div>
18
30
 
@@ -33,5 +45,8 @@
33
45
  &.justify-end {
34
46
  justify-content: flex-end;
35
47
  }
48
+ &.fluid > :global(*) {
49
+ flex: 1 1 0;
50
+ }
36
51
  }
37
52
  </style>
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { DialogContentProps } from './types.js';
3
3
 
4
- let { children }: DialogContentProps = $props();
4
+ let { ref = $bindable(), class: classes, children, ...attributes }: DialogContentProps = $props();
5
5
  </script>
6
6
 
7
- <div class="dialog-content">
7
+ <div class={['dialog-content', classes]} bind:this={ref} {...attributes}>
8
8
  {@render children?.()}
9
9
  </div>
10
10
 
@@ -1,4 +1,4 @@
1
1
  import type { DialogContentProps } from './types.ts';
2
- declare const DialogContent: import("svelte").Component<DialogContentProps, {}, "">;
2
+ declare const DialogContent: import("svelte").Component<DialogContentProps, {}, "ref">;
3
3
  type DialogContent = ReturnType<typeof DialogContent>;
4
4
  export default DialogContent;
@@ -1,11 +1,12 @@
1
1
  <script lang="ts">
2
2
  import { onClickOutside } from 'runed';
3
+ import { on } from 'svelte/events';
3
4
  import type { DialogSurfaceProps } from './types.js';
4
5
  import { getDialogContext } from './dialog.svelte.js';
5
6
  import { Button } from '../../index.js';
6
7
  import DismissFilled from 'fluentui-icons-svelte/DismissFilled.svelte';
7
8
 
8
- let { ref = $bindable(), children, ...attributes }: DialogSurfaceProps = $props();
9
+ let { ref = $bindable(), class: classes, oncancel, children, ...attributes }: DialogSurfaceProps = $props();
9
10
 
10
11
  const CONTEXT = getDialogContext();
11
12
 
@@ -17,22 +18,50 @@
17
18
 
18
19
  const { closeDialog } = methods;
19
20
 
21
+ // runed reports an outside press 10ms after the pointer goes down. By then the press that opens the
22
+ // dialog has already opened it, and a press while it is closed has nothing to dismiss, so remember
23
+ // whether the dialog was open at the moment the pointer went down.
24
+ let openOnPointerDown = false;
25
+
26
+ $effect(() => {
27
+ const doc = ref?.ownerDocument;
28
+ if (!doc) return;
29
+ return on(doc, 'pointerdown', () => (openOnPointerDown = !!ref?.open), { capture: true });
30
+ });
31
+
20
32
  // Registered unconditionally and guarded from inside, so switching `type` after mount is honored.
21
33
  onClickOutside(
22
34
  () => ref,
23
35
  () => {
24
- if (config.type === 'alert') return;
36
+ if (config.type === 'alert' || !openOnPointerDown) return;
25
37
  closeDialog();
26
38
  }
27
39
  );
28
40
 
41
+ // Escape cancels a modal dialog natively. An alert has to be answered with one of its actions, and any
42
+ // other modal closes through `closeDialog` so `onOpenChange` hears about it. A consumer's `oncancel`
43
+ // can still veto it with `preventDefault`.
44
+ function handleCancel(e: Event & { currentTarget: EventTarget & HTMLDialogElement }) {
45
+ oncancel?.(e);
46
+ if (e.defaultPrevented) return;
47
+ e.preventDefault();
48
+ if (config.type !== 'alert') closeDialog();
49
+ }
50
+
29
51
  $effect(() => {
30
52
  if (!ref) return;
31
53
  _state.dialogRef = ref;
32
54
  });
33
55
  </script>
34
56
 
35
- <dialog open={_state.open} class="fs-dialog" aria-labelledby={_state.titleId} bind:this={ref} {...attributes}>
57
+ <dialog
58
+ open={_state.open}
59
+ class={['fs-dialog', classes]}
60
+ aria-labelledby={_state.titleId}
61
+ bind:this={ref}
62
+ oncancel={handleCancel}
63
+ {...attributes}
64
+ >
36
65
  <div class="dialog-wrapper">
37
66
  {#if config.type === 'non-modal'}
38
67
  <Button class="close-icon" appearance="subtle" onclick={() => methods.closeDialog()} aria-label="Close dialog">
@@ -1,19 +1,36 @@
1
1
  <script lang="ts" generics="T extends DialogTitleTag">
2
2
  import { PREFIX } from '../../internal/constants.js';
3
3
  import { getDialogContext } from './dialog.svelte.js';
4
+ import { TITLE_TAG } from './utils.js';
4
5
  import type { DialogTitleProps, DialogTitleTag } from './types.js';
5
6
 
6
- let { as = 'h3' as T, ref = $bindable(), children }: DialogTitleProps<T> = $props();
7
-
8
- const FALLBACK_ID = $props.id();
9
- const ID = `${PREFIX}dialog-title-${FALLBACK_ID}`;
7
+ const _ID = $props.id();
8
+ const FALLBACK_ID = `${PREFIX}dialog-title-${_ID}`;
9
+
10
+ let {
11
+ as = 'h3' as T,
12
+ ref = $bindable(),
13
+ id = FALLBACK_ID,
14
+ class: classes,
15
+ children,
16
+ ...attributes
17
+ }: DialogTitleProps<T> = $props();
18
+
19
+ /**
20
+ * The tag the invariant rules over. The markup renders it through here, so it is checked
21
+ * during SSR and on every prop update rather than only once on mount.
22
+ */
23
+ const _as = $derived.by(() => {
24
+ if (!TITLE_TAG.includes(as)) throw new Error(`Invalid tag: ${as}. Must be one of ${TITLE_TAG.join(', ')}`);
25
+ return as;
26
+ });
10
27
 
11
28
  const context = getDialogContext();
12
29
 
13
30
  $effect.pre(() => {
14
31
  if (!context) return;
15
32
 
16
- context.state.titleId = ID;
33
+ context.state.titleId = id ?? undefined;
17
34
 
18
35
  return () => {
19
36
  context.state.titleId = undefined;
@@ -21,7 +38,7 @@
21
38
  });
22
39
  </script>
23
40
 
24
- <svelte:element this={as} id={ID} class="dialog-title" bind:this={ref}>
41
+ <svelte:element this={_as} {id} class={['dialog-title', classes]} bind:this={ref} {...attributes}>
25
42
  {@render children?.()}
26
43
  </svelte:element>
27
44
 
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { Button } from '../../index.js';
3
+ import { invokeHandlers } from '../../internal/index.js';
3
4
  import { getDialogContext } from './dialog.svelte.js';
4
5
  import type { DialogTriggerProps } from './types.js';
5
6
 
6
- let { children }: DialogTriggerProps = $props();
7
+ let { ref = $bindable(), class: classes, onclick, children, ...attributes }: DialogTriggerProps = $props();
7
8
 
8
9
  const CONTEXT = getDialogContext();
9
10
 
@@ -14,7 +15,12 @@
14
15
  const { methods } = CONTEXT;
15
16
  </script>
16
17
 
17
- <Button class="dialog-trigger" onclick={methods.openDialog}>
18
+ <Button
19
+ bind:ref
20
+ class={['dialog-trigger', classes]}
21
+ onclick={(e: MouseEvent) => invokeHandlers(e, [], [onclick, methods.openDialog])}
22
+ {...attributes}
23
+ >
18
24
  {#if children}
19
25
  {@render children()}
20
26
  {:else}
@@ -1,4 +1,4 @@
1
1
  import type { DialogTriggerProps } from './types.ts';
2
- declare const DialogTrigger: import("svelte").Component<DialogTriggerProps, {}, "">;
2
+ declare const DialogTrigger: import("svelte").Component<DialogTriggerProps, {}, "ref">;
3
3
  type DialogTrigger = ReturnType<typeof DialogTrigger>;
4
4
  export default DialogTrigger;
@@ -99,7 +99,7 @@ Add a `DialogActions` region to render action buttons in the dialog footer. Use
99
99
 
100
100
  | Name | Type | Default | Description |
101
101
  | ----------------------- | ------------------- | ------- | ----------------------------------------- |
102
- | `ref` | `HTMLButtonElement` | | The DOM reference of the trigger element. |
102
+ | `ref` _bindable_ | `HTMLButtonElement` | | The DOM reference of the trigger element. |
103
103
  | `ButtonProps<'button'>` | | | |
104
104
 
105
105
  <!-- /props:DialogTriggerProps -->
@@ -136,7 +136,7 @@ Add a `DialogActions` region to render action buttons in the dialog footer. Use
136
136
 
137
137
  | Name | Type | Default | Description |
138
138
  | -------------------------- | ---------------- | ------- | ----------------------------------------- |
139
- | `ref` | `HTMLDivElement` | | The DOM reference of the content element. |
139
+ | `ref` _bindable_ | `HTMLDivElement` | | The DOM reference of the content element. |
140
140
  | Element Attributes (`div`) | | | |
141
141
 
142
142
  <!-- /props:DialogContentProps -->
@@ -35,12 +35,16 @@ export type DialogSurfaceProps = {
35
35
  } & HTMLAttributes<HTMLDialogElement>;
36
36
  /** @propsmith DialogTriggerProps */
37
37
  export type DialogTriggerProps = {
38
- /** The DOM reference of the trigger element. */
38
+ /** The DOM reference of the trigger element.
39
+ * @bindable
40
+ */
39
41
  ref?: HTMLButtonElement;
40
42
  } & ButtonProps<'button'>;
41
43
  /** @propsmith DialogContentProps */
42
44
  export type DialogContentProps = {
43
- /** The DOM reference of the content element. */
45
+ /** The DOM reference of the content element.
46
+ * @bindable
47
+ */
44
48
  ref?: HTMLDivElement;
45
49
  } & HTMLAttributes<HTMLDivElement>;
46
50
  /** @propsmith DialogActionsProps */
@@ -0,0 +1,2 @@
1
+ import type { DialogTitleTag } from './types.ts';
2
+ export declare const TITLE_TAG: DialogTitleTag[];
@@ -0,0 +1 @@
1
+ export const TITLE_TAG = ['div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
@@ -0,0 +1,110 @@
1
+ # Rating
2
+
3
+ A rating lets users give a value to an item by selecting one of a row of stars. It supports half-filled stars, four sizes and a custom number of items.
4
+
5
+ ## Usage
6
+
7
+ ```svelte
8
+ <script>
9
+ import { Rating } from 'fluentui-svelte';
10
+
11
+ let value = $state(3);
12
+ </script>
13
+
14
+ <Rating bind:value />
15
+ ```
16
+
17
+ ## Component API
18
+
19
+ A rating lets users give a value to an item, using a row of symbols such as stars. It supports half-filled items and four sizes.
20
+
21
+ ## Examples
22
+
23
+ ### Bind the value
24
+
25
+ Use `bind:value` to read the rating the user picks.
26
+
27
+ ```svelte
28
+ <script>
29
+ let value = $state(3);
30
+ </script>
31
+
32
+ <Rating bind:value />
33
+ ```
34
+
35
+ ### Half stars
36
+
37
+ Set `step={0.5}` to let users pick half-filled stars.
38
+
39
+ ```svelte
40
+ <Rating value={2.5} step={0.5} />
41
+ ```
42
+
43
+ ### Quarter stars
44
+
45
+ Until the user rates, a fractional `value` is drawn in quarters, which is handy to show an average. Once the user picks a star, the rating snaps to the `step`.
46
+
47
+ ```svelte
48
+ <Rating value={3.75} />
49
+ ```
50
+
51
+ ### Size
52
+
53
+ Use `size` to choose between `small`, `medium`, `large` and `extra-large`.
54
+
55
+ ```svelte
56
+ <Rating value={3} size="small" />
57
+ <Rating value={3} size="medium" />
58
+ <Rating value={3} size="large" />
59
+ <Rating value={3} size="extra-large" />
60
+ ```
61
+
62
+ ### Shape
63
+
64
+ Use `iconFilled` and `iconOutline` to replace the stars with any other icon component. Both fall back to the star when omitted, and partially filled items are clipped, so custom shapes also work with `step={0.5}`.
65
+
66
+ ```svelte
67
+ <script>
68
+ import { HeartFilled, HeartRegular } from 'fluentui-icons-svelte';
69
+ </script>
70
+
71
+ <Rating value={3} iconFilled={HeartFilled} iconOutline={HeartRegular} />
72
+ ```
73
+
74
+ ### Max
75
+
76
+ Use `max` to change the number of stars.
77
+
78
+ ```svelte
79
+ <Rating value={4} max={10} />
80
+ ```
81
+
82
+ ### Item label
83
+
84
+ Use `itemLabel` to customize the accessible label of each radio input.
85
+
86
+ ```svelte
87
+ <Rating value={3} itemLabel={(rating) => `${rating} out of 5 stars`} />
88
+ ```
89
+
90
+ ## Component Props
91
+
92
+ <!-- DO NOT EDIT THIS SECTION (propsmith generated) -->
93
+ <!-- props:RatingProps -->
94
+
95
+ | Name | Type | Default | Description |
96
+ | --------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
97
+ | `value` _bindable_ | `number` | `0` | The current rating. Fractions are drawn in quarters until the user rates. |
98
+ | `max` | `number` | `5` | The number of items displayed. Must be a whole number greater than 1. |
99
+ | `step` | `1` &#124; `0.5` | `1` | The precision of the rating. `0.5` allows half-filled items. |
100
+ | `size` | `'small'` &#124; `'medium'` &#124; `'large'` &#124; `'extra-large'` | `'extra-large'` | The size of the rating items. |
101
+ | `iconFilled` | `Component` | | The icon of a filled item. Falls back to a filled star. Partially filled items are clipped, so it also works with half steps. |
102
+ | `iconOutline` | `Component` | | The icon of an empty item. Falls back to an outlined star. |
103
+ | `name` | `string` | | The name of the radio inputs. Generated when not provided. |
104
+ | `itemLabel` | `(rating: number) => string` | `` (rating) => `${rating}` `` | Generates the accessible label of the radio input for a rating value. |
105
+ | `onChange` | `(value: number) => void` | | Called when the user changes the rating. |
106
+ | `class` | `string` | | The class to apply to the rating. |
107
+ | `ref` _bindable_ | `HTMLElement` | | The DOM reference of the rating element. |
108
+ | `HTMLAttributes<HTMLDivElement>` without `onchange` | | | |
109
+
110
+ <!-- /props:RatingProps -->
@@ -0,0 +1,210 @@
1
+ <script lang="ts">
2
+ import { PREFIX } from '../../internal/constants.js';
3
+ import StarFilled from 'fluentui-icons-svelte/StarFilled.svelte';
4
+ import StarHalfFilled from 'fluentui-icons-svelte/StarHalfFilled.svelte';
5
+ import StarOneQuarterFilled from 'fluentui-icons-svelte/StarOneQuarterFilled.svelte';
6
+ import StarRegular from 'fluentui-icons-svelte/StarRegular.svelte';
7
+ import StarThreeQuarterFilled from 'fluentui-icons-svelte/StarThreeQuarterFilled.svelte';
8
+ import type { RatingProps } from './types.js';
9
+
10
+ const FALLBACK_ID = $props.id();
11
+ const COMPONENT_NAME = 'rating';
12
+ const ID = `${PREFIX}${COMPONENT_NAME}-${FALLBACK_ID}`;
13
+ const VALUE_ID = `${ID}-value`;
14
+
15
+ let {
16
+ value = $bindable(0),
17
+ max = 5,
18
+ step = 1,
19
+ size = 'extra-large',
20
+ iconFilled,
21
+ iconOutline,
22
+ name = ID,
23
+ itemLabel = (rating) => `${rating}`,
24
+ onChange,
25
+ class: classes,
26
+ ref = $bindable(),
27
+ role = 'radiogroup',
28
+ ...attributes
29
+ }: RatingProps = $props();
30
+
31
+ /** Until the user rates, the value is only displayed, so it is drawn with the precision of a quarter. */
32
+ let rated = $state(false);
33
+ let hovered = $state<number>();
34
+
35
+ const _max = $derived.by(() => {
36
+ if (!Number.isInteger(max) || max <= 1) throw new Error('Max must be a whole number greater than 1');
37
+ return max;
38
+ });
39
+ const items = $derived(Array.from({ length: _max }, (_, index) => index + 1));
40
+ const shown = $derived(hovered ?? value);
41
+ const checkedValue = $derived(value === 0 ? 0 : Math.min(_max, Math.max(step, Math.round(value / step) * step)));
42
+
43
+ const custom = $derived(!!(iconFilled || iconOutline));
44
+
45
+ /** How much of the item is filled, snapped to the precision it is shown with. */
46
+ function fillFor(item: number) {
47
+ const fill = Math.min(Math.max(shown - (item - 1), 0), 1);
48
+ const precision = hovered !== undefined || rated ? step / 2 : 0.25;
49
+ return Math.round(fill / precision) * precision;
50
+ }
51
+
52
+ function iconFor(snapped: number) {
53
+ if (snapped >= 1) return StarFilled;
54
+ if (snapped >= 0.75) return StarThreeQuarterFilled;
55
+ if (snapped >= 0.5) return StarHalfFilled;
56
+ if (snapped >= 0.25) return StarOneQuarterFilled;
57
+ return StarRegular;
58
+ }
59
+
60
+ function select(next: number) {
61
+ rated = true;
62
+ value = next;
63
+ onChange?.(next);
64
+ }
65
+ </script>
66
+
67
+ <!--
68
+ @component
69
+ A rating lets users give a value to an item, using a row of symbols such as stars. It supports half-filled items and four sizes.
70
+
71
+ - Usage:
72
+ ```tsx
73
+ <script>
74
+ import { Rating } from 'fluentui-svelte';
75
+
76
+ let value = $state(3);
77
+ </script>
78
+
79
+ <Rating bind:value />
80
+ <Rating bind:value step={0.5} size="medium" />
81
+ ```
82
+ -->
83
+ <div
84
+ class={['fs-rating', `size-${size}`, classes]}
85
+ bind:this={ref}
86
+ {role}
87
+ onpointerleave={() => (hovered = undefined)}
88
+ {...attributes}
89
+ aria-label={attributes['aria-label'] ?? (attributes['aria-labelledby'] ? undefined : 'Rating')}
90
+ aria-describedby={[attributes['aria-describedby'], VALUE_ID].filter(Boolean).join(' ')}
91
+ >
92
+ <span id={VALUE_ID} hidden>Current rating: {value} out of {_max}.</span>
93
+ {#each items as item (item)}
94
+ {@const fill = fillFor(item)}
95
+ <span class="rating-item">
96
+ {#if custom}
97
+ {@const Outline = iconOutline ?? StarRegular}
98
+ {@const Filled = iconFilled ?? StarFilled}
99
+ <span class="rating-icon custom" class:selected={fill > 0} style="--rating-fill: {fill * 100}%">
100
+ <Outline aria-hidden="true" />
101
+ <span class="filled"><Filled aria-hidden="true" /></span>
102
+ </span>
103
+ {:else}
104
+ {@const Icon = iconFor(fill)}
105
+ <span class="rating-icon" class:selected={fill > 0}><Icon aria-hidden="true" /></span>
106
+ {/if}
107
+ {#if step === 0.5}
108
+ <input
109
+ class="half-value"
110
+ type="radio"
111
+ {name}
112
+ value={item - 0.5}
113
+ checked={checkedValue === item - 0.5}
114
+ aria-label={itemLabel(item - 0.5)}
115
+ onclick={() => value !== item - 0.5 && select(item - 0.5)}
116
+ onpointerenter={() => (hovered = item - 0.5)}
117
+ />
118
+ {/if}
119
+ <input
120
+ class="full-value"
121
+ type="radio"
122
+ {name}
123
+ value={item}
124
+ checked={checkedValue === item}
125
+ aria-label={itemLabel(item)}
126
+ onclick={() => value !== item && select(item)}
127
+ onpointerenter={() => (hovered = item)}
128
+ />
129
+ </span>
130
+ {/each}
131
+ </div>
132
+
133
+ <style>
134
+ .fs-rating {
135
+ --rating-size: 24px;
136
+ display: inline-flex;
137
+ align-items: center;
138
+ color: var(--fs-accent-fill-default);
139
+
140
+ &.size-small {
141
+ --rating-size: 12px;
142
+ }
143
+
144
+ &.size-medium {
145
+ --rating-size: 16px;
146
+ }
147
+
148
+ &.size-large {
149
+ --rating-size: 20px;
150
+ }
151
+ }
152
+
153
+ .rating-item {
154
+ display: inline-flex;
155
+ position: relative;
156
+ border-radius: var(--fs-control-border-radius);
157
+ padding: 2px;
158
+
159
+ &:has(input:focus-visible) {
160
+ box-shadow: var(--fs-focus-stroke-outer);
161
+ }
162
+
163
+ & input {
164
+ appearance: none;
165
+ position: absolute;
166
+ inset-block: 0;
167
+ inset-inline-start: 0;
168
+ margin: 0;
169
+ outline: none;
170
+ cursor: pointer;
171
+ inline-size: 100%;
172
+ block-size: 100%;
173
+ }
174
+
175
+ & .half-value {
176
+ z-index: 1;
177
+ inline-size: 50%;
178
+ }
179
+ }
180
+
181
+ .rating-icon {
182
+ display: inline-flex;
183
+ color: var(--fs-text-secondary);
184
+
185
+ &.selected {
186
+ color: var(--fs-accent-fill-default);
187
+ }
188
+
189
+ & :global(svg) {
190
+ inline-size: var(--rating-size);
191
+ block-size: var(--rating-size);
192
+ }
193
+
194
+ &.custom {
195
+ position: relative;
196
+
197
+ & .filled {
198
+ display: inline-flex;
199
+ position: absolute;
200
+ inset: 0;
201
+ color: var(--fs-accent-fill-default);
202
+ clip-path: inset(0 calc(100% - var(--rating-fill)) 0 0);
203
+
204
+ &:dir(rtl) {
205
+ clip-path: inset(0 0 0 calc(100% - var(--rating-fill)));
206
+ }
207
+ }
208
+ }
209
+ }
210
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { RatingProps } from './types.ts';
2
+ /**
3
+ * A rating lets users give a value to an item, using a row of symbols such as stars. It supports half-filled items and four sizes.
4
+ *
5
+ * - Usage:
6
+ * ```tsx
7
+ * <script>
8
+ * import { Rating } from 'fluentui-svelte';
9
+ *
10
+ * let value = $state(3);
11
+ * </script>
12
+ *
13
+ * <Rating bind:value />
14
+ * <Rating bind:value step={0.5} size="medium" />
15
+ * ```
16
+ */
17
+ declare const Rating: import("svelte").Component<RatingProps, {}, "ref" | "value">;
18
+ type Rating = ReturnType<typeof Rating>;
19
+ export default Rating;
@@ -0,0 +1,44 @@
1
+ import type { Component } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ /** @propsmith RatingProps */
4
+ export type RatingProps = {
5
+ /** The current rating. Fractions are drawn in quarters until the user rates.
6
+ * @default 0
7
+ * @bindable
8
+ */
9
+ value?: number;
10
+ /** The number of items displayed. Must be a whole number greater than 1.
11
+ * @default 5
12
+ */
13
+ max?: number;
14
+ /** The precision of the rating. `0.5` allows half-filled items.
15
+ * @default 1
16
+ */
17
+ step?: 1 | 0.5;
18
+ /** The size of the rating items.
19
+ * @default 'extra-large'
20
+ */
21
+ size?: 'small' | 'medium' | 'large' | 'extra-large';
22
+ /** The icon of a filled item. Falls back to a filled star. Partially filled items are clipped, so it also works with half steps.
23
+ * @type Component
24
+ */
25
+ iconFilled?: Component;
26
+ /** The icon of an empty item. Falls back to an outlined star.
27
+ * @type Component
28
+ */
29
+ iconOutline?: Component;
30
+ /** The name of the radio inputs. Generated when not provided. */
31
+ name?: string;
32
+ /** Generates the accessible label of the radio input for a rating value.
33
+ * @default (rating) => `${rating}`
34
+ */
35
+ itemLabel?: (rating: number) => string;
36
+ /** Called when the user changes the rating. */
37
+ onChange?: (value: number) => void;
38
+ /** The class to apply to the rating. */
39
+ class?: string;
40
+ /** The DOM reference of the rating element.
41
+ * @bindable
42
+ */
43
+ ref?: HTMLElement;
44
+ } & Omit<HTMLAttributes<HTMLDivElement>, 'onchange'>;
@@ -0,0 +1 @@
1
+ export {};