@quenty/blend 2.0.0-canary.236.5597d0a.0 → 2.0.1
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/CHANGELOG.md +17 -1
- package/README.md +6 -49
- package/package.json +17 -17
- package/src/Client/Blend/Blend.lua +175 -10
- package/src/Client/Test/BlendChildren.story.lua +3 -3
- package/src/Client/Test/BlendComputePairs.story.lua +3 -3
- package/src/Client/Test/BlendPromise.story.lua +3 -3
- package/src/Client/Test/BlendSpring.story.lua +3 -3
- package/src/Client/Test/BlendTextbox.story.lua +3 -3
- package/test/scripts/Client/ClientMain.client.lua +1 -3
- package/test/scripts/Server/ServerMain.server.lua +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,23 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
## [2.0.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/blend@2.0.0...@quenty/blend@2.0.1) (2021-12-30)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @quenty/blend
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [2.0.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/blend@1.2.0...@quenty/blend@2.0.0) (2021-12-22)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @quenty/blend
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# [1.2.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/blend@1.1.0...@quenty/blend@1.2.0) (2021-12-18)
|
|
7
23
|
|
|
8
24
|
|
|
9
25
|
### Features
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
## Blend
|
|
2
2
|
<div align="center">
|
|
3
|
-
<a href="http://quenty.github.io/
|
|
4
|
-
<img src="https://
|
|
3
|
+
<a href="http://quenty.github.io/NevermoreEngine/">
|
|
4
|
+
<img src="https://github.com/Quenty/NevermoreEngine/actions/workflows/docs.yml/badge.svg" alt="Documentation status" />
|
|
5
5
|
</a>
|
|
6
6
|
<a href="https://discord.gg/mhtGUS8">
|
|
7
|
-
<img src="https://img.shields.io/
|
|
7
|
+
<img src="https://img.shields.io/discord/385151591524597761?color=5865F2&label=discord&logo=discord&logoColor=white" alt="Discord" />
|
|
8
8
|
</a>
|
|
9
9
|
<a href="https://github.com/Quenty/NevermoreEngine/actions">
|
|
10
10
|
<img src="https://github.com/Quenty/NevermoreEngine/actions/workflows/build.yml/badge.svg" alt="Build and release status" />
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
Declarative UI system inspired by Fusion
|
|
15
15
|
|
|
16
|
+
<div align="center"><a href="https://quenty.github.io/NevermoreEngine/api/Blend">View docs →</a></div>
|
|
17
|
+
|
|
16
18
|
## Installation
|
|
17
19
|
```
|
|
18
20
|
npm install @quenty/blend --save
|
|
@@ -24,49 +26,4 @@ This system is designed to be very similar to fusion, except that we do not havi
|
|
|
24
26
|
|
|
25
27
|
* No global state
|
|
26
28
|
* Extensible
|
|
27
|
-
* No implicit reliance upon GC
|
|
28
|
-
|
|
29
|
-
## Usage
|
|
30
|
-
|
|
31
|
-
See files in src/Client/Test that are stories. Blend returns an observable that will create/return one instance.
|
|
32
|
-
|
|
33
|
-
Note that subscribe function anchors everything into a maid/cleanup function that can be used to disconnect the whole tree.
|
|
34
|
-
|
|
35
|
-
```lua
|
|
36
|
-
local require = ... -- Nevermore import here
|
|
37
|
-
local Blend = require("Blend")
|
|
38
|
-
local Maid = require("Maid")
|
|
39
|
-
|
|
40
|
-
local maid = Maid.new()
|
|
41
|
-
|
|
42
|
-
local isVisible = Instance.new("BoolValue")
|
|
43
|
-
isVisible.Value = false
|
|
44
|
-
|
|
45
|
-
local percentVisible = Blend.Spring(Blend.Computed(isVisible, function(visible)
|
|
46
|
-
return visible and 1 or 0
|
|
47
|
-
end), 35)
|
|
48
|
-
|
|
49
|
-
local transparency = Blend.Computed(percentVisible, function(percent)
|
|
50
|
-
return 1 - percent
|
|
51
|
-
end)
|
|
52
|
-
|
|
53
|
-
maid:GiveTask((Blend.New "Frame" {
|
|
54
|
-
Size = UDim2.new(0.5, 0, 0.5, 0);
|
|
55
|
-
BackgroundColor3 = Color3.new(0.9, 0.9, 0.9);
|
|
56
|
-
AnchorPoint = Vector2.new(0.5, 0.5);
|
|
57
|
-
Position = UDim2.new(0.5, 0, 0.5, 0);
|
|
58
|
-
BackgroundTransparency = transparency;
|
|
59
|
-
Parent = parent; -- TODO: Assign parent
|
|
60
|
-
|
|
61
|
-
[Blend.Children] = {
|
|
62
|
-
Blend.New "UIScale" {
|
|
63
|
-
Scale = Blend.Computed(percentVisible, function(percent)
|
|
64
|
-
return 0.8 + 0.2*percent
|
|
65
|
-
end);
|
|
66
|
-
};
|
|
67
|
-
Blend.New "UICorner" {
|
|
68
|
-
CornerRadius = UDim.new(0.05, 0);
|
|
69
|
-
};
|
|
70
|
-
};
|
|
71
|
-
}):Subscribe())
|
|
72
|
-
```
|
|
29
|
+
* No implicit reliance upon GC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/blend",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Declarative UI system.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -27,23 +27,23 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@quenty/acceltween": "2.0.
|
|
31
|
-
"@quenty/brio": "
|
|
32
|
-
"@quenty/instanceutils": "
|
|
33
|
-
"@quenty/loader": "3.1.
|
|
34
|
-
"@quenty/maid": "2.0.
|
|
35
|
-
"@quenty/promise": "
|
|
36
|
-
"@quenty/rx": "
|
|
37
|
-
"@quenty/spring": "3.0.
|
|
38
|
-
"@quenty/steputils": "3.0.
|
|
39
|
-
"@quenty/string": "2.2.
|
|
40
|
-
"@quenty/symbol": "2.0.
|
|
41
|
-
"@quenty/valuebaseutils": "
|
|
42
|
-
"@quenty/valueobject": "
|
|
30
|
+
"@quenty/acceltween": "^2.0.1",
|
|
31
|
+
"@quenty/brio": "^3.5.1",
|
|
32
|
+
"@quenty/instanceutils": "^3.5.1",
|
|
33
|
+
"@quenty/loader": "^3.1.2",
|
|
34
|
+
"@quenty/maid": "^2.0.2",
|
|
35
|
+
"@quenty/promise": "^3.3.1",
|
|
36
|
+
"@quenty/rx": "^3.5.1",
|
|
37
|
+
"@quenty/spring": "^3.0.1",
|
|
38
|
+
"@quenty/steputils": "^3.0.1",
|
|
39
|
+
"@quenty/string": "^2.2.1",
|
|
40
|
+
"@quenty/symbol": "^2.0.1",
|
|
41
|
+
"@quenty/valuebaseutils": "^3.5.1",
|
|
42
|
+
"@quenty/valueobject": "^3.5.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@quenty/contentproviderutils": "
|
|
46
|
-
"@quenty/playerthumbnailutils": "4.
|
|
45
|
+
"@quenty/contentproviderutils": "^3.3.1",
|
|
46
|
+
"@quenty/playerthumbnailutils": "^3.4.1"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "d146c77d0a8e452824de0ab0b4b03ba0370bcc1b"
|
|
49
49
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
--[=[
|
|
2
|
+
Declarative UI system inspired by Fusion
|
|
3
|
+
@class Blend
|
|
4
|
+
]=]
|
|
4
5
|
|
|
5
6
|
local require = require(script.Parent.loader).load(script)
|
|
6
7
|
|
|
@@ -25,6 +26,26 @@ local Blend = {}
|
|
|
25
26
|
|
|
26
27
|
Blend.Children = Symbol.named("children")
|
|
27
28
|
|
|
29
|
+
--[=[
|
|
30
|
+
Creates a new function which will return an observable that, given the props
|
|
31
|
+
in question, will construct a new instance and assign all props. This is the
|
|
32
|
+
equivalent of a pipe-able Rx command.
|
|
33
|
+
|
|
34
|
+
```lua
|
|
35
|
+
Blend.New "ScreenGui" {
|
|
36
|
+
Parent = game.Players.LocalPlayer.PlayerGui;
|
|
37
|
+
[Blend.Children] = {
|
|
38
|
+
Blend.New "Frame" {
|
|
39
|
+
Size = UDim2.new(1, 0, 1, 0);
|
|
40
|
+
BackgroundTransparency = 0.5;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
@param className string
|
|
46
|
+
@return (props: { [string]: any; }) -> Observable<Instance>
|
|
47
|
+
```
|
|
48
|
+
]=]
|
|
28
49
|
function Blend.New(className)
|
|
29
50
|
assert(type(className) == "string", "Bad className")
|
|
30
51
|
|
|
@@ -51,6 +72,12 @@ function Blend.New(className)
|
|
|
51
72
|
end
|
|
52
73
|
end
|
|
53
74
|
|
|
75
|
+
--[=[
|
|
76
|
+
Creates a new Blend State which is actually just a ValueObject underneath.
|
|
77
|
+
|
|
78
|
+
@param defaultValue T
|
|
79
|
+
@return ValueObject<T>
|
|
80
|
+
]=]
|
|
54
81
|
function Blend.State(defaultValue)
|
|
55
82
|
return ValueObject.new(defaultValue)
|
|
56
83
|
end
|
|
@@ -72,6 +99,32 @@ function Blend.Dynamic(...)
|
|
|
72
99
|
})
|
|
73
100
|
end
|
|
74
101
|
|
|
102
|
+
--[=[
|
|
103
|
+
Takes a list of variables and uses them to compute an observable that
|
|
104
|
+
will combine into any value. These variables can be any value, and if they
|
|
105
|
+
can be converted into an Observable, they will be, which will be used to compute
|
|
106
|
+
the value.
|
|
107
|
+
|
|
108
|
+
```lua
|
|
109
|
+
local verbState = Blend.State("hi")
|
|
110
|
+
local nameState = Blend.State("alice")
|
|
111
|
+
|
|
112
|
+
local computed = Blend.Computed(verbState, nameState, function(verb, name)
|
|
113
|
+
return verb .. " " .. name
|
|
114
|
+
end)
|
|
115
|
+
|
|
116
|
+
computed:Subscribe(function(sentence)
|
|
117
|
+
print(sentence)
|
|
118
|
+
end) --> "hi alice"
|
|
119
|
+
|
|
120
|
+
nameState.Value = "bob" --> "hi bob"
|
|
121
|
+
verbState.Value = "bye" --> "bye bob"
|
|
122
|
+
nameState.Value = "alice" --> "bye alice"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
@param ... A series of convertable states, followed by a function at the end.
|
|
126
|
+
@return Observable<T>
|
|
127
|
+
]=]
|
|
75
128
|
function Blend.Computed(...)
|
|
76
129
|
local values = {...}
|
|
77
130
|
local n = select("#", ...)
|
|
@@ -106,6 +159,22 @@ function Blend.Computed(...)
|
|
|
106
159
|
end
|
|
107
160
|
end
|
|
108
161
|
|
|
162
|
+
--[=[
|
|
163
|
+
Short hand to register a propertyEvent changing
|
|
164
|
+
|
|
165
|
+
```lua
|
|
166
|
+
Blend.mount(workspace, {
|
|
167
|
+
[Blend.OnChange "Name"] = function(name)
|
|
168
|
+
print(name)
|
|
169
|
+
end;
|
|
170
|
+
}) --> Immediately will print "Workspace"
|
|
171
|
+
|
|
172
|
+
workspace.Name = "Hello" --> Prints "Hello"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
@param propertyName string
|
|
176
|
+
@return (instance: Instance) -> Observable
|
|
177
|
+
]=]
|
|
109
178
|
function Blend.OnChange(propertyName)
|
|
110
179
|
assert(type(propertyName) == "string", "Bad propertyName")
|
|
111
180
|
|
|
@@ -114,6 +183,24 @@ function Blend.OnChange(propertyName)
|
|
|
114
183
|
end
|
|
115
184
|
end
|
|
116
185
|
|
|
186
|
+
--[=[
|
|
187
|
+
Short hand to register an event from the instance
|
|
188
|
+
|
|
189
|
+
```lua
|
|
190
|
+
Blend.mount(workspace, {
|
|
191
|
+
[Blend.OnEvent "ChildAdded"] = function(child)
|
|
192
|
+
print("Child added", child)
|
|
193
|
+
end;
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
local folder = Instance.new("Folder")
|
|
197
|
+
folder.Name = "Hi"
|
|
198
|
+
folder.Parent = workspace --> prints "Child added Hi"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
@param eventName string
|
|
202
|
+
@return (instance: Instance) -> Observable
|
|
203
|
+
]=]
|
|
117
204
|
function Blend.OnEvent(eventName)
|
|
118
205
|
assert(type(eventName) == "string", "Bad eventName")
|
|
119
206
|
|
|
@@ -153,7 +240,7 @@ function Blend.ComputedPairs(source, compute)
|
|
|
153
240
|
local brio = Brio.new(result)
|
|
154
241
|
innerMaid:GiveTask(brio)
|
|
155
242
|
|
|
156
|
-
local cleanup = Blend.
|
|
243
|
+
local cleanup = Blend.mountChildren(parent, brio)
|
|
157
244
|
if cleanup then
|
|
158
245
|
innerMaid:GiveTask(cleanup)
|
|
159
246
|
end
|
|
@@ -173,6 +260,13 @@ function Blend.ComputedPairs(source, compute)
|
|
|
173
260
|
end
|
|
174
261
|
end
|
|
175
262
|
|
|
263
|
+
--[=[
|
|
264
|
+
Like Blend.Spring, but for AccelTween
|
|
265
|
+
|
|
266
|
+
@param source any -- Source observable (or convertable)
|
|
267
|
+
@param acceleration any -- Source acceleration (or convertable)
|
|
268
|
+
@return Observable
|
|
269
|
+
]=]
|
|
176
270
|
function Blend.AccelTween(source, acceleration)
|
|
177
271
|
local sourceObservable = Blend.toPropertyObservable(source) or Rx.of(source)
|
|
178
272
|
local accelerationObservable = Blend.toNumberObservable(acceleration)
|
|
@@ -211,7 +305,27 @@ function Blend.AccelTween(source, acceleration)
|
|
|
211
305
|
end)
|
|
212
306
|
end
|
|
213
307
|
|
|
214
|
-
|
|
308
|
+
--[=[
|
|
309
|
+
Converts this arbitrary value into an observable that will initialize a spring
|
|
310
|
+
and interpolate it between values upon subscription.
|
|
311
|
+
|
|
312
|
+
```lua
|
|
313
|
+
local percentVisible = Blend.State(0)
|
|
314
|
+
local visibleSpring = Blend.Spring(percentVisible, 30)
|
|
315
|
+
local transparency = Blend.Computed(visibleSpring, function(percent)
|
|
316
|
+
return 1 - percent
|
|
317
|
+
end);
|
|
318
|
+
|
|
319
|
+
Blend.mount(frame, {
|
|
320
|
+
BackgroundTransparency = visibleSpring;
|
|
321
|
+
})
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
@param source any
|
|
325
|
+
@param speed any
|
|
326
|
+
@param damper any
|
|
327
|
+
@return Observable?
|
|
328
|
+
]=]
|
|
215
329
|
function Blend.Spring(source, speed, damper)
|
|
216
330
|
local sourceObservable = Blend.toPropertyObservable(source) or Rx.of(source)
|
|
217
331
|
local speedObservable = Blend.toNumberObservable(speed)
|
|
@@ -261,6 +375,12 @@ function Blend.Spring(source, speed, damper)
|
|
|
261
375
|
end)
|
|
262
376
|
end
|
|
263
377
|
|
|
378
|
+
--[=[
|
|
379
|
+
Converts this arbitrary value into an observable suitable for use in properties.
|
|
380
|
+
|
|
381
|
+
@param value any
|
|
382
|
+
@return Observable?
|
|
383
|
+
]=]
|
|
264
384
|
function Blend.toPropertyObservable(value)
|
|
265
385
|
if Observable.isObservable(value) then
|
|
266
386
|
return value
|
|
@@ -280,6 +400,12 @@ function Blend.toPropertyObservable(value)
|
|
|
280
400
|
return nil
|
|
281
401
|
end
|
|
282
402
|
|
|
403
|
+
--[=[
|
|
404
|
+
Converts this arbitrary value into an observable that emits numbers.
|
|
405
|
+
|
|
406
|
+
@param value number | any
|
|
407
|
+
@return Observable<number>?
|
|
408
|
+
]=]
|
|
283
409
|
function Blend.toNumberObservable(value)
|
|
284
410
|
if type(value) == "number" then
|
|
285
411
|
return Rx.of(value)
|
|
@@ -288,6 +414,12 @@ function Blend.toNumberObservable(value)
|
|
|
288
414
|
end
|
|
289
415
|
end
|
|
290
416
|
|
|
417
|
+
--[=[
|
|
418
|
+
Converts this arbitrary value into an observable that can be used to emit events.
|
|
419
|
+
|
|
420
|
+
@param value any
|
|
421
|
+
@return Observable?
|
|
422
|
+
]=]
|
|
291
423
|
function Blend.toEventObservable(value)
|
|
292
424
|
if Observable.isObservable(value) then
|
|
293
425
|
return value
|
|
@@ -298,6 +430,12 @@ function Blend.toEventObservable(value)
|
|
|
298
430
|
end
|
|
299
431
|
end
|
|
300
432
|
|
|
433
|
+
--[=[
|
|
434
|
+
Converts this arbitrary value into an event handler, which can be subscribed to
|
|
435
|
+
|
|
436
|
+
@param value any
|
|
437
|
+
@return function?
|
|
438
|
+
]=]
|
|
301
439
|
function Blend.toEventHandler(value)
|
|
302
440
|
if type(value) == "function" then
|
|
303
441
|
return value
|
|
@@ -319,7 +457,18 @@ function Blend.toEventHandler(value)
|
|
|
319
457
|
return nil
|
|
320
458
|
end
|
|
321
459
|
|
|
322
|
-
|
|
460
|
+
--[=[
|
|
461
|
+
Mounts children to the parent and returns an object which will cleanup and delete
|
|
462
|
+
all children when removed.
|
|
463
|
+
|
|
464
|
+
Note that this effectively recursively mounts children and their values, which is
|
|
465
|
+
the heart of the reactive tree.
|
|
466
|
+
|
|
467
|
+
@param parent Instance
|
|
468
|
+
@param value any
|
|
469
|
+
@return MaidTask
|
|
470
|
+
]=]
|
|
471
|
+
function Blend.mountChildren(parent, value)
|
|
323
472
|
if typeof(value) == "Instance" then
|
|
324
473
|
value.Parent = parent
|
|
325
474
|
|
|
@@ -336,7 +485,7 @@ function Blend.addChildren(parent, value)
|
|
|
336
485
|
local maid = Maid.new()
|
|
337
486
|
|
|
338
487
|
-- Add for lifetime
|
|
339
|
-
local cleanup = Blend.
|
|
488
|
+
local cleanup = Blend.mountChildren(parent, value:GetValue())
|
|
340
489
|
if cleanup then
|
|
341
490
|
maid:GiveTask(cleanup)
|
|
342
491
|
end
|
|
@@ -355,7 +504,7 @@ function Blend.addChildren(parent, value)
|
|
|
355
504
|
local maid = Maid.new()
|
|
356
505
|
|
|
357
506
|
maid:GiveTask(observable:Subscribe(function(result)
|
|
358
|
-
maid._current = Blend.
|
|
507
|
+
maid._current = Blend.mountChildren(parent, result)
|
|
359
508
|
end))
|
|
360
509
|
|
|
361
510
|
return maid
|
|
@@ -365,7 +514,7 @@ function Blend.addChildren(parent, value)
|
|
|
365
514
|
-- hope we're actually recursing over a nested table.
|
|
366
515
|
-- this allows us to add arrays into the blend.
|
|
367
516
|
for _, item in pairs(value) do
|
|
368
|
-
local cleanup = Blend.
|
|
517
|
+
local cleanup = Blend.mountChildren(parent, item)
|
|
369
518
|
if cleanup then
|
|
370
519
|
maid:GiveTask(cleanup)
|
|
371
520
|
end
|
|
@@ -384,6 +533,22 @@ function Blend.addChildren(parent, value)
|
|
|
384
533
|
return nil
|
|
385
534
|
end
|
|
386
535
|
|
|
536
|
+
--[=[
|
|
537
|
+
Mounts the instance to the props. This handles mounting children, and events.
|
|
538
|
+
|
|
539
|
+
The contract is that the props table is turned into observables. Note the following.
|
|
540
|
+
|
|
541
|
+
* Keys of strings are turned into properties
|
|
542
|
+
* If this can be turned into an observable, it will be used to subscribe to this event
|
|
543
|
+
* Otherwise, we assign directly
|
|
544
|
+
* Keys of functions are invoked on the instance in question
|
|
545
|
+
* If this returns an observable (or can be turned into one), we subscribe the event immediately
|
|
546
|
+
* If the key is [Blend.Children] then we invoke mountChildren on it
|
|
547
|
+
|
|
548
|
+
@param instance Instance
|
|
549
|
+
@param props table
|
|
550
|
+
@return Maid
|
|
551
|
+
]=]
|
|
387
552
|
function Blend.mount(instance, props)
|
|
388
553
|
local maid = Maid.new()
|
|
389
554
|
|
|
@@ -432,7 +597,7 @@ function Blend.mount(instance, props)
|
|
|
432
597
|
|
|
433
598
|
local childProp = props[Blend.Children]
|
|
434
599
|
if childProp then
|
|
435
|
-
local cleanup = Blend.
|
|
600
|
+
local cleanup = Blend.mountChildren(instance, childProp)
|
|
436
601
|
if cleanup then
|
|
437
602
|
maid:GiveTask(cleanup)
|
|
438
603
|
end
|