ember-primitives 0.48.2 → 0.50.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.
- package/bin/index.mjs +271 -0
- package/declarations/components/portal.d.ts.map +1 -1
- package/declarations/components/rating/public-types.d.ts +0 -4
- package/declarations/components/rating/public-types.d.ts.map +1 -1
- package/declarations/components/rating/rating.d.ts +9 -1
- package/declarations/components/rating/rating.d.ts.map +1 -1
- package/declarations/components/rating/stars.d.ts.map +1 -1
- package/declarations/components/rating/state.d.ts +4 -0
- package/declarations/components/rating/state.d.ts.map +1 -1
- package/declarations/components/rating/utils.d.ts +0 -1
- package/declarations/components/rating/utils.d.ts.map +1 -1
- package/declarations/tabster.d.ts.map +1 -1
- package/declarations/utils.d.ts.map +1 -1
- package/declarations/viewport/in-viewport.d.ts +70 -0
- package/declarations/viewport/in-viewport.d.ts.map +1 -0
- package/declarations/viewport/viewport.d.ts +59 -0
- package/declarations/viewport/viewport.d.ts.map +1 -0
- package/declarations/viewport.d.ts +3 -0
- package/declarations/viewport.d.ts.map +1 -0
- package/dist/-private.js +0 -1
- package/dist/-private.js.map +1 -1
- package/dist/color-scheme.js +0 -1
- package/dist/color-scheme.js.map +1 -1
- package/dist/{component-Bs3N-G9z.js → component-BXy_iafw.js} +2 -3
- package/dist/component-BXy_iafw.js.map +1 -0
- package/dist/components/accordion.js +5 -6
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/avatar.js +3 -4
- package/dist/components/avatar.js.map +1 -1
- package/dist/components/dialog.js +2 -3
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/external-link.js +1 -2
- package/dist/components/external-link.js.map +1 -1
- package/dist/components/form.js +1 -2
- package/dist/components/form.js.map +1 -1
- package/dist/components/heading.js +1 -2
- package/dist/components/heading.js.map +1 -1
- package/dist/components/keys.js +2 -3
- package/dist/components/keys.js.map +1 -1
- package/dist/components/layout/hero.js +1 -1
- package/dist/components/layout/sticky-footer.js +1 -1
- package/dist/components/link.js +1 -2
- package/dist/components/link.js.map +1 -1
- package/dist/components/menu.js +6 -8
- package/dist/components/menu.js.map +1 -1
- package/dist/components/one-time-password.js +1 -2
- package/dist/components/popover.js +3 -4
- package/dist/components/popover.js.map +1 -1
- package/dist/components/portal-targets.js +2 -3
- package/dist/components/portal-targets.js.map +1 -1
- package/dist/components/portal.js +3 -7
- package/dist/components/portal.js.map +1 -1
- package/dist/components/progress.js +2 -3
- package/dist/components/progress.js.map +1 -1
- package/dist/components/rating.js +1 -2
- package/dist/components/scroller.js +1 -2
- package/dist/components/scroller.js.map +1 -1
- package/dist/components/shadowed.js +2 -3
- package/dist/components/shadowed.js.map +1 -1
- package/dist/components/switch.js +5 -6
- package/dist/components/switch.js.map +1 -1
- package/dist/components/tabs.js +6 -7
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/toggle-group.js +3 -4
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle.js +2 -3
- package/dist/components/toggle.js.map +1 -1
- package/dist/components/visually-hidden.js +1 -2
- package/dist/components/visually-hidden.js.map +1 -1
- package/dist/components/zoetrope.js +1 -2
- package/dist/dom-context.js +2 -3
- package/dist/dom-context.js.map +1 -1
- package/dist/floating-ui.js +1 -2
- package/dist/head.js +1 -2
- package/dist/head.js.map +1 -1
- package/dist/helpers/body-class.js +0 -1
- package/dist/helpers/body-class.js.map +1 -1
- package/dist/helpers/link.js +0 -1
- package/dist/helpers/link.js.map +1 -1
- package/dist/helpers/service.js +0 -1
- package/dist/helpers/service.js.map +1 -1
- package/dist/helpers.js +0 -1
- package/dist/helpers.js.map +1 -1
- package/dist/iframe.js +0 -1
- package/dist/iframe.js.map +1 -1
- package/dist/{index-DKE67I8L.js → index-gRO4Cvlf.js} +2 -2
- package/dist/index-gRO4Cvlf.js.map +1 -0
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/load.js +0 -1
- package/dist/load.js.map +1 -1
- package/dist/narrowing.js +0 -1
- package/dist/narrowing.js.map +1 -1
- package/dist/on-resize.js +0 -1
- package/dist/on-resize.js.map +1 -1
- package/dist/{otp-C6hCCXKx.js → otp-7rz1PWP0.js} +6 -7
- package/dist/otp-7rz1PWP0.js.map +1 -0
- package/dist/proper-links.js +0 -1
- package/dist/proper-links.js.map +1 -1
- package/dist/qp.js +0 -1
- package/dist/qp.js.map +1 -1
- package/dist/rating-BrIiwDLw.js +152 -0
- package/dist/rating-BrIiwDLw.js.map +1 -0
- package/dist/resize-observer.js +0 -1
- package/dist/resize-observer.js.map +1 -1
- package/dist/service.js +0 -1
- package/dist/service.js.map +1 -1
- package/dist/store.js +0 -1
- package/dist/store.js.map +1 -1
- package/dist/styles.css.js +0 -1
- package/dist/tabster.js +0 -1
- package/dist/tabster.js.map +1 -1
- package/dist/test-support.js +0 -1
- package/dist/test-support.js.map +1 -1
- package/dist/{utils-C5796IKA.js → utils-D0v9WKmV.js} +1 -2
- package/dist/utils-D0v9WKmV.js.map +1 -0
- package/dist/utils.js +4 -1
- package/dist/utils.js.map +1 -1
- package/dist/viewport/in-viewport.js +82 -0
- package/dist/viewport/in-viewport.js.map +1 -0
- package/dist/viewport/viewport.js +92 -0
- package/dist/viewport/viewport.js.map +1 -0
- package/dist/viewport.js +3 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +24 -20
- package/src/-private.ts +4 -0
- package/src/color-scheme.ts +165 -0
- package/src/components/-private/typed-elements.gts +13 -0
- package/src/components/-private/utils.ts +16 -0
- package/src/components/accordion/content.gts +34 -0
- package/src/components/accordion/header.gts +36 -0
- package/src/components/accordion/item.gts +55 -0
- package/src/components/accordion/public.ts +64 -0
- package/src/components/accordion/trigger.gts +32 -0
- package/src/components/accordion.gts +195 -0
- package/src/components/avatar.gts +108 -0
- package/src/components/dialog.gts +234 -0
- package/src/components/external-link.gts +14 -0
- package/src/components/form.gts +75 -0
- package/src/components/heading.gts +36 -0
- package/src/components/keys.gts +53 -0
- package/src/components/layout/hero.css +5 -0
- package/src/components/layout/hero.gts +17 -0
- package/src/components/layout/sticky-footer.css +9 -0
- package/src/components/layout/sticky-footer.gts +40 -0
- package/src/components/link.gts +172 -0
- package/src/components/menu.gts +373 -0
- package/src/components/one-time-password/buttons.gts +31 -0
- package/src/components/one-time-password/input.gts +198 -0
- package/src/components/one-time-password/otp.gts +130 -0
- package/src/components/one-time-password/utils.ts +201 -0
- package/src/components/one-time-password.gts +2 -0
- package/src/components/popover.gts +248 -0
- package/src/components/portal-targets.gts +136 -0
- package/src/components/portal.gts +194 -0
- package/src/components/progress.gts +154 -0
- package/src/components/rating/public-types.ts +44 -0
- package/src/components/rating/range.gts +22 -0
- package/src/components/rating/rating.gts +228 -0
- package/src/components/rating/stars.gts +60 -0
- package/src/components/rating/state.gts +144 -0
- package/src/components/rating/utils.ts +7 -0
- package/src/components/rating.gts +5 -0
- package/src/components/scroller.gts +179 -0
- package/src/components/shadowed.gts +110 -0
- package/src/components/switch.gts +103 -0
- package/src/components/tabs.gts +519 -0
- package/src/components/toggle-group.gts +265 -0
- package/src/components/toggle.gts +81 -0
- package/src/components/violations.css +105 -0
- package/src/components/violations.css.ts +1 -0
- package/src/components/visually-hidden.css +14 -0
- package/src/components/visually-hidden.gts +15 -0
- package/src/components/zoetrope/index.gts +358 -0
- package/src/components/zoetrope/styles.css +40 -0
- package/src/components/zoetrope/types.ts +65 -0
- package/src/components/zoetrope.ts +3 -0
- package/src/dom-context.gts +245 -0
- package/src/floating-ui/component.gts +186 -0
- package/src/floating-ui/middleware.ts +13 -0
- package/src/floating-ui/modifier.ts +183 -0
- package/src/floating-ui.ts +2 -0
- package/src/head.gts +37 -0
- package/src/helpers/body-class.ts +94 -0
- package/src/helpers/link.ts +125 -0
- package/src/helpers/service.ts +25 -0
- package/src/helpers.ts +2 -0
- package/src/iframe.ts +31 -0
- package/src/index.ts +43 -0
- package/src/load.gts +77 -0
- package/src/narrowing.ts +7 -0
- package/src/on-resize.ts +64 -0
- package/src/proper-links.ts +140 -0
- package/src/qp.ts +107 -0
- package/src/resize-observer.ts +132 -0
- package/src/service.ts +103 -0
- package/src/store.ts +72 -0
- package/src/styles.css.ts +5 -0
- package/src/tabster.ts +54 -0
- package/src/template-registry.ts +44 -0
- package/src/test-support/a11y.ts +50 -0
- package/src/test-support/dom.ts +112 -0
- package/src/test-support/otp.ts +64 -0
- package/src/test-support/rating.ts +144 -0
- package/src/test-support/routing.ts +62 -0
- package/src/test-support/zoetrope.ts +51 -0
- package/src/test-support.gts +6 -0
- package/src/type-utils.ts +1 -0
- package/src/utils.ts +75 -0
- package/src/viewport/in-viewport.gts +128 -0
- package/src/viewport/viewport.ts +122 -0
- package/src/viewport.ts +2 -0
- package/dist/component-Bs3N-G9z.js.map +0 -1
- package/dist/index-DKE67I8L.js.map +0 -1
- package/dist/otp-C6hCCXKx.js.map +0 -1
- package/dist/rating-D052JWRa.js +0 -149
- package/dist/rating-D052JWRa.js.map +0 -1
- package/dist/utils-C5796IKA.js.map +0 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import Component from "@glimmer/component";
|
|
2
|
+
import { hash } from "@ember/helper";
|
|
3
|
+
import { on } from "@ember/modifier";
|
|
4
|
+
|
|
5
|
+
import { uniqueId } from "../../utils.ts";
|
|
6
|
+
import { RatingRange } from "./range.gts";
|
|
7
|
+
import { Stars } from "./stars.gts";
|
|
8
|
+
import { RatingState } from "./state.gts";
|
|
9
|
+
|
|
10
|
+
import type { ComponentIcons, StringIcons } from "./public-types.ts";
|
|
11
|
+
import type { WithBoundArgs } from "@glint/template";
|
|
12
|
+
|
|
13
|
+
export interface Signature {
|
|
14
|
+
/*
|
|
15
|
+
* The element all passed attributes / modifiers are applied to.
|
|
16
|
+
*
|
|
17
|
+
* This is a `<fieldset>`, becaues the rating elements are
|
|
18
|
+
* powered by a group of radio buttons.
|
|
19
|
+
*/
|
|
20
|
+
Element: HTMLFieldSetElement;
|
|
21
|
+
Args: (ComponentIcons | StringIcons) & {
|
|
22
|
+
/**
|
|
23
|
+
* The number of stars/whichever-icon to show
|
|
24
|
+
*
|
|
25
|
+
* Defaults to 5
|
|
26
|
+
*/
|
|
27
|
+
max?: number;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The current number of stars/whichever-icon to show as selected
|
|
31
|
+
*
|
|
32
|
+
* Defaults to 0
|
|
33
|
+
*/
|
|
34
|
+
value?: number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* When generating the radio inputs, this changes what value of rating each radio
|
|
38
|
+
* input will be incremented by.
|
|
39
|
+
*
|
|
40
|
+
* e.g.: Set to 0.5 for half-star ratings.
|
|
41
|
+
*
|
|
42
|
+
* Defaults to 1
|
|
43
|
+
*/
|
|
44
|
+
step?: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Prevents click events on the icons and sets aria-readonly.
|
|
48
|
+
*
|
|
49
|
+
* Also sets data-readonly=true on the wrapping element
|
|
50
|
+
*/
|
|
51
|
+
readonly?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Toggles the ability to interact with the rating component.
|
|
55
|
+
* When `true` (the default), the Rating component can be as a form input
|
|
56
|
+
* to gather user feedback.
|
|
57
|
+
*
|
|
58
|
+
* When false, only the `@value` will be shown, and it cannot be changed.
|
|
59
|
+
*/
|
|
60
|
+
interactive?: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Callback when the selected rating changes.
|
|
64
|
+
* Can include half-ratings if the iconHalf argument is passed.
|
|
65
|
+
*/
|
|
66
|
+
onChange?: (value: number) => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
Blocks: {
|
|
70
|
+
default: [
|
|
71
|
+
rating: {
|
|
72
|
+
/**
|
|
73
|
+
* The maximum rating
|
|
74
|
+
*/
|
|
75
|
+
max: number;
|
|
76
|
+
/**
|
|
77
|
+
* The maxium rating
|
|
78
|
+
*/
|
|
79
|
+
total: number;
|
|
80
|
+
/**
|
|
81
|
+
* The current rating
|
|
82
|
+
*/
|
|
83
|
+
value: number;
|
|
84
|
+
/**
|
|
85
|
+
* The name shared by the field group
|
|
86
|
+
*/
|
|
87
|
+
name: string;
|
|
88
|
+
/**
|
|
89
|
+
* If the rating can be changed
|
|
90
|
+
*/
|
|
91
|
+
isReadonly: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* If the rating can be changed
|
|
94
|
+
*/
|
|
95
|
+
isChangeable: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* The stars / items radio group
|
|
98
|
+
*/
|
|
99
|
+
Stars: WithBoundArgs<
|
|
100
|
+
typeof Stars,
|
|
101
|
+
"stars" | "icon" | "isReadonly" | "name" | "total" | "currentValue"
|
|
102
|
+
>;
|
|
103
|
+
/**
|
|
104
|
+
* Input range for adjusting the rating via fractional means
|
|
105
|
+
*/
|
|
106
|
+
Range: WithBoundArgs<typeof RatingRange, "max" | "value" | "name" | "handleChange">;
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
label: [
|
|
110
|
+
state: {
|
|
111
|
+
/**
|
|
112
|
+
* The current rating
|
|
113
|
+
*/
|
|
114
|
+
value: number;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The maximum rating
|
|
118
|
+
*/
|
|
119
|
+
total: number;
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class Rating extends Component<Signature> {
|
|
126
|
+
name = `rating-${uniqueId()}`;
|
|
127
|
+
|
|
128
|
+
get icon() {
|
|
129
|
+
return this.args.icon ?? "★";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get isInteractive() {
|
|
133
|
+
return this.args.interactive ?? true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get isChangeable() {
|
|
137
|
+
const readonly = this.args.readonly ?? false;
|
|
138
|
+
|
|
139
|
+
return !readonly && this.isInteractive;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
get isReadonly() {
|
|
143
|
+
return !this.isChangeable;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get needsDescription() {
|
|
147
|
+
return !this.isInteractive;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
<template>
|
|
151
|
+
<RatingState
|
|
152
|
+
@max={{@max}}
|
|
153
|
+
@step={{@step}}
|
|
154
|
+
@value={{@value}}
|
|
155
|
+
@name={{this.name}}
|
|
156
|
+
@readonly={{this.isReadonly}}
|
|
157
|
+
@onChange={{@onChange}}
|
|
158
|
+
as |r publicState|
|
|
159
|
+
>
|
|
160
|
+
<fieldset
|
|
161
|
+
class="ember-primitives__rating"
|
|
162
|
+
data-total={{r.total}}
|
|
163
|
+
data-value={{r.value}}
|
|
164
|
+
data-readonly={{this.isReadonly}}
|
|
165
|
+
{{! We use event delegation, this isn't a primary interactive -- we're capturing events from inputs }}
|
|
166
|
+
{{! template-lint-disable no-invalid-interactive }}
|
|
167
|
+
{{on "click" r.handleClick}}
|
|
168
|
+
...attributes
|
|
169
|
+
>
|
|
170
|
+
{{#let
|
|
171
|
+
(component
|
|
172
|
+
Stars
|
|
173
|
+
stars=r.stars
|
|
174
|
+
icon=this.icon
|
|
175
|
+
isReadonly=this.isReadonly
|
|
176
|
+
name=this.name
|
|
177
|
+
total=r.total
|
|
178
|
+
currentValue=r.value
|
|
179
|
+
)
|
|
180
|
+
as |RatingStars|
|
|
181
|
+
}}
|
|
182
|
+
|
|
183
|
+
{{#if (has-block)}}
|
|
184
|
+
{{yield
|
|
185
|
+
(hash
|
|
186
|
+
max=r.total
|
|
187
|
+
total=r.total
|
|
188
|
+
value=r.value
|
|
189
|
+
name=this.name
|
|
190
|
+
isReadonly=this.isReadonly
|
|
191
|
+
isChangeable=this.isChangeable
|
|
192
|
+
Stars=RatingStars
|
|
193
|
+
Range=(component
|
|
194
|
+
RatingRange
|
|
195
|
+
step=r.step
|
|
196
|
+
max=r.total
|
|
197
|
+
value=r.value
|
|
198
|
+
name=this.name
|
|
199
|
+
handleChange=r.handleChange
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
}}
|
|
203
|
+
{{else}}
|
|
204
|
+
{{#if this.needsDescription}}
|
|
205
|
+
{{#if (has-block "label")}}
|
|
206
|
+
{{yield publicState to="label"}}
|
|
207
|
+
{{else}}
|
|
208
|
+
<span visually-hidden class="ember-primitives__rating__label">Rated
|
|
209
|
+
{{r.value}}
|
|
210
|
+
out of
|
|
211
|
+
{{r.total}}</span>
|
|
212
|
+
{{/if}}
|
|
213
|
+
{{else}}
|
|
214
|
+
{{#if (has-block "label")}}
|
|
215
|
+
<legend>
|
|
216
|
+
{{yield publicState to="label"}}
|
|
217
|
+
</legend>
|
|
218
|
+
{{/if}}
|
|
219
|
+
{{/if}}
|
|
220
|
+
|
|
221
|
+
<RatingStars />
|
|
222
|
+
{{/if}}
|
|
223
|
+
{{/let}}
|
|
224
|
+
|
|
225
|
+
</fieldset>
|
|
226
|
+
</RatingState>
|
|
227
|
+
</template>
|
|
228
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { uniqueId } from "../../utils.ts";
|
|
2
|
+
import { isString, lte } from "./utils.ts";
|
|
3
|
+
|
|
4
|
+
import type { ComponentIcons, StringIcons } from "./public-types.ts";
|
|
5
|
+
import type { TOC } from "@ember/component/template-only";
|
|
6
|
+
|
|
7
|
+
export const Stars: TOC<{
|
|
8
|
+
Args: {
|
|
9
|
+
// Configuration
|
|
10
|
+
stars: number[];
|
|
11
|
+
icon: StringIcons["icon"] | ComponentIcons["icon"];
|
|
12
|
+
isReadonly: boolean;
|
|
13
|
+
|
|
14
|
+
// HTML Boilerplate
|
|
15
|
+
name: string;
|
|
16
|
+
|
|
17
|
+
// State
|
|
18
|
+
currentValue: number;
|
|
19
|
+
total: number;
|
|
20
|
+
};
|
|
21
|
+
}> = <template>
|
|
22
|
+
<div class="ember-primitives__rating__items">
|
|
23
|
+
{{#each @stars as |star|}}
|
|
24
|
+
{{#let (uniqueId) as |id|}}
|
|
25
|
+
<span
|
|
26
|
+
class="ember-primitives__rating__item"
|
|
27
|
+
data-number={{star}}
|
|
28
|
+
data-selected={{lte star @currentValue}}
|
|
29
|
+
data-readonly={{@isReadonly}}
|
|
30
|
+
>
|
|
31
|
+
<label for="input-{{id}}">
|
|
32
|
+
<span visually-hidden>{{star}} star</span>
|
|
33
|
+
{{#if @icon}}
|
|
34
|
+
<span aria-hidden="true">
|
|
35
|
+
{{#if (isString @icon)}}
|
|
36
|
+
{{@icon}}
|
|
37
|
+
{{else}}
|
|
38
|
+
<@icon
|
|
39
|
+
@value={{star}}
|
|
40
|
+
@isSelected={{lte star @currentValue}}
|
|
41
|
+
@readonly={{@isReadonly}}
|
|
42
|
+
/>
|
|
43
|
+
{{/if}}
|
|
44
|
+
</span>
|
|
45
|
+
{{/if}}
|
|
46
|
+
</label>
|
|
47
|
+
|
|
48
|
+
<input
|
|
49
|
+
id="input-{{id}}"
|
|
50
|
+
type="radio"
|
|
51
|
+
name={{@name}}
|
|
52
|
+
value={{star}}
|
|
53
|
+
readonly={{@isReadonly}}
|
|
54
|
+
checked={{Object.is star @currentValue}}
|
|
55
|
+
/>
|
|
56
|
+
</span>
|
|
57
|
+
{{/let}}
|
|
58
|
+
{{/each}}
|
|
59
|
+
</div>
|
|
60
|
+
</template>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import Component from "@glimmer/component";
|
|
2
|
+
import { cached } from "@glimmer/tracking";
|
|
3
|
+
import { assert } from "@ember/debug";
|
|
4
|
+
import { hash } from "@ember/helper";
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
7
|
+
// @ts-expect-error
|
|
8
|
+
import { localCopy } from "tracked-toolbox";
|
|
9
|
+
|
|
10
|
+
export class RatingState extends Component<{
|
|
11
|
+
Args: {
|
|
12
|
+
max: number | undefined;
|
|
13
|
+
value: number | undefined;
|
|
14
|
+
step: number | undefined;
|
|
15
|
+
readonly: boolean | undefined;
|
|
16
|
+
name: string;
|
|
17
|
+
onChange?: (value: number) => void;
|
|
18
|
+
};
|
|
19
|
+
Blocks: {
|
|
20
|
+
default: [
|
|
21
|
+
internalApi: {
|
|
22
|
+
stars: number[];
|
|
23
|
+
step: number;
|
|
24
|
+
value: number;
|
|
25
|
+
total: number;
|
|
26
|
+
handleClick: (event: Event) => void;
|
|
27
|
+
handleChange: (event: Event) => void;
|
|
28
|
+
setRating: (num: number) => void;
|
|
29
|
+
},
|
|
30
|
+
publicApi: {
|
|
31
|
+
value: number;
|
|
32
|
+
total: number;
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
};
|
|
36
|
+
}> {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
38
|
+
@localCopy("args.value") declare _value: number;
|
|
39
|
+
|
|
40
|
+
get value() {
|
|
41
|
+
return this._value ?? 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get step() {
|
|
45
|
+
return this.args.step ?? 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get max() {
|
|
49
|
+
return this.args.max ?? 5;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@cached
|
|
53
|
+
get stars() {
|
|
54
|
+
const result = [];
|
|
55
|
+
|
|
56
|
+
// 0 is "none selected"
|
|
57
|
+
let current = 0;
|
|
58
|
+
|
|
59
|
+
current += this.step;
|
|
60
|
+
|
|
61
|
+
while (current <= this.max) {
|
|
62
|
+
result.push(current);
|
|
63
|
+
current += this.step;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setRating = (value: number) => {
|
|
70
|
+
if (this.args.readonly) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (value === this._value) {
|
|
75
|
+
this._value = 0;
|
|
76
|
+
} else {
|
|
77
|
+
this._value = value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.args.onChange?.(value);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
setFromString = (value: unknown) => {
|
|
84
|
+
assert("[BUG]: value from input must be a string.", typeof value === "string");
|
|
85
|
+
|
|
86
|
+
const num = parseFloat(value);
|
|
87
|
+
|
|
88
|
+
if (isNaN(num)) {
|
|
89
|
+
// something went wrong.
|
|
90
|
+
// Since we're using event delegation,
|
|
91
|
+
// this could be from an unrelated input
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.setRating(num);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Click events are captured by
|
|
100
|
+
* - radio changes (mouse and keyboard)
|
|
101
|
+
* - but only range clicks
|
|
102
|
+
*/
|
|
103
|
+
handleClick = (event: Event) => {
|
|
104
|
+
// Since we're doing event delegation on a click, we want to make sure
|
|
105
|
+
// we don't do anything on other elements
|
|
106
|
+
const isValid =
|
|
107
|
+
event.target instanceof HTMLInputElement &&
|
|
108
|
+
event.target.name === this.args.name &&
|
|
109
|
+
event.target.type === "radio";
|
|
110
|
+
|
|
111
|
+
if (!isValid) return;
|
|
112
|
+
|
|
113
|
+
const selected = event.target?.value;
|
|
114
|
+
|
|
115
|
+
this.setFromString(selected);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Only attached to a range element, if present.
|
|
120
|
+
* Range elements don't fire click events on keyboard usage, like radios do
|
|
121
|
+
*/
|
|
122
|
+
handleChange = (event: Event) => {
|
|
123
|
+
const isValid = event.target !== null && "value" in event.target;
|
|
124
|
+
|
|
125
|
+
if (!isValid) return;
|
|
126
|
+
|
|
127
|
+
this.setFromString(event.target.value);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
<template>
|
|
131
|
+
{{yield
|
|
132
|
+
(hash
|
|
133
|
+
stars=this.stars
|
|
134
|
+
total=this.stars.length
|
|
135
|
+
handleClick=this.handleClick
|
|
136
|
+
handleChange=this.handleChange
|
|
137
|
+
setRating=this.setRating
|
|
138
|
+
value=this.value
|
|
139
|
+
step=this.step
|
|
140
|
+
)
|
|
141
|
+
(hash total=this.stars.length value=this.value)
|
|
142
|
+
}}
|
|
143
|
+
</template>
|
|
144
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import Component from "@glimmer/component";
|
|
2
|
+
import { isDestroyed, isDestroying } from "@ember/destroyable";
|
|
3
|
+
import { hash } from "@ember/helper";
|
|
4
|
+
|
|
5
|
+
import { modifier } from "ember-modifier";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Utility component for helping with scrolling in any direction within
|
|
9
|
+
* any of the 4 directions: up, down, left, right.
|
|
10
|
+
*
|
|
11
|
+
* This can be used to auto-scroll content as new content is inserted into the scrollable area, or possibly to bring focus to something on the page.
|
|
12
|
+
*/
|
|
13
|
+
export class Scroller extends Component<{
|
|
14
|
+
/**
|
|
15
|
+
* A containing element is required - in this case, a div.
|
|
16
|
+
* It must be scrollable for this component to work, but can be customized.
|
|
17
|
+
*
|
|
18
|
+
* By default, this element will have some styling applied:
|
|
19
|
+
* overflow: auto;
|
|
20
|
+
*
|
|
21
|
+
* By default, this element will have tabindex="0" to support keyboard usage.
|
|
22
|
+
*
|
|
23
|
+
* The scroll-behavior is "auto", which can be controlled via CSS
|
|
24
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
Element: HTMLDivElement;
|
|
28
|
+
Blocks: {
|
|
29
|
+
default: [
|
|
30
|
+
{
|
|
31
|
+
/**
|
|
32
|
+
* Scroll the content to the bottom
|
|
33
|
+
*
|
|
34
|
+
* ```gjs
|
|
35
|
+
* import { Scroller } from 'ember-primitives';
|
|
36
|
+
*
|
|
37
|
+
* <template>
|
|
38
|
+
* <Scroller as |s|>
|
|
39
|
+
* ...
|
|
40
|
+
*
|
|
41
|
+
* {{ (s.scrollToBottom) }}
|
|
42
|
+
* </Scroller>
|
|
43
|
+
* </template>
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
scrollToBottom: () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Scroll the content to the top
|
|
49
|
+
*
|
|
50
|
+
* ```gjs
|
|
51
|
+
* import { Scroller } from 'ember-primitives';
|
|
52
|
+
*
|
|
53
|
+
* <template>
|
|
54
|
+
* <Scroller as |s|>
|
|
55
|
+
* ...
|
|
56
|
+
*
|
|
57
|
+
* {{ (s.scrollToTop) }}
|
|
58
|
+
* </Scroller>
|
|
59
|
+
* </template>
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
scrollToTop: () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Scroll the content to the left
|
|
65
|
+
*
|
|
66
|
+
* ```gjs
|
|
67
|
+
* import { Scroller } from 'ember-primitives';
|
|
68
|
+
*
|
|
69
|
+
* <template>
|
|
70
|
+
* <Scroller as |s|>
|
|
71
|
+
* ...
|
|
72
|
+
*
|
|
73
|
+
* {{ (s.scrollToLeft) }}
|
|
74
|
+
* </Scroller>
|
|
75
|
+
* </template>
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
scrollToLeft: () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Scroll the content to the right
|
|
81
|
+
*
|
|
82
|
+
* ```gjs
|
|
83
|
+
* import { Scroller } from 'ember-primitives';
|
|
84
|
+
*
|
|
85
|
+
* <template>
|
|
86
|
+
* <Scroller as |s|>
|
|
87
|
+
* ...
|
|
88
|
+
*
|
|
89
|
+
* {{ (s.scrollToRight) }}
|
|
90
|
+
* </Scroller>
|
|
91
|
+
* </template>
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
scrollToRight: () => void;
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
};
|
|
98
|
+
}> {
|
|
99
|
+
declare withinElement: HTMLDivElement;
|
|
100
|
+
|
|
101
|
+
ref = modifier((el: HTMLDivElement) => {
|
|
102
|
+
this.withinElement = el;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
#frame?: number;
|
|
106
|
+
|
|
107
|
+
scrollToBottom = () => {
|
|
108
|
+
if (this.#frame) {
|
|
109
|
+
cancelAnimationFrame(this.#frame);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.#frame = requestAnimationFrame(() => {
|
|
113
|
+
if (isDestroyed(this) || isDestroying(this)) return;
|
|
114
|
+
|
|
115
|
+
this.withinElement.scrollTo({
|
|
116
|
+
top: this.withinElement.scrollHeight,
|
|
117
|
+
behavior: "auto",
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
scrollToTop = () => {
|
|
123
|
+
if (this.#frame) {
|
|
124
|
+
cancelAnimationFrame(this.#frame);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.#frame = requestAnimationFrame(() => {
|
|
128
|
+
if (isDestroyed(this) || isDestroying(this)) return;
|
|
129
|
+
|
|
130
|
+
this.withinElement.scrollTo({
|
|
131
|
+
top: 0,
|
|
132
|
+
behavior: "auto",
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
scrollToLeft = () => {
|
|
138
|
+
if (this.#frame) {
|
|
139
|
+
cancelAnimationFrame(this.#frame);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.#frame = requestAnimationFrame(() => {
|
|
143
|
+
if (isDestroyed(this) || isDestroying(this)) return;
|
|
144
|
+
|
|
145
|
+
this.withinElement.scrollTo({
|
|
146
|
+
left: 0,
|
|
147
|
+
behavior: "auto",
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
scrollToRight = () => {
|
|
153
|
+
if (this.#frame) {
|
|
154
|
+
cancelAnimationFrame(this.#frame);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.#frame = requestAnimationFrame(() => {
|
|
158
|
+
if (isDestroyed(this) || isDestroying(this)) return;
|
|
159
|
+
|
|
160
|
+
this.withinElement.scrollTo({
|
|
161
|
+
left: this.withinElement.scrollWidth,
|
|
162
|
+
behavior: "auto",
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
<template>
|
|
168
|
+
<div tabindex="0" ...attributes {{this.ref}}>
|
|
169
|
+
{{yield
|
|
170
|
+
(hash
|
|
171
|
+
scrollToBottom=this.scrollToBottom
|
|
172
|
+
scrollToTop=this.scrollToTop
|
|
173
|
+
scrollToLeft=this.scrollToLeft
|
|
174
|
+
scrollToRight=this.scrollToRight
|
|
175
|
+
)
|
|
176
|
+
}}
|
|
177
|
+
</div>
|
|
178
|
+
</template>
|
|
179
|
+
}
|