svelte-tweakpane-ui 1.3.3 → 1.5.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.
@@ -5,6 +5,7 @@
5
5
  import GenericInputFolding from '../internal/GenericInputFolding.svelte';
6
6
  import * as pluginModule from '@kitschpatrol/tweakpane-plugin-rotation';
7
7
  import { BROWSER } from 'esm-env';
8
+ import { shallowEqual } from 'fast-equals';
8
9
  export let value;
9
10
  export let order = void 0;
10
11
  export let unit = void 0;
@@ -15,24 +16,28 @@
15
16
  let options;
16
17
  let internalValue;
17
18
  const buttonClass = 'tp-rotationswatchv_b';
18
- function updateInternalValue() {
19
+ function updateInternalValueFromValue() {
19
20
  if (Array.isArray(value)) {
20
- const [x, y, z] = value;
21
- internalValue = { x, y, z };
22
- } else {
23
- internalValue = value;
21
+ const newInternalValue = { x: value[0], y: value[1], z: value[2] };
22
+ if (!shallowEqual(newInternalValue, internalValue)) {
23
+ internalValue = newInternalValue;
24
+ }
25
+ } else if (!shallowEqual(value, internalValue)) {
26
+ internalValue = { ...value };
24
27
  }
25
28
  }
26
- function updateValue() {
29
+ function updateValueFromInternalValue() {
27
30
  if (Array.isArray(value)) {
28
- const { x, y, z } = internalValue;
29
- value = [x, y, z];
30
- } else {
31
- value = internalValue;
31
+ const newValue = [internalValue.x, internalValue.y, internalValue.z];
32
+ if (!shallowEqual(newValue, value)) {
33
+ value = newValue;
34
+ }
35
+ } else if (!shallowEqual(internalValue, value)) {
36
+ value = { ...internalValue };
32
37
  }
33
38
  }
34
- $: value, updateInternalValue();
35
- $: internalValue, updateValue();
39
+ $: value, updateInternalValueFromValue();
40
+ $: internalValue, updateValueFromInternalValue();
36
41
  $: options = {
37
42
  x: optionsX,
38
43
  y: optionsY,
@@ -5,6 +5,7 @@
5
5
  import GenericInputFolding from '../internal/GenericInputFolding.svelte';
6
6
  import * as pluginModule from '@kitschpatrol/tweakpane-plugin-rotation';
7
7
  import { BROWSER } from 'esm-env';
8
+ import { shallowEqual } from 'fast-equals';
8
9
  export let value;
9
10
  export let optionsX = void 0;
10
11
  export let optionsY = void 0;
@@ -14,24 +15,28 @@
14
15
  let options;
15
16
  let internalValue;
16
17
  const buttonClass = 'tp-rotationswatchv_b';
17
- function updateInternalValue() {
18
+ function updateInternalValueFromValue() {
18
19
  if (Array.isArray(value)) {
19
- const [x, y, z, w] = value;
20
- internalValue = { x, y, z, w };
21
- } else {
22
- internalValue = value;
20
+ const newInternalValue = { x: value[0], y: value[1], z: value[2], w: value[3] };
21
+ if (!shallowEqual(newInternalValue, internalValue)) {
22
+ internalValue = newInternalValue;
23
+ }
24
+ } else if (!shallowEqual(value, internalValue)) {
25
+ internalValue = { ...value };
23
26
  }
24
27
  }
25
- function updateValue() {
28
+ function updateValueFromInternalValue() {
26
29
  if (Array.isArray(value)) {
27
- const { x, y, z, w } = internalValue;
28
- value = [x, y, z, w];
29
- } else {
30
- value = internalValue;
30
+ const newValue = [internalValue.x, internalValue.y, internalValue.z, internalValue.w];
31
+ if (!shallowEqual(newValue, value)) {
32
+ value = newValue;
33
+ }
34
+ } else if (!shallowEqual(internalValue, value)) {
35
+ value = { ...internalValue };
31
36
  }
32
37
  }
33
- $: value, updateInternalValue();
34
- $: internalValue, updateValue();
38
+ $: value, updateInternalValueFromValue();
39
+ $: internalValue, updateValueFromInternalValue();
35
40
  $: options = {
36
41
  x: optionsX,
37
42
  y: optionsY,
@@ -2,6 +2,7 @@
2
2
 
3
3
  <script>
4
4
  import GenericSlider from '../internal/GenericSlider.svelte';
5
+ import {} from 'svelte';
5
6
  export let value;
6
7
  export let wide = void 0;
7
8
  let ref;
@@ -16,4 +17,4 @@
16
17
  $: ref && wide !== void 0 && updateWide(wide);
17
18
  </script>
18
19
 
19
- <GenericSlider bind:value bind:ref bind:wide on:change {...$$restProps} />
20
+ <GenericSlider bind:value bind:ref on:change {...$$restProps} />
@@ -0,0 +1,30 @@
1
+ <script context="module"></script>
2
+
3
+ <script>
4
+ import GenericSlider from '../internal/GenericSlider.svelte';
5
+ import * as pluginModule from '@kitschpatrol/tweakpane-plugin-inputs';
6
+ export let value;
7
+ export let wide = void 0;
8
+ let options;
9
+ $: options = {
10
+ view: 'stepper'
11
+ };
12
+ let ref;
13
+ function updateWide(wide2) {
14
+ const inputField = ref?.element.querySelector('div.tp-stepv_t');
15
+ const buttonContainer = ref?.element.querySelector('div.tp-stepv_s');
16
+ const buttons = buttonContainer?.querySelectorAll('button');
17
+ if (wide2) {
18
+ inputField?.style.setProperty('display', 'none');
19
+ buttonContainer?.style.setProperty('flex', '1');
20
+ for (const button of buttons ?? []) {
21
+ button.style.setProperty('flex', '1');
22
+ }
23
+ } else {
24
+ inputField?.style.removeProperty('display');
25
+ }
26
+ }
27
+ $: ref && wide !== void 0 && updateWide(wide);
28
+ </script>
29
+
30
+ <GenericSlider bind:value bind:ref on:change {options} plugin={pluginModule} {...$$restProps} />
@@ -0,0 +1,194 @@
1
+ import { SvelteComponent } from 'svelte';
2
+ import type { ValueChangeEvent } from '../utils.js';
3
+ import type { SliderInputBindingApi as GenericSliderRef } from 'tweakpane';
4
+ export type StepperChangeEvent = ValueChangeEvent<number>;
5
+ declare const __propDef: {
6
+ props: {
7
+ /**
8
+ * A `number` value to control.
9
+ * @bindable
10
+ * */
11
+ value: number;
12
+ } & Omit<
13
+ {
14
+ /**
15
+ * Minimum value.
16
+ *
17
+ * Specifying both a `min` and a `max` prop turns the control into a slider.
18
+ * @default `undefined`
19
+ */
20
+ min?: number;
21
+ /**
22
+ * Maximum value.
23
+ *
24
+ * Specifying both a `min` and a `max` prop turns the control into a slider.
25
+ * @default `undefined`
26
+ */
27
+ max?: number;
28
+ /**
29
+ * A function to customize the point value's string representation (e.g. rounding, etc.).
30
+ * @default `undefined` \
31
+ * Normal `.toString()` formatting.
32
+ */
33
+ format?: ((value: number) => string) | undefined;
34
+ /**
35
+ * The unit scale for key-based input for all dimensions (e.g. the up and down arrow keys).
36
+ * @default `1` \
37
+ * Or `stepValue` if defined.
38
+ */
39
+ keyScale?: number;
40
+ /**
41
+ * The unit scale for pointer-based input for all dimensions.
42
+ * @default `undefined` \
43
+ * [Dynamic based on magnitude of
44
+ * `value`](https://github.com/cocopon/tweakpane/blob/66dfbea04bfe9b7f031673c955ceda1f92356e75/packages/core/src/common/number/util.ts#L54).
45
+ */
46
+ pointerScale?: number;
47
+ /**
48
+ * The minimum step interval.
49
+ * @default `undefined` \
50
+ * No step constraint.
51
+ */
52
+ step?: number;
53
+ /**
54
+ * When `true`, expand the width of the control at the expense of the numeric input
55
+ * field.
56
+ * @default `false`
57
+ */
58
+ wide?: boolean;
59
+ } & {
60
+ /**
61
+ * A `number` value to control.
62
+ * @bindable
63
+ */
64
+ value: number;
65
+ } & Omit<
66
+ {
67
+ /**
68
+ * The binding's target object with values to manipulate.
69
+ * @bindable
70
+ */
71
+ object: import('@tweakpane/core').Bindable & Record<string, number>;
72
+ /** The key for the value in the target `object` that the control should manipulate. */
73
+ key: string;
74
+ /**
75
+ * Prevent interactivity and gray out the control.
76
+ * @default `false`
77
+ */
78
+ disabled?: boolean;
79
+ /**
80
+ * Text displayed next to control.
81
+ * @default `undefined`
82
+ */
83
+ label?: string | undefined;
84
+ /**
85
+ * Tweakpane's internal options object.
86
+ *
87
+ * See [`BindingParams`](https://tweakpane.github.io/docs/api/types/BindingParams.html).
88
+ *
89
+ * Valid types are contingent on the type of the value `key` points to in `object`.
90
+ *
91
+ * This is intended internal use, when implementing convenience components wrapping Binding's
92
+ * functionality. Options of interest are instead exposed as top-level props in _Svelte
93
+ * Tweakpane UI_.
94
+ * @default `undefined`
95
+ */
96
+ options?: import('tweakpane').NumberInputParams | undefined;
97
+ /**
98
+ * Custom color scheme.
99
+ *
100
+ * @default `undefined` \
101
+ * Inherits default Tweakpane theme equivalent to `ThemeUtils.presets.standard`, or the theme
102
+ * set with `setGlobalDefaultTheme()`.
103
+ */
104
+ theme?: import('..').Theme | undefined;
105
+ /**
106
+ * Reference to internal Tweakpane
107
+ * [`BindingApi`](https://tweakpane.github.io/docs/api/classes/_internal_.BindingApi.html) for
108
+ * this control.
109
+ *
110
+ * This property is exposed for advanced use cases only, such as when implementing convenience
111
+ * components wrapping `<Binding>`'s functionality.
112
+ *
113
+ * Direct manipulation of Tweakpane's internals can break _Svelte Tweakpane UI_ abstractions.
114
+ *
115
+ * @bindable
116
+ * @readonly
117
+ */
118
+ ref?: GenericSliderRef | undefined;
119
+ /**
120
+ * Imported Tweakpane `TpPluginBundle` (aliased as `Plugin`) module to automatically register in
121
+ * the `<Binding>`'s containing `<Pane>`.
122
+ *
123
+ * This property is exposed for advanced use cases only, such as when implementing convenience
124
+ * components wrapping `<Binding>`'s functionality in combination with a Tweakpane plugin.
125
+ *
126
+ * Direct manipulation of Tweakpane's internals can break _Svelte Tweakpane UI_ abstractions.
127
+ *
128
+ * @default `undefined`
129
+ */
130
+ plugin?: import('../utils.js').Plugin | undefined;
131
+ },
132
+ 'object' | 'key'
133
+ >,
134
+ 'ref' | 'options' | 'plugin' | 'amount'
135
+ >;
136
+ slots: {};
137
+ events: {
138
+ /**
139
+ * Fires when `value` changes.
140
+ *
141
+ * _This event is provided for advanced use cases. It's usually preferred to bind to the `value` prop instead._
142
+ *
143
+ * The `event.details` payload includes a copy of the value and an `origin` field to distinguish between user-interactive changes (`internal`)
144
+ * and changes resulting from programmatic manipulation of the `value` (`external`).
145
+ *
146
+ * @extends ValueChangeEvent
147
+ * @event
148
+ * */
149
+ change: StepperChangeEvent;
150
+ };
151
+ };
152
+ export type StepperProps = typeof __propDef.props;
153
+ export type StepperEvents = typeof __propDef.events;
154
+ export type StepperSlots = typeof __propDef.slots;
155
+ /**
156
+ * A control for simple incremental value changes.
157
+ *
158
+ * Similar in functionality to a `<Slider>`, but with nice big buttons to increment and decrement the value.
159
+ *
160
+ * Integrates the [Stepper](https://github.com/tallneil/tweakpane-plugin-inputs/blob/main/src/stepper/plugin.ts)
161
+ * control from [Neil Shankar's](https://tallneil.io/) ["Inputs for Tweakpane
162
+ * " plugin](https://github.com/tallneil/tweakpane-plugin-inputs).
163
+ *
164
+ * Usage outside of a `<Pane>` component will implicitly wrap the stepper in `<Pane position="inline">`.
165
+ *
166
+ * Note that _Svelte Tweakpane UI_ embeds a functionally identical [fork](https://github.com/kitschpatrol/tweakpane-plugin-inputs) of the plugin with build optimizations.
167
+ *
168
+ * @emits {StepperChangeEvent} change - When `value` changes. (This event is provided for advanced use cases. Prefer binding to `value`.)
169
+ *
170
+ * @example
171
+ * ```svelte
172
+ * <script lang="ts">
173
+ * import { Stepper } from 'svelte-tweakpane-ui';
174
+ * let angle = 45;
175
+ * </script>
176
+ *
177
+ * <Stepper bind:value={angle} label="Angle" step={45} />
178
+ *
179
+ * <div class="demo" style:--a="{angle}deg"></div>
180
+ *
181
+ * <style>
182
+ * div.demo {
183
+ * aspect-ratio: 1;
184
+ * width: 100%;
185
+ * background: linear-gradient(var(--a), magenta, orange);
186
+ * }
187
+ * </style>
188
+ * ```
189
+ *
190
+ * @sourceLink
191
+ * [Stepper.svelte](https://github.com/kitschpatrol/svelte-tweakpane-ui/blob/main/src/lib/control/Stepper.svelte)
192
+ */
193
+ export default class Stepper extends SvelteComponent<StepperProps, StepperEvents, StepperSlots> {}
194
+ export {};
@@ -44,17 +44,31 @@
44
44
  _ref?.dispose();
45
45
  });
46
46
  const dispatch = createEventDispatcher();
47
+ function safeCopy(value) {
48
+ if (value instanceof File) {
49
+ return new File([value], value.name, {
50
+ lastModified: value.lastModified,
51
+ type: value.type
52
+ });
53
+ }
54
+ if (BROWSER && value instanceof HTMLImageElement) {
55
+ const copy2 = new Image();
56
+ copy2.src = value.src;
57
+ return copy2;
58
+ }
59
+ return copy(value);
60
+ }
47
61
  let lastObject = object;
48
- let lastValue = copy(object[key]);
62
+ let lastValue = safeCopy(object[key]);
49
63
  let internalChange = false;
50
64
  function onBoundValueChange(object2) {
51
65
  if (lastObject !== object2) {
52
66
  internalChange = false;
53
67
  }
54
68
  if (!shallowEqual(object2[key], lastValue)) {
55
- lastValue = copy(object2[key]);
69
+ lastValue = safeCopy(object2[key]);
56
70
  dispatch('change', {
57
- value: copy(object2[key]),
71
+ value: safeCopy(object2[key]),
58
72
  origin: internalChange ? 'internal' : 'external'
59
73
  });
60
74
  if (!internalChange && _ref) {
@@ -71,7 +85,7 @@
71
85
  }
72
86
  function onTweakpaneChange() {
73
87
  internalChange = true;
74
- object = object;
88
+ object[key] = safeCopy(object[key]);
75
89
  }
76
90
  $: DEV && enforceReadonly(_ref, ref, 'Binding', 'ref', true);
77
91
  $: options, $parentStore !== void 0 && index !== void 0 && create();
@@ -123,7 +123,7 @@ export type AutoValueSlots = typeof __propDef.slots;
123
123
  * import { AutoValue } from 'svelte-tweakpane-ui';
124
124
  *
125
125
  * let number = 0;
126
- * let color = '#ff00ff';
126
+ * let color = { r: 255, g: 0, b: 255 };
127
127
  * let point = { x: 0, y: 0 };
128
128
  * let text = 'Cosmic manifold';
129
129
  * </script>
@@ -151,7 +151,7 @@ export type ElementSlots = typeof __propDef.slots;
151
151
  * gradientAngle = 45;
152
152
  * textAngle = 0;
153
153
  * }}
154
- * disabled={gradientAngle == 45 && textAngle == 0}
154
+ * disabled={gradientAngle === 45 && textAngle === 0}
155
155
  * title="Reset"
156
156
  * />
157
157
  * </Pane>
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export {
18
18
  type CubicBezierValueTuple,
19
19
  default as CubicBezier
20
20
  } from './control/CubicBezier.svelte';
21
+ export { default as File, type FileChangeEvent, type FileValue } from './control/File.svelte';
21
22
  export { default as Image, type ImageChangeEvent, type ImageValue } from './control/Image.svelte';
22
23
  export {
23
24
  default as IntervalSlider,
@@ -74,6 +75,7 @@ export {
74
75
  type RotationQuaternionValueTuple
75
76
  } from './control/RotationQuaternion.svelte';
76
77
  export { default as Slider, type SliderChangeEvent } from './control/Slider.svelte';
78
+ export { default as Stepper, type StepperChangeEvent } from './control/Stepper.svelte';
77
79
  export { default as Text, type TextChangeEvent } from './control/Text.svelte';
78
80
  export { default as Textarea, type TextareaChangeEvent } from './control/Textarea.svelte';
79
81
  export { default as Wheel, type WheelChangeEvent } from './control/Wheel.svelte';
package/dist/index.js CHANGED
@@ -1,11 +1,9 @@
1
- // Components
2
1
  export { default as Button } from './control/Button.svelte';
3
- // Essentials (1st party plugins)
4
2
  export { default as ButtonGrid } from './control/ButtonGrid.svelte';
5
3
  export { default as Checkbox } from './control/Checkbox.svelte';
6
4
  export { default as Color } from './control/Color.svelte';
7
5
  export { default as CubicBezier } from './control/CubicBezier.svelte';
8
- // Additional plugins (3rd party / community)
6
+ export { default as File } from './control/File.svelte';
9
7
  export { default as Image } from './control/Image.svelte';
10
8
  export { default as IntervalSlider } from './control/IntervalSlider.svelte';
11
9
  export { default as List } from './control/List.svelte';
@@ -16,10 +14,10 @@ export { default as Ring } from './control/Ring.svelte';
16
14
  export { default as RotationEuler } from './control/RotationEuler.svelte';
17
15
  export { default as RotationQuaternion } from './control/RotationQuaternion.svelte';
18
16
  export { default as Slider } from './control/Slider.svelte';
17
+ export { default as Stepper } from './control/Stepper.svelte';
19
18
  export { default as Text } from './control/Text.svelte';
20
19
  export { default as Textarea } from './control/Textarea.svelte';
21
20
  export { default as Wheel } from './control/Wheel.svelte';
22
- // Core (tweakpane building blocks)
23
21
  export { default as Binding } from './core/Binding.svelte';
24
22
  export { default as Blade } from './core/Blade.svelte';
25
23
  export { default as Folder } from './core/Folder.svelte';
@@ -27,7 +25,6 @@ export { default as Pane } from './core/Pane.svelte';
27
25
  export { default as Separator } from './core/Separator.svelte';
28
26
  export { default as TabGroup } from './core/TabGroup.svelte';
29
27
  export { default as TabPage } from './core/TabPage.svelte';
30
- // Extra (svelte convenience components)
31
28
  export { default as AutoObject } from './extra/AutoObject.svelte';
32
29
  export { default as AutoValue } from './extra/AutoValue.svelte';
33
30
  export { default as Element } from './extra/Element.svelte';
@@ -4,6 +4,7 @@
4
4
  generics="T extends any, U extends BindingOptions = BindingOptions, V extends BindingRef = BindingRef"
5
5
  >
6
6
  import Binding from '../core/Binding.svelte';
7
+ import { shallowEqual } from 'fast-equals';
7
8
  export let value;
8
9
  export let ref = void 0;
9
10
  export let options = void 0;
@@ -12,7 +13,9 @@
12
13
  return value;
13
14
  }
14
15
  function setValue() {
15
- object[key] = value;
16
+ if (!shallowEqual(value, object[key])) {
17
+ object[key] = value;
18
+ }
16
19
  }
17
20
  $: object = { [key]: getValue() };
18
21
  $: value = object[key];
@@ -3,7 +3,7 @@
3
3
  import { applyTheme } from '../theme.js';
4
4
  import { updateCollapsibility } from '../utils.js';
5
5
  import { BROWSER } from 'esm-env';
6
- import { getContext, onDestroy, setContext } from 'svelte';
6
+ import { getContext, onDestroy, setContext, tick } from 'svelte';
7
7
  import { writable } from 'svelte/store';
8
8
  import { Pane as TpPane } from 'tweakpane';
9
9
  export let title = void 0;
@@ -29,11 +29,12 @@
29
29
  if ($existingParentStore !== void 0) {
30
30
  console.warn('<Panes> must not be nested');
31
31
  }
32
- let _expanded = expanded;
33
32
  if (BROWSER) {
34
33
  $parentStore = new TpPane({ expanded, title });
35
34
  $parentStore.on('fold', () => {
36
- _expanded = $parentStore.expanded;
35
+ if ($parentStore.expanded !== void 0 && expanded !== $parentStore.expanded) {
36
+ expanded = $parentStore.expanded;
37
+ }
37
38
  });
38
39
  tpPane = $parentStore;
39
40
  setContext('parentStore', parentStore);
@@ -57,19 +58,19 @@
57
58
  }
58
59
  }
59
60
  }
60
- function syncFolded() {
61
- if (tpPane && tpPane.expanded !== _expanded) {
62
- tpPane.expanded = _expanded;
63
- }
64
- expanded = _expanded;
61
+ function updateExpanded(expanded2) {
62
+ void tick().then(() => {
63
+ if (tpPane?.expanded !== void 0 && expanded2 !== void 0 && tpPane.expanded !== expanded2) {
64
+ tpPane.expanded = expanded2;
65
+ }
66
+ });
65
67
  }
66
68
  $: tpPane?.element && tpPane?.element.classList.add('svelte-tweakpane-ui');
67
69
  $: tpPane && setScale(scale);
68
70
  $: tpPane && updateCollapsibility(userExpandable, tpPane.element, 'tp-rotv_b', 'tp-rotv_m');
69
- $: tpPane && title && (tpPane.title = title);
71
+ $: tpPane && title !== void 0 && (tpPane.title = title.length > 0 ? title : ' ');
70
72
  $: tpPane && applyTheme(tpPane.element, theme);
71
- $: _expanded, tpPane && expanded !== void 0 && syncFolded();
72
- $: tpPane && (_expanded = expanded);
73
+ $: tpPane && updateExpanded(expanded);
73
74
  </script>
74
75
 
75
76
  {#if BROWSER}
@@ -224,6 +224,8 @@ export type ProfilerSlots = typeof __propDef.slots;
224
224
  * }
225
225
  *
226
226
  * onMount(() => {
227
+ * let animationFrameHandle: number;
228
+ *
227
229
  * (function tick() {
228
230
  * // Nesting measurements creates a hierarchy
229
231
  * // in the Profile visualization
@@ -252,6 +254,10 @@ export type ProfilerSlots = typeof __propDef.slots;
252
254
  *
253
255
  * requestAnimationFrame(tick);
254
256
  * })();
257
+ *
258
+ * return () => {
259
+ * cancelAnimationFrame(animationFrameHandle);
260
+ * };
255
261
  * });
256
262
  * </script>
257
263
  *