@stanko/ctrls 0.1.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/README.md +359 -0
- package/dist/ctrls/ctrl-boolean.d.ts +22 -0
- package/dist/ctrls/ctrl-boolean.js +67 -0
- package/dist/ctrls/ctrl-dual-range.d.ts +47 -0
- package/dist/ctrls/ctrl-dual-range.js +113 -0
- package/dist/ctrls/ctrl-easing.d.ts +37 -0
- package/dist/ctrls/ctrl-easing.js +288 -0
- package/dist/ctrls/ctrl-radio.d.ts +28 -0
- package/dist/ctrls/ctrl-radio.js +83 -0
- package/dist/ctrls/ctrl-range.d.ts +25 -0
- package/dist/ctrls/ctrl-range.js +83 -0
- package/dist/ctrls/ctrl-seed.d.ts +22 -0
- package/dist/ctrls/ctrl-seed.js +79 -0
- package/dist/ctrls/index.d.ts +114 -0
- package/dist/ctrls/index.js +158 -0
- package/dist/ctrls.css +716 -0
- package/dist/ctrls.css.map +1 -0
- package/dist/utils/alea.d.ts +2 -0
- package/dist/utils/alea.js +41 -0
- package/dist/utils/generate-seed.d.ts +1 -0
- package/dist/utils/generate-seed.js +10 -0
- package/dist/utils/icons.d.ts +3 -0
- package/dist/utils/icons.js +4 -0
- package/dist/utils/random.d.ts +2 -0
- package/dist/utils/random.js +7 -0
- package/dist/utils/round-to-step.d.ts +1 -0
- package/dist/utils/round-to-step.js +5 -0
- package/dist/utils/string-utils.d.ts +3 -0
- package/dist/utils/string-utils.js +9 -0
- package/dist/utils/words.d.ts +1 -0
- package/dist/utils/words.js +1954 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Ctrls
|
|
2
|
+
|
|
3
|
+
## Made for algorithmic art
|
|
4
|
+
|
|
5
|
+
I made Ctrls for my [algorithmic art projects](https://muffinman.io/art/). By default, the state is saved in the URL, which lets you navigate history and easily share links. Besides standard components like checkboxes and ranges, it also includes two that are especially useful for generative projects: RNG seed and easing. These not only let you adjust parameters but also provide functions (`rng` and `easing`) that you can call directly.
|
|
6
|
+
|
|
7
|
+
You can play with the live example above or check the older version on my [Space Invaders generator](https://muffinman.io/invaders/).
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Minimal API
|
|
12
|
+
- Six components (RNG seed, easing, boolean, radio, range and dual range)
|
|
13
|
+
- Fully typed, including the dynamic typings for properties it controls
|
|
14
|
+
- Light and dark themes included
|
|
15
|
+
- Easy to theme through CSS variables
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
Install the library:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @stanko/ctrls
|
|
23
|
+
````
|
|
24
|
+
|
|
25
|
+
Define your controls as an array and instantiate a `Ctrls` class:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Ctrls } from "@stanko/ctrls";
|
|
29
|
+
import type { TypedControlConfig } from "@stanko/ctrls";
|
|
30
|
+
|
|
31
|
+
// Import CSS
|
|
32
|
+
import "@stanko/ctrls/dist/ctrls.css";
|
|
33
|
+
|
|
34
|
+
// Define configuration for controls
|
|
35
|
+
const config = [
|
|
36
|
+
{
|
|
37
|
+
type: "boolean",
|
|
38
|
+
name: "animate",
|
|
39
|
+
defaultValue: true,
|
|
40
|
+
isRandomizationDisabled: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: "seed",
|
|
44
|
+
name: "opacitySeed",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "range",
|
|
48
|
+
name: "hue",
|
|
49
|
+
defaultValue: 220,
|
|
50
|
+
min: 0,
|
|
51
|
+
max: 360,
|
|
52
|
+
step: 1,
|
|
53
|
+
},
|
|
54
|
+
// in the demo above there are three more components:
|
|
55
|
+
// - speed, shape and size
|
|
56
|
+
|
|
57
|
+
// Casting the config to enable editor's code completion
|
|
58
|
+
] as const satisfies readonly TypedControlConfig[];
|
|
59
|
+
|
|
60
|
+
// Create the instance of Ctrls
|
|
61
|
+
export const options = new Ctrls(config, {
|
|
62
|
+
showRandomizeButton: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export type Options = ReturnValue<options.getValues()>;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Add your handlers. Ctrls provides `onChange` and `onInput` methods.
|
|
69
|
+
|
|
70
|
+
To get the current values, use `.getValues()`.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
options.onChange = (updatedValues: Partial<Options>) => {
|
|
74
|
+
// Example: re-render your drawing
|
|
75
|
+
render(options.getValues()); // Get all values and pass it to the render method
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
options.onInput = (updatedValues: Partial<Options>) => {
|
|
79
|
+
// Example: update a CSS variable
|
|
80
|
+
if (updatedValues.hue) {
|
|
81
|
+
document.body.style.setProperty("--hue", updatedValues.hue);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API
|
|
87
|
+
|
|
88
|
+
To instantiate:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
type ControlsOptions = {
|
|
92
|
+
showRandomizeButton?: boolean;
|
|
93
|
+
storage?: "hash" | null;
|
|
94
|
+
theme?: "system" | "light" | "dark";
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Public API:
|
|
99
|
+
|
|
100
|
+
* `element`
|
|
101
|
+
* `onChange`
|
|
102
|
+
* `onInput`
|
|
103
|
+
* `getValues`
|
|
104
|
+
* `randomize`
|
|
105
|
+
|
|
106
|
+
But technically all of the methods are public. I've done this on purpose to let you experiment.
|
|
107
|
+
|
|
108
|
+
## Controls
|
|
109
|
+
|
|
110
|
+
All controls share the following properties:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
{
|
|
114
|
+
// --- Mandatory --- //
|
|
115
|
+
type: CtrlType; // "seed" | "easing" | "boolean" | "range" | "dual-range" | "radio"
|
|
116
|
+
name: string;
|
|
117
|
+
|
|
118
|
+
// --- Optional --- //
|
|
119
|
+
// If passed it will be used instead of the name
|
|
120
|
+
label?: string;
|
|
121
|
+
// Depends on the control type, if it is not passed it will be randomly generated
|
|
122
|
+
defaultValue?: T;
|
|
123
|
+
// Should the value be randomized when randomize button is clicked
|
|
124
|
+
isRandomizationDisabled?: boolean; // default: false
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Some controls will have a few more properties explained below.
|
|
129
|
+
|
|
130
|
+
### Boolean
|
|
131
|
+
|
|
132
|
+
A checkbox control for boolean parameter.
|
|
133
|
+
|
|
134
|
+
<div class="example">
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"type": "boolean",
|
|
139
|
+
"name": "debug"
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
### Seed
|
|
146
|
+
|
|
147
|
+
A text input which generates a random text seed and creates a random number generator using the seed.
|
|
148
|
+
|
|
149
|
+
<div class="example">
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"type": "seed",
|
|
154
|
+
"name": "mainSeed"
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
A seeded RNG will be created and it will be included in the values. For the example above the return value of `.getValues()` would look like this:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
{
|
|
164
|
+
mainSeed: "a-random-seed-value",
|
|
165
|
+
mainSeedRng: () => number;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Easing
|
|
170
|
+
|
|
171
|
+
Cubic-bezier easing control which also creates easing function you can use directly. It includes five presets, which can be changed through the `presets` property. To disable presets pass an empty array.
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
{
|
|
175
|
+
// Optional
|
|
176
|
+
presets?: Record<string, [number, number, number, number]>; // default: 1
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
|
|
182
|
+
<div class="example">
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"type": "easing",
|
|
187
|
+
"name": "distribution",
|
|
188
|
+
"defaultValue": [0.8, 0.1, 0.2, 0.9],
|
|
189
|
+
"presets": {
|
|
190
|
+
"ease_in": [0.42, 0, 1, 1],
|
|
191
|
+
"ease_out": [0, 0, 0.58, 1],
|
|
192
|
+
"ease_in_out": [0.42, 0, 0.58, 1]
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
Please note that the string `ease_` will be removed from the preset labels.
|
|
200
|
+
|
|
201
|
+
An easing function will be created and it will be included in the values. For the example above the return value of `.getValues()` would look like this:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
{
|
|
205
|
+
distribution: [0.8, 0.1, 0.2, 0.9],
|
|
206
|
+
distributionEasing: (t: number) => number;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Range
|
|
211
|
+
|
|
212
|
+
A range slider that controls a number parameter. Accepts minimum, maximum and step values. It includes a few additional properties:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
{
|
|
216
|
+
// Mandatory
|
|
217
|
+
min: number;
|
|
218
|
+
max: number;
|
|
219
|
+
|
|
220
|
+
// Optional
|
|
221
|
+
step?: number; // default: 1
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
|
|
227
|
+
<div class="example">
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"type": "range",
|
|
232
|
+
"name": "width",
|
|
233
|
+
"defaultValue": 5,
|
|
234
|
+
"min": 0,
|
|
235
|
+
"max": 10,
|
|
236
|
+
"step": 1
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
### Dual range
|
|
243
|
+
|
|
244
|
+
A range input with two handles that control minimum and maximum values. Includes the same additional properties as the range input.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
{
|
|
248
|
+
// Mandatory
|
|
249
|
+
min: number;
|
|
250
|
+
max: number;
|
|
251
|
+
|
|
252
|
+
// Optional
|
|
253
|
+
step?: number; // default: 1
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
|
|
259
|
+
<div class="example">
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"type": "dual-range",
|
|
264
|
+
"name": "size",
|
|
265
|
+
"defaultValue": { "min": 3, "max": 7 },
|
|
266
|
+
"min": 0,
|
|
267
|
+
"max": 10,
|
|
268
|
+
"step": 1
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
### Radio
|
|
275
|
+
|
|
276
|
+
A grid of radio buttons. Can be set to have between one and five columns.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
{
|
|
280
|
+
// Mandatory
|
|
281
|
+
items: Record<string, string>; // Map of radio items (key, value)
|
|
282
|
+
|
|
283
|
+
// Optional
|
|
284
|
+
columns?: number; // default: 3
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
|
|
290
|
+
<div class="example">
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"type": "radio",
|
|
295
|
+
"name": "main shape",
|
|
296
|
+
"items": {
|
|
297
|
+
"triangle": "3",
|
|
298
|
+
"rectangle": "4",
|
|
299
|
+
"hexagon": "6",
|
|
300
|
+
"octagon": "8",
|
|
301
|
+
"circle": "32"
|
|
302
|
+
},
|
|
303
|
+
"columns": 2
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
## Theming
|
|
310
|
+
|
|
311
|
+
Ctrls relies on CSS variables for theming. There is a lot of variables you can tweak, but I suggest starting with these four:
|
|
312
|
+
|
|
313
|
+
```css
|
|
314
|
+
--ctrls-c: 0.7;
|
|
315
|
+
--ctrls-h: 220;
|
|
316
|
+
--ctrls-radius: 4px;
|
|
317
|
+
--ctrls-range-thumb-radius: 4px;
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
<div class="theme-controls">
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
## Why?
|
|
324
|
+
|
|
325
|
+
You might ask why I made another library when [dat.GUI](https://github.com/dataarts/dat.gui) and [TweakPane](https://tweakpane.github.io/docs/) already exist. They both solve a general use case, with extensive options and elaborate APIs, while I wanted something smaller and easier to adapt for [my algorithmic drawings](https://muffinman.io/art/).
|
|
326
|
+
|
|
327
|
+
I originally built a crude library with no plans to release it. Over time I polished it, ported it to TypeScript, and realized it might actually be useful to others. The code is solid, though I would love to add a few more features.
|
|
328
|
+
|
|
329
|
+
Ctrls is a very opinionated library tailored to my likings. I'm open to suggestions, but they need to fit algorithmic art workflows and align with my vision. It's not meant to be a general-purpose library - others already cover that space well.
|
|
330
|
+
|
|
331
|
+
I also want to mention that I really like TweakPane, and Ctrls' homepage is heavily influenced by it.
|
|
332
|
+
|
|
333
|
+
## Cheers!
|
|
334
|
+
|
|
335
|
+
Thank you for stopping by! If you end up using Ctrls, please let me know, I would love to see it.
|
|
336
|
+
|
|
337
|
+
## TODO
|
|
338
|
+
|
|
339
|
+
* [ ] Demo - favicon and metadata
|
|
340
|
+
* [ ] Readme - screenshots
|
|
341
|
+
* [ ] Prefix input names' with `ctrl_${id}_${name}` to avoid collisions
|
|
342
|
+
* [ ] Allow users to pass a custom PRNG lib
|
|
343
|
+
* [ ] Hash storage - check if there is an instance using hash storage already
|
|
344
|
+
* [ ] Demo - stop animation when not in viewport
|
|
345
|
+
* [ ] Add title which collapses the controls
|
|
346
|
+
* [ ] Storage - local storage
|
|
347
|
+
* [x] Use web safe / system fonts by default
|
|
348
|
+
* [x] Experiment with chroma for light and dark shades of the main color
|
|
349
|
+
* [x] On input event / handler
|
|
350
|
+
* [x] Revisit naming `controls` vs `options` vs `values`
|
|
351
|
+
* [x] Remove lucide as a dependency, swap with local SVGs
|
|
352
|
+
* [x] Add Aleas PRNG instead of seedrandom
|
|
353
|
+
* [x] Storage options - none
|
|
354
|
+
* [x] Fix get random value float point error in dual range (modulo with floats)
|
|
355
|
+
* [x] Easing - fix handles jumping to previous positions after selecting preset (no storage)
|
|
356
|
+
* [x] Scope the CSS
|
|
357
|
+
* [x] Controls -> Ctrls
|
|
358
|
+
* [x] Easing - option to pass custom presets (or an empty array to disable them)
|
|
359
|
+
* [x] onChange - maybe add current options (?) - decided not to do
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Ctrl, CtrlType, CtrlChangeHandler, CtrlConfig } from ".";
|
|
2
|
+
export declare class BooleanCtrl implements Ctrl<boolean> {
|
|
3
|
+
type: CtrlType;
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
value: boolean;
|
|
7
|
+
isRandomizationDisabled: boolean;
|
|
8
|
+
onChange: CtrlChangeHandler<boolean>;
|
|
9
|
+
onInput: CtrlChangeHandler<boolean>;
|
|
10
|
+
element: HTMLElement;
|
|
11
|
+
input: HTMLInputElement;
|
|
12
|
+
constructor(config: CtrlConfig<boolean>, onChange: CtrlChangeHandler<boolean>, onInput: CtrlChangeHandler<boolean>);
|
|
13
|
+
parse: (string: string) => string is "true";
|
|
14
|
+
getRandomValue: () => boolean;
|
|
15
|
+
getDefaultValue: () => boolean;
|
|
16
|
+
valueToString: (value?: boolean) => string;
|
|
17
|
+
buildUI: () => {
|
|
18
|
+
element: HTMLLabelElement;
|
|
19
|
+
input: HTMLInputElement;
|
|
20
|
+
};
|
|
21
|
+
update: (value: boolean) => void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { checkIcon } from "../utils/icons";
|
|
2
|
+
export class BooleanCtrl {
|
|
3
|
+
constructor(config, onChange, onInput) {
|
|
4
|
+
this.type = "boolean";
|
|
5
|
+
this.parse = (string) => {
|
|
6
|
+
return string === "true";
|
|
7
|
+
};
|
|
8
|
+
this.getRandomValue = () => {
|
|
9
|
+
return Math.random() > 0.5;
|
|
10
|
+
};
|
|
11
|
+
this.getDefaultValue = () => {
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
14
|
+
this.valueToString = (value = this.value) => {
|
|
15
|
+
return value.toString();
|
|
16
|
+
};
|
|
17
|
+
this.buildUI = () => {
|
|
18
|
+
const input = document.createElement("input");
|
|
19
|
+
input.classList.add("ctrls__boolean-input");
|
|
20
|
+
input.setAttribute("type", "checkbox");
|
|
21
|
+
input.checked = this.value;
|
|
22
|
+
input.addEventListener("change", () => {
|
|
23
|
+
this.value = input.checked;
|
|
24
|
+
this.onChange(this.name, this.value);
|
|
25
|
+
});
|
|
26
|
+
input.addEventListener("input", () => {
|
|
27
|
+
this.value = input.checked;
|
|
28
|
+
this.onInput(this.name, this.value);
|
|
29
|
+
});
|
|
30
|
+
const checkmark = document.createElement("span");
|
|
31
|
+
checkmark.classList.add("ctrls__boolean-checkmark");
|
|
32
|
+
checkmark.innerHTML = checkIcon;
|
|
33
|
+
const right = document.createElement("div");
|
|
34
|
+
right.classList.add("ctrls__control-right");
|
|
35
|
+
right.appendChild(input);
|
|
36
|
+
right.appendChild(checkmark);
|
|
37
|
+
const label = document.createElement("span");
|
|
38
|
+
label.textContent = this.label;
|
|
39
|
+
label.classList.add("ctrls__control-label");
|
|
40
|
+
const element = document.createElement("label");
|
|
41
|
+
element.classList.add("ctrls__control", "ctrls__control--boolean");
|
|
42
|
+
element.appendChild(label);
|
|
43
|
+
element.appendChild(right);
|
|
44
|
+
return {
|
|
45
|
+
element,
|
|
46
|
+
input,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
this.update = (value) => {
|
|
50
|
+
this.value = value;
|
|
51
|
+
this.input.checked = this.value;
|
|
52
|
+
};
|
|
53
|
+
this.type = "boolean";
|
|
54
|
+
this.name = config.name;
|
|
55
|
+
this.label = config.label || config.name;
|
|
56
|
+
this.value =
|
|
57
|
+
config.defaultValue === undefined
|
|
58
|
+
? this.getDefaultValue()
|
|
59
|
+
: config.defaultValue;
|
|
60
|
+
this.isRandomizationDisabled = config.isRandomizationDisabled || false;
|
|
61
|
+
this.onChange = onChange;
|
|
62
|
+
this.onInput = onInput;
|
|
63
|
+
const { input, element } = this.buildUI();
|
|
64
|
+
this.input = input;
|
|
65
|
+
this.element = element;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import DualRangeInput from "@stanko/dual-range-input";
|
|
2
|
+
import type { Ctrl, CtrlChangeHandler, CtrlType, CtrlTypeRegistry } from ".";
|
|
3
|
+
export type DualRangeControlOptions = {
|
|
4
|
+
min: number;
|
|
5
|
+
max: number;
|
|
6
|
+
step?: number;
|
|
7
|
+
};
|
|
8
|
+
export type DualRangeValue = {
|
|
9
|
+
min: number;
|
|
10
|
+
max: number;
|
|
11
|
+
};
|
|
12
|
+
export declare class DualRangeCtrl implements Ctrl<DualRangeValue> {
|
|
13
|
+
type: CtrlType;
|
|
14
|
+
name: string;
|
|
15
|
+
label: string;
|
|
16
|
+
value: DualRangeValue;
|
|
17
|
+
isRandomizationDisabled: boolean;
|
|
18
|
+
onChange: CtrlChangeHandler<DualRangeValue>;
|
|
19
|
+
onInput: CtrlChangeHandler<DualRangeValue>;
|
|
20
|
+
min: number;
|
|
21
|
+
max: number;
|
|
22
|
+
step: number;
|
|
23
|
+
element: HTMLElement;
|
|
24
|
+
minInput: HTMLInputElement;
|
|
25
|
+
maxInput: HTMLInputElement;
|
|
26
|
+
dualRange: DualRangeInput;
|
|
27
|
+
constructor(config: CtrlTypeRegistry["dual-range"]["config"], onChange: CtrlChangeHandler<DualRangeValue>, onInput: CtrlChangeHandler<DualRangeValue>);
|
|
28
|
+
parse: (string: string) => {
|
|
29
|
+
min: number;
|
|
30
|
+
max: number;
|
|
31
|
+
};
|
|
32
|
+
getRandomValue: () => {
|
|
33
|
+
min: number;
|
|
34
|
+
max: number;
|
|
35
|
+
};
|
|
36
|
+
getDefaultValue: () => {
|
|
37
|
+
min: number;
|
|
38
|
+
max: number;
|
|
39
|
+
};
|
|
40
|
+
valueToString: (value?: DualRangeValue) => string;
|
|
41
|
+
buildUI: () => {
|
|
42
|
+
element: HTMLDivElement;
|
|
43
|
+
minInput: HTMLInputElement;
|
|
44
|
+
maxInput: HTMLInputElement;
|
|
45
|
+
};
|
|
46
|
+
update: (value: DualRangeValue) => void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import random from "../utils/random";
|
|
2
|
+
import DualRangeInput from "@stanko/dual-range-input";
|
|
3
|
+
import { roundToStep } from "../utils/round-to-step";
|
|
4
|
+
// TODO
|
|
5
|
+
// Add a span with the current value
|
|
6
|
+
export class DualRangeCtrl {
|
|
7
|
+
constructor(config, onChange, onInput) {
|
|
8
|
+
this.type = "dual-range";
|
|
9
|
+
this.parse = (string) => {
|
|
10
|
+
const [min, max] = string.split(",").map(parseFloat);
|
|
11
|
+
return { min, max };
|
|
12
|
+
};
|
|
13
|
+
this.getRandomValue = () => {
|
|
14
|
+
const { step } = this;
|
|
15
|
+
const min = random(this.min, this.max - step);
|
|
16
|
+
const max = random(min + step, this.max);
|
|
17
|
+
return {
|
|
18
|
+
min: roundToStep(min, step),
|
|
19
|
+
max: roundToStep(max, step),
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
this.getDefaultValue = () => {
|
|
23
|
+
return {
|
|
24
|
+
min: this.min,
|
|
25
|
+
max: this.max,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
this.valueToString = (value = this.value) => {
|
|
29
|
+
return `${value.min},${value.max}`;
|
|
30
|
+
};
|
|
31
|
+
this.buildUI = () => {
|
|
32
|
+
const { min, max, step, value } = this;
|
|
33
|
+
const minInput = document.createElement("input");
|
|
34
|
+
minInput.setAttribute("type", "range");
|
|
35
|
+
minInput.setAttribute("min", min.toString());
|
|
36
|
+
minInput.setAttribute("max", max.toString());
|
|
37
|
+
minInput.setAttribute("step", step.toString());
|
|
38
|
+
minInput.setAttribute("value", value.min.toString());
|
|
39
|
+
minInput.addEventListener("input", () => {
|
|
40
|
+
this.value = {
|
|
41
|
+
min: parseFloat(minInput.value),
|
|
42
|
+
max: parseFloat(maxInput.value),
|
|
43
|
+
};
|
|
44
|
+
this.onInput(this.name, this.value);
|
|
45
|
+
});
|
|
46
|
+
minInput.addEventListener("change", () => {
|
|
47
|
+
this.value = {
|
|
48
|
+
min: parseFloat(minInput.value),
|
|
49
|
+
max: parseFloat(maxInput.value),
|
|
50
|
+
};
|
|
51
|
+
this.onChange(this.name, this.value);
|
|
52
|
+
});
|
|
53
|
+
const maxInput = document.createElement("input");
|
|
54
|
+
maxInput.setAttribute("type", "range");
|
|
55
|
+
maxInput.setAttribute("min", min.toString());
|
|
56
|
+
maxInput.setAttribute("max", max.toString());
|
|
57
|
+
maxInput.setAttribute("step", step.toString());
|
|
58
|
+
maxInput.setAttribute("value", value.max.toString());
|
|
59
|
+
maxInput.addEventListener("change", () => {
|
|
60
|
+
this.value = {
|
|
61
|
+
min: parseFloat(minInput.value),
|
|
62
|
+
max: parseFloat(maxInput.value),
|
|
63
|
+
};
|
|
64
|
+
this.onChange(this.name, this.value);
|
|
65
|
+
});
|
|
66
|
+
const inputWrapper = document.createElement("div");
|
|
67
|
+
inputWrapper.classList.add("dual-range-input");
|
|
68
|
+
inputWrapper.appendChild(minInput);
|
|
69
|
+
inputWrapper.appendChild(maxInput);
|
|
70
|
+
const right = document.createElement("div");
|
|
71
|
+
right.classList.add("ctrls__control-right");
|
|
72
|
+
right.appendChild(inputWrapper);
|
|
73
|
+
const label = document.createElement("span");
|
|
74
|
+
label.textContent = this.label;
|
|
75
|
+
label.classList.add("ctrls__control-label");
|
|
76
|
+
const element = document.createElement("div");
|
|
77
|
+
element.classList.add("ctrls__control", "ctrls__control--dual-range");
|
|
78
|
+
element.appendChild(label);
|
|
79
|
+
element.appendChild(right);
|
|
80
|
+
return {
|
|
81
|
+
element,
|
|
82
|
+
minInput,
|
|
83
|
+
maxInput,
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
this.update = (value) => {
|
|
87
|
+
const { min, max } = value;
|
|
88
|
+
this.value = value;
|
|
89
|
+
this.minInput.setAttribute("max", max.toString());
|
|
90
|
+
this.maxInput.setAttribute("min", min.toString());
|
|
91
|
+
this.minInput.value = value.min.toString();
|
|
92
|
+
this.maxInput.value = value.max.toString();
|
|
93
|
+
this.dualRange.update();
|
|
94
|
+
};
|
|
95
|
+
this.name = config.name;
|
|
96
|
+
this.label = config.label || config.name;
|
|
97
|
+
this.min = config.min;
|
|
98
|
+
this.max = config.max;
|
|
99
|
+
this.step = config.step || 1;
|
|
100
|
+
this.value =
|
|
101
|
+
config.defaultValue === undefined
|
|
102
|
+
? this.getDefaultValue()
|
|
103
|
+
: config.defaultValue;
|
|
104
|
+
this.isRandomizationDisabled = config.isRandomizationDisabled || false;
|
|
105
|
+
this.onChange = onChange;
|
|
106
|
+
this.onInput = onInput;
|
|
107
|
+
const { minInput, maxInput, element } = this.buildUI();
|
|
108
|
+
this.minInput = minInput;
|
|
109
|
+
this.maxInput = maxInput;
|
|
110
|
+
this.element = element;
|
|
111
|
+
this.dualRange = new DualRangeInput(this.minInput, this.maxInput);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Ctrl, CtrlChangeHandler, CtrlType, CtrlTypeRegistry } from ".";
|
|
2
|
+
export type Easing = [number, number, number, number];
|
|
3
|
+
export declare class EasingCtrl implements Ctrl<Easing> {
|
|
4
|
+
type: CtrlType;
|
|
5
|
+
name: string;
|
|
6
|
+
label: string;
|
|
7
|
+
value: Easing;
|
|
8
|
+
isRandomizationDisabled: boolean;
|
|
9
|
+
onChange: CtrlChangeHandler<Easing>;
|
|
10
|
+
onInput: CtrlChangeHandler<Easing>;
|
|
11
|
+
element: HTMLElement;
|
|
12
|
+
ticks: SVGLineElement[];
|
|
13
|
+
control: HTMLDivElement;
|
|
14
|
+
handles: HTMLButtonElement[];
|
|
15
|
+
lines: SVGLineElement[];
|
|
16
|
+
path: SVGPathElement;
|
|
17
|
+
presets: Record<string, Easing>;
|
|
18
|
+
constructor(config: CtrlTypeRegistry["easing"]["config"], onChange: CtrlChangeHandler<Easing>, onInput: CtrlChangeHandler<Easing>);
|
|
19
|
+
parse: (string: string) => Easing;
|
|
20
|
+
getRandomValue: () => Easing;
|
|
21
|
+
getDefaultValue: () => Easing;
|
|
22
|
+
valueToString: (value?: Easing) => string;
|
|
23
|
+
getRelativeValues: (value?: Easing) => {
|
|
24
|
+
px: Easing;
|
|
25
|
+
percentage: Easing;
|
|
26
|
+
};
|
|
27
|
+
buildUI: () => {
|
|
28
|
+
element: HTMLDivElement;
|
|
29
|
+
ticks: SVGLineElement[];
|
|
30
|
+
control: HTMLDivElement;
|
|
31
|
+
handles: HTMLButtonElement[];
|
|
32
|
+
lines: SVGLineElement[];
|
|
33
|
+
path: SVGPathElement;
|
|
34
|
+
};
|
|
35
|
+
updateUI: (value?: Easing) => void;
|
|
36
|
+
update: (value?: Easing) => void;
|
|
37
|
+
}
|