@lightningjs/blits 1.30.1 → 1.30.2
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/.editorconfig +7 -7
- package/.eslintignore +1 -1
- package/.eslintrc.cjs +65 -65
- package/.github/workflows/comment-lint-results.yml +124 -124
- package/.github/workflows/comment-test-results.yml +108 -108
- package/.github/workflows/deploy-docs.yml +27 -27
- package/.github/workflows/run-blits-tests.yml +141 -141
- package/.github/workflows/run-lint.yml +127 -127
- package/.husky/pre-commit +1 -1
- package/.vscode/extensions.json +7 -7
- package/.vscode/settings.json +9 -9
- package/CHANGELOG.md +1019 -1012
- package/COPYING +1 -0
- package/LICENSE +202 -202
- package/NOTICE +12 -12
- package/README.md +33 -33
- package/bin/index.js +8 -8
- package/blits.d.ts +6 -6
- package/docs/README.md +19 -19
- package/docs/_sidebar.md +37 -37
- package/docs/built-in/directives.md +50 -50
- package/docs/built-in/for-loop.md +203 -203
- package/docs/built-in/layout.md +168 -168
- package/docs/components/component_state.md +90 -90
- package/docs/components/computed_properties.md +45 -45
- package/docs/components/lifecycle_events.md +42 -42
- package/docs/components/methods.md +45 -45
- package/docs/components/props.md +56 -56
- package/docs/components/user_input.md +186 -186
- package/docs/components/utility_methods.md +320 -320
- package/docs/components/watchers.md +34 -34
- package/docs/essentials/application_root.md +19 -19
- package/docs/essentials/components.md +68 -68
- package/docs/essentials/displaying_images.md +67 -67
- package/docs/essentials/displaying_text.md +135 -135
- package/docs/essentials/element_attributes.md +195 -195
- package/docs/essentials/settings.md +117 -117
- package/docs/essentials/template_syntax.md +83 -83
- package/docs/getting_started/file_structure.md +58 -58
- package/docs/getting_started/getting_started.md +45 -45
- package/docs/getting_started/intro.md +20 -20
- package/docs/index.html +47 -47
- package/docs/performance/lazy-loading.md +161 -161
- package/docs/plugins/global_app_state.md +98 -98
- package/docs/plugins/language.md +172 -172
- package/docs/plugins/text-to-speech-announcer.md +119 -119
- package/docs/plugins/theme.md +176 -176
- package/docs/router/basics.md +175 -175
- package/docs/router/hooks.md +70 -70
- package/docs/router/transitions.md +146 -146
- package/docs/sidebar.json +171 -171
- package/docs/transitions_animations/transitions.md +150 -150
- package/index.d.ts +1099 -1099
- package/index.js +28 -28
- package/jsconfig.json +12 -12
- package/package.json +56 -56
- package/public/assets/fps_sprite.base64.js +1 -1
- package/scripts/postpublish.js +59 -59
- package/scripts/prepublishOnly.js +84 -84
- package/scripts/prepush.js +78 -78
- package/src/announcer/announcer.js +162 -162
- package/src/announcer/speechSynthesis.js +107 -107
- package/src/application.js +100 -100
- package/src/component/base/announcer.js +27 -27
- package/src/component/base/events.js +69 -69
- package/src/component/base/index.js +33 -33
- package/src/component/base/methods.js +244 -244
- package/src/component/base/router.js +41 -41
- package/src/component/base/timeouts_intervals.js +99 -99
- package/src/component/base/utils.js +72 -72
- package/src/component/setup/computed.js +48 -48
- package/src/component/setup/index.js +62 -62
- package/src/component/setup/input.js +30 -30
- package/src/component/setup/methods.js +37 -37
- package/src/component/setup/props.js +62 -62
- package/src/component/setup/props.test.js +238 -238
- package/src/component/setup/routes.js +42 -42
- package/src/component/setup/state.js +59 -59
- package/src/component/setup/watch.js +35 -35
- package/src/component.js +437 -437
- package/src/component.test.js +388 -388
- package/src/components/Circle.js +73 -73
- package/src/components/Circle.orig.js +31 -31
- package/src/components/FPScounter.js +1510 -1510
- package/src/components/FPScounter.orig.js +121 -121
- package/src/components/RouterView.js +81 -81
- package/src/components/RouterView.orig.js +54 -54
- package/src/components/Sprite.js +94 -94
- package/src/components/Sprite.orig.js +58 -58
- package/src/components/index.js +28 -28
- package/src/components/index.orig.js +28 -28
- package/src/constants.js +21 -21
- package/src/engine.js +1 -1
- package/src/engines/L3/element.js +744 -744
- package/src/engines/L3/element.test.js +579 -579
- package/src/engines/L3/fontLoader.js +38 -38
- package/src/engines/L3/index.js +7 -7
- package/src/engines/L3/launch.js +123 -123
- package/src/engines/L3/shaderLoader.js +12 -12
- package/src/engines/index.js +1 -1
- package/src/focus.js +90 -90
- package/src/helpers/deepEqualArray.js +50 -50
- package/src/helpers/deepEqualArray.test.js +127 -127
- package/src/helpers/fetchJson.js +34 -34
- package/src/helpers/sequence.js +26 -26
- package/src/launch.js +123 -123
- package/src/lib/blitsfileconverter/blitsfileconverter.js +52 -52
- package/src/lib/codegenerator/generator.js +788 -788
- package/src/lib/codegenerator/generator.test.js +3801 -3801
- package/src/lib/colors/colors.js +92 -92
- package/src/lib/colors/colors.test.js +290 -290
- package/src/lib/colors/htmlColors.js +168 -168
- package/src/lib/componentId.js +49 -49
- package/src/lib/componentId.test.js +84 -84
- package/src/lib/eventListeners.js +123 -123
- package/src/lib/eventListeners.test.js +236 -236
- package/src/lib/hooks.js +59 -59
- package/src/lib/lifecycle.js +85 -85
- package/src/lib/log.js +117 -117
- package/src/lib/precompiler/precompiler.js +73 -73
- package/src/lib/reactivity/effect.js +93 -93
- package/src/lib/reactivity/effect.test.js +35 -35
- package/src/lib/reactivity/reactive.js +208 -208
- package/src/lib/reactivity/reactive.test.js +27 -27
- package/src/lib/reactivityguard/computedprops.js +218 -218
- package/src/lib/symbols.js +108 -108
- package/src/lib/templateparser/parser.js +386 -386
- package/src/lib/templateparser/parser.test.js +1411 -1411
- package/src/lib/utils.js +27 -27
- package/src/plugin.js +27 -27
- package/src/plugins/appstate.js +26 -26
- package/src/plugins/index.js +20 -20
- package/src/plugins/language.js +152 -152
- package/src/plugins/log.js +25 -25
- package/src/plugins/theme.js +94 -94
- package/src/router/router.js +514 -514
- package/src/router/router.test.js +473 -473
- package/src/router/transitions/fadeIn.js +27 -27
- package/src/router/transitions/fadeInOut.js +33 -33
- package/src/router/transitions/index.js +21 -21
- package/src/router/transitions/slideIn.js +28 -28
- package/src/router/transitions/slideInOut.js +33 -33
- package/src/settings.js +40 -40
- package/vite/blitsFileConverter.js +67 -67
- package/vite/index.js +28 -28
- package/vite/msdfGenerator.js +269 -269
- package/vite/preCompiler.js +42 -42
- package/vite/reactivityGuard.js +45 -45
- package/vscode/data/template-attributes.json +589 -589
|
@@ -1,744 +1,744 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2023 Comcast Cable Communications Management, LLC
|
|
3
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
* you may not use this file except in compliance with the License.
|
|
5
|
-
* You may obtain a copy of the License at
|
|
6
|
-
*
|
|
7
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
*
|
|
9
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
* See the License for the specific language governing permissions and
|
|
13
|
-
* limitations under the License.
|
|
14
|
-
*
|
|
15
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { renderer } from './launch.js'
|
|
19
|
-
import colors from '../../lib/colors/colors.js'
|
|
20
|
-
|
|
21
|
-
import { Log } from '../../lib/log.js'
|
|
22
|
-
import symbols from '../../lib/symbols.js'
|
|
23
|
-
import Settings from '../../settings.js'
|
|
24
|
-
|
|
25
|
-
// temporary counter to work around shader caching issues
|
|
26
|
-
let counter = 0
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Creates a padding object from a value and direction.
|
|
30
|
-
* @param {number|object|string|undefined} padding - The padding value.
|
|
31
|
-
* @param {string} direction - The layout direction ('vertical' or 'horizontal').
|
|
32
|
-
* @returns {{start: number, end: number, oppositeStart: number, oppositeEnd: number}} The padding object.
|
|
33
|
-
*/
|
|
34
|
-
const createPaddingObject = (padding, direction) => {
|
|
35
|
-
if (padding === undefined) {
|
|
36
|
-
return { start: 0, end: 0, oppositeStart: 0, oppositeEnd: 0 }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (typeof padding === 'number') {
|
|
40
|
-
return { start: padding, end: padding, oppositeStart: padding, oppositeEnd: padding }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// todo: do we need to do this runtime every time? or can we optimize this?
|
|
44
|
-
if (isObjectString(padding) === true) {
|
|
45
|
-
padding = parseToObject(padding)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (typeof padding === 'object') {
|
|
49
|
-
const {
|
|
50
|
-
top = undefined,
|
|
51
|
-
right = undefined,
|
|
52
|
-
bottom = undefined,
|
|
53
|
-
left = undefined,
|
|
54
|
-
x = 0,
|
|
55
|
-
y = 0,
|
|
56
|
-
} = padding
|
|
57
|
-
|
|
58
|
-
// use specific values if provided, otherwise fall back to x or y
|
|
59
|
-
return direction === 'vertical'
|
|
60
|
-
? {
|
|
61
|
-
start: top !== undefined ? top : y,
|
|
62
|
-
end: bottom !== undefined ? bottom : y,
|
|
63
|
-
oppositeStart: left !== undefined ? left : x,
|
|
64
|
-
oppositeEnd: right !== undefined ? right : x,
|
|
65
|
-
}
|
|
66
|
-
: {
|
|
67
|
-
start: left !== undefined ? left : x,
|
|
68
|
-
end: right !== undefined ? right : x,
|
|
69
|
-
oppositeStart: top !== undefined ? top : y,
|
|
70
|
-
oppositeEnd: bottom !== undefined ? bottom : y,
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return { start: 0, end: 0, oppositeStart: 0, oppositeEnd: 0 }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Layout function for arranging children in a layout container.
|
|
78
|
-
* @param {object} config - The layout configuration object.
|
|
79
|
-
* @this {import('../../component.js').BlitsElement}
|
|
80
|
-
*/
|
|
81
|
-
const layoutFn = function (config) {
|
|
82
|
-
const position = config.direction === 'vertical' ? 'y' : 'x'
|
|
83
|
-
const oppositePosition = config.direction === 'vertical' ? 'x' : 'y'
|
|
84
|
-
const oppositeMount = config.direction === 'vertical' ? 'mountX' : 'mountY'
|
|
85
|
-
const dimension = config.direction === 'vertical' ? 'height' : 'width'
|
|
86
|
-
const oppositeDimension = config.direction === 'vertical' ? 'width' : 'height'
|
|
87
|
-
const padding = createPaddingObject(config.padding, config.direction)
|
|
88
|
-
|
|
89
|
-
let offset = padding.start
|
|
90
|
-
|
|
91
|
-
const children = this.node.children
|
|
92
|
-
const childrenLength = children.length
|
|
93
|
-
let otherDimension = 0
|
|
94
|
-
const gap = config.gap || 0
|
|
95
|
-
for (let i = 0; i < childrenLength; i++) {
|
|
96
|
-
if (this.children[i] !== undefined && this.children[i].props.raw.show === false) {
|
|
97
|
-
continue
|
|
98
|
-
}
|
|
99
|
-
const node = children[i]
|
|
100
|
-
node[position] = offset
|
|
101
|
-
node[oppositePosition] = padding.oppositeStart
|
|
102
|
-
// todo: temporary text check, due to 1px width of empty text node
|
|
103
|
-
if (dimension === 'width') {
|
|
104
|
-
offset += node.width + (node.width !== ('text' in node ? 1 : 0) ? gap : 0)
|
|
105
|
-
} else {
|
|
106
|
-
offset +=
|
|
107
|
-
'text' in node
|
|
108
|
-
? node.width > 1
|
|
109
|
-
? node.height + gap
|
|
110
|
-
: 0
|
|
111
|
-
: node.height !== 0
|
|
112
|
-
? node.height + gap
|
|
113
|
-
: 0
|
|
114
|
-
}
|
|
115
|
-
otherDimension = Math.max(
|
|
116
|
-
otherDimension,
|
|
117
|
-
node[oppositeDimension] + padding.oppositeStart + padding.oppositeEnd
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
// adjust the size of the layout container
|
|
121
|
-
this.node[dimension] = offset - gap + padding.end
|
|
122
|
-
this.node[oppositeDimension] = otherDimension
|
|
123
|
-
|
|
124
|
-
const align = {
|
|
125
|
-
start: 0,
|
|
126
|
-
end: 1,
|
|
127
|
-
center: 0.5,
|
|
128
|
-
}[config['align-items'] || 'start']
|
|
129
|
-
|
|
130
|
-
if (align !== 0) {
|
|
131
|
-
for (let i = 0; i < childrenLength; i++) {
|
|
132
|
-
const node = children[i]
|
|
133
|
-
node[oppositePosition] = otherDimension * align
|
|
134
|
-
node[oppositeMount] = align
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// emit an updated event
|
|
139
|
-
if (config['@updated'] !== undefined) {
|
|
140
|
-
config['@updated']({ w: this.node.width, h: this.node.height }, this)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// trigger layout on parent if parent is a layout
|
|
144
|
-
if (this.config.parent && this.config.parent.props.__layout === true) {
|
|
145
|
-
this.config.parent.triggerLayout(this.config.parent.props)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Checks if a value is a transition object.
|
|
151
|
-
* @param {any} value - The value to check.
|
|
152
|
-
* @returns {boolean} True if the value is a transition object, false otherwise.
|
|
153
|
-
*/
|
|
154
|
-
const isTransition = (value) => {
|
|
155
|
-
return value !== null && typeof value === 'object' && 'transition' in value === true
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Checks if a string is an object string (starts and ends with curly braces).
|
|
160
|
-
* @param {string} str - The string to check.
|
|
161
|
-
* @returns {boolean} True if the string is an object string, false otherwise.
|
|
162
|
-
*/
|
|
163
|
-
const isObjectString = (str) => {
|
|
164
|
-
return typeof str === 'string' && str.startsWith('{') && str.endsWith('}')
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Parses a string into an object, converting single quotes to double and adding quotes to keys.
|
|
169
|
-
* @param {string} str - The string to parse.
|
|
170
|
-
* @returns {object} The parsed object.
|
|
171
|
-
*/
|
|
172
|
-
const parseToObject = (str) => {
|
|
173
|
-
return JSON.parse(str.replace(/'/g, '"').replace(/([\w-_]+)\s*:/g, '"$1":'))
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Parses a percentage string or returns the value as-is.
|
|
178
|
-
* @param {string|number} v - The value to parse (may be a percentage string).
|
|
179
|
-
* @param {string} base - The base property name ('width' or 'height').
|
|
180
|
-
* @returns {number|string} The parsed value as a number or the original value.
|
|
181
|
-
* @this {{ element: { config: { parent?: { node?: Record<string, number> } } } }}
|
|
182
|
-
*/
|
|
183
|
-
const parsePercentage = function (v, base) {
|
|
184
|
-
if (typeof v !== 'string') {
|
|
185
|
-
return v
|
|
186
|
-
} else if (v.indexOf('%') === v.length - 1) {
|
|
187
|
-
return (
|
|
188
|
-
(this.element.config.parent &&
|
|
189
|
-
(this.element.config.parent.node[base] || 0) * (parseFloat(v) / 100)) ||
|
|
190
|
-
0
|
|
191
|
-
)
|
|
192
|
-
}
|
|
193
|
-
return v
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Unpacks a transition value from an object or returns the value as-is.
|
|
198
|
-
* @param {any} v - The value to unpack.
|
|
199
|
-
* @returns {any} The unpacked value.
|
|
200
|
-
*/
|
|
201
|
-
const unpackTransition = (v) => {
|
|
202
|
-
if (typeof v !== 'object' || v === null) return v
|
|
203
|
-
if (v.constructor === Object) {
|
|
204
|
-
if ('value' in v === true) {
|
|
205
|
-
return v.value
|
|
206
|
-
}
|
|
207
|
-
if ('transition' in v === true) {
|
|
208
|
-
return unpackTransition(v.transition)
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return v
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const colorMap = {
|
|
215
|
-
top: 'colorTop',
|
|
216
|
-
bottom: 'colorBottom',
|
|
217
|
-
left: 'colorLeft',
|
|
218
|
-
right: 'colorRight',
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Default text settings for text nodes (initialized on first use).
|
|
223
|
-
* @type {object|null}
|
|
224
|
-
*/
|
|
225
|
-
let textDefaults = null
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* @typedef {import('../../component.js').BlitsElement} BlitsElement
|
|
229
|
-
*
|
|
230
|
-
* This is always a BlitsElement
|
|
231
|
-
* @this {BlitsElement} this
|
|
232
|
-
*/
|
|
233
|
-
const propsTransformer = {
|
|
234
|
-
set parent(v) {
|
|
235
|
-
this.props['parent'] = v === 'root' ? renderer.root : v.node
|
|
236
|
-
},
|
|
237
|
-
set rotation(v) {
|
|
238
|
-
this.props['rotation'] = v * (Math.PI / 180)
|
|
239
|
-
},
|
|
240
|
-
set w(v) {
|
|
241
|
-
this.props['width'] = parsePercentage.call(this, v, 'width')
|
|
242
|
-
},
|
|
243
|
-
set width(v) {
|
|
244
|
-
this.props['width'] = parsePercentage.call(this, v, 'width')
|
|
245
|
-
},
|
|
246
|
-
set h(v) {
|
|
247
|
-
this.props['height'] = parsePercentage.call(this, v, 'height')
|
|
248
|
-
},
|
|
249
|
-
set height(v) {
|
|
250
|
-
this.props['height'] = parsePercentage.call(this, v, 'height')
|
|
251
|
-
},
|
|
252
|
-
set x(v) {
|
|
253
|
-
this.props['x'] = parsePercentage.call(this, v, 'width')
|
|
254
|
-
},
|
|
255
|
-
set y(v) {
|
|
256
|
-
this.props['y'] = parsePercentage.call(this, v, 'height')
|
|
257
|
-
},
|
|
258
|
-
set z(v) {
|
|
259
|
-
this.props['zIndex'] = v
|
|
260
|
-
},
|
|
261
|
-
set zIndex(v) {
|
|
262
|
-
this.props['zIndex'] = v
|
|
263
|
-
},
|
|
264
|
-
set color(v) {
|
|
265
|
-
if (typeof v === 'string' && v.startsWith('{') === false) {
|
|
266
|
-
this.props['color'] = colors.normalize(v)
|
|
267
|
-
} else if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
268
|
-
this.props['color'] = 0
|
|
269
|
-
Object.entries(v).forEach((color) => {
|
|
270
|
-
this.props[colorMap[color[0]]] = colors.normalize(color[1])
|
|
271
|
-
})
|
|
272
|
-
}
|
|
273
|
-
},
|
|
274
|
-
set src(v) {
|
|
275
|
-
this.props['src'] = v
|
|
276
|
-
if (this.raw['color'] === undefined) {
|
|
277
|
-
this.props['color'] = this.props['src'] ? 0xffffffff : 0x00000000
|
|
278
|
-
}
|
|
279
|
-
// apply auto sizing when no width or height specified
|
|
280
|
-
if (!('w' in this.raw) && !('w' in this.raw) && !('h' in this.raw) && !('height' in this.raw)) {
|
|
281
|
-
this.props['autosize'] = true
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
set texture(v) {
|
|
285
|
-
this.props['texture'] = v
|
|
286
|
-
|
|
287
|
-
if (this.raw['color'] === undefined && (v === null || v === undefined)) {
|
|
288
|
-
this.props['color'] = 0x00000000
|
|
289
|
-
} else if (this.raw['color'] === undefined) {
|
|
290
|
-
this.props['color'] = 0xffffffff
|
|
291
|
-
}
|
|
292
|
-
},
|
|
293
|
-
set fit(v) {
|
|
294
|
-
const resizeMode = {}
|
|
295
|
-
|
|
296
|
-
if (v === 'cover' || v === 'contain') {
|
|
297
|
-
this.props['textureOptions'] = { resizeMode: { type: v } }
|
|
298
|
-
return
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
302
|
-
resizeMode['type'] = v.type || 'cover'
|
|
303
|
-
|
|
304
|
-
if (typeof v.position === 'number') {
|
|
305
|
-
resizeMode['clipY'] = resizeMode['clipX'] = v.position
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (typeof v.position === 'object') {
|
|
309
|
-
resizeMode['clipX'] = 'x' in v.position === true ? v.position.x : null
|
|
310
|
-
resizeMode['clipY'] = 'y' in v.position === true ? v.position.y : null
|
|
311
|
-
}
|
|
312
|
-
this.props['textureOptions'] = { resizeMode }
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
set rtt(v) {
|
|
316
|
-
this.props['rtt'] = v
|
|
317
|
-
if (this.raw['color'] === undefined) {
|
|
318
|
-
this.props['color'] = v === true ? 0xffffffff : 0x00000000
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
set mount(v) {
|
|
322
|
-
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
323
|
-
if ('x' in v === true) {
|
|
324
|
-
this.props['mountX'] = v.x
|
|
325
|
-
}
|
|
326
|
-
if ('y' in v === true) {
|
|
327
|
-
this.props['mountY'] = v.y
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
this.props['mountX'] = v
|
|
331
|
-
this.props['mountY'] = v
|
|
332
|
-
}
|
|
333
|
-
},
|
|
334
|
-
set pivot(v) {
|
|
335
|
-
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
336
|
-
if ('x' in v === true) {
|
|
337
|
-
this.props['pivotX'] = v.x
|
|
338
|
-
}
|
|
339
|
-
if ('y' in v === true) {
|
|
340
|
-
this.props['pivotY'] = v.y
|
|
341
|
-
}
|
|
342
|
-
} else {
|
|
343
|
-
this.props['pivotX'] = v
|
|
344
|
-
this.props['pivotY'] = v
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
set scale(v) {
|
|
348
|
-
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
349
|
-
if ('x' in v === true) {
|
|
350
|
-
this.props['scaleX'] = v.x
|
|
351
|
-
}
|
|
352
|
-
if ('y' in v === true) {
|
|
353
|
-
this.props['scaleY'] = v.y
|
|
354
|
-
}
|
|
355
|
-
} else {
|
|
356
|
-
this.props['scale'] = v
|
|
357
|
-
}
|
|
358
|
-
},
|
|
359
|
-
set show(v) {
|
|
360
|
-
if (v) {
|
|
361
|
-
this.props['alpha'] = this.raw['alpha'] !== undefined ? this.raw['alpha'] : 1
|
|
362
|
-
} else {
|
|
363
|
-
this.props['alpha'] = 0
|
|
364
|
-
}
|
|
365
|
-
},
|
|
366
|
-
set alpha(v) {
|
|
367
|
-
if (this.raw['show'] === undefined || this.raw['show'] == true) {
|
|
368
|
-
this.props['alpha'] = v
|
|
369
|
-
}
|
|
370
|
-
},
|
|
371
|
-
set shader(v) {
|
|
372
|
-
if (v !== null) {
|
|
373
|
-
this.props['shader'] = renderer.createShader(v.type, v.props)
|
|
374
|
-
} else {
|
|
375
|
-
this.props['shader'] = renderer.createShader('DefaultShader')
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
set effects(v) {
|
|
379
|
-
for (let i = 0; i < v.length; i++) {
|
|
380
|
-
if (v[i].props && v[i].props.color) {
|
|
381
|
-
v[i].props.color = colors.normalize(v[i].props.color)
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (this.element.node === undefined) {
|
|
385
|
-
this.props['shader'] = renderer.createShader('DynamicShader', {
|
|
386
|
-
effects: v.map((effect) => {
|
|
387
|
-
return renderer.createEffect(effect.type, effect.props)
|
|
388
|
-
}),
|
|
389
|
-
})
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
set clipping(v) {
|
|
393
|
-
this.props['clipping'] = v
|
|
394
|
-
},
|
|
395
|
-
set overflow(v) {
|
|
396
|
-
this.props['clipping'] = !!!v
|
|
397
|
-
},
|
|
398
|
-
set font(v) {
|
|
399
|
-
this.props['fontFamily'] = v
|
|
400
|
-
},
|
|
401
|
-
set size(v) {
|
|
402
|
-
this.props['fontSize'] = v
|
|
403
|
-
},
|
|
404
|
-
set wordwrap(v) {
|
|
405
|
-
Log.warn('The wordwrap attribute is deprecated, use maxwidth instead')
|
|
406
|
-
this.props['width'] = v
|
|
407
|
-
this.props['contain'] = 'width'
|
|
408
|
-
},
|
|
409
|
-
set maxwidth(v) {
|
|
410
|
-
this.props['width'] = v
|
|
411
|
-
this.props['contain'] = 'width'
|
|
412
|
-
},
|
|
413
|
-
set maxheight(v) {
|
|
414
|
-
this.props['height'] = v
|
|
415
|
-
this.props['contain'] = 'both'
|
|
416
|
-
},
|
|
417
|
-
set contain(v) {
|
|
418
|
-
this.props['contain'] = v
|
|
419
|
-
},
|
|
420
|
-
set maxlines(v) {
|
|
421
|
-
this.props['maxLines'] = v
|
|
422
|
-
},
|
|
423
|
-
set textoverflow(v) {
|
|
424
|
-
this.props['overflowSuffix'] = v === false ? ' ' : v === true ? undefined : v
|
|
425
|
-
},
|
|
426
|
-
set letterspacing(v) {
|
|
427
|
-
this.props['letterSpacing'] = v || 1
|
|
428
|
-
},
|
|
429
|
-
set lineheight(v) {
|
|
430
|
-
this.props['lineHeight'] = v
|
|
431
|
-
},
|
|
432
|
-
set align(v) {
|
|
433
|
-
this.props['textAlign'] = v
|
|
434
|
-
},
|
|
435
|
-
set content(v) {
|
|
436
|
-
this.props['text'] = '' + v
|
|
437
|
-
},
|
|
438
|
-
set placement(v) {
|
|
439
|
-
let x, y
|
|
440
|
-
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
441
|
-
if ('x' in v === true) {
|
|
442
|
-
x = v.x
|
|
443
|
-
}
|
|
444
|
-
if ('y' in v === true) {
|
|
445
|
-
y = v.y
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
v === 'center' || v === 'right' ? (x = v) : (y = v)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Set X position
|
|
452
|
-
if (x === 'center') {
|
|
453
|
-
this.x = '50%'
|
|
454
|
-
this.props['mountX'] = 0.5
|
|
455
|
-
} else if (x === 'right') {
|
|
456
|
-
this.x = '100%'
|
|
457
|
-
this.props['mountX'] = 1
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Set Y position
|
|
461
|
-
if (y === 'middle') {
|
|
462
|
-
this.y = '50%'
|
|
463
|
-
this.props['mountY'] = 0.5
|
|
464
|
-
} else if (y === 'bottom') {
|
|
465
|
-
this.y = '100%'
|
|
466
|
-
this.props['mountY'] = 1
|
|
467
|
-
}
|
|
468
|
-
},
|
|
469
|
-
set 'inspector-data'(v) {
|
|
470
|
-
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
471
|
-
this.props['data'] = v
|
|
472
|
-
}
|
|
473
|
-
},
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const Element = {
|
|
477
|
-
/**
|
|
478
|
-
* Populates the element with data
|
|
479
|
-
* @param {import('../../component.js').BlitsElementProps} data
|
|
480
|
-
*/
|
|
481
|
-
populate(data) {
|
|
482
|
-
const props = data
|
|
483
|
-
props['node'] = this.config.node
|
|
484
|
-
|
|
485
|
-
if (props[symbols.isSlot] === true) {
|
|
486
|
-
this[symbols.isSlot] = true
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
this.props.element = this
|
|
490
|
-
|
|
491
|
-
this.props['parent'] = props['parent'] || this.config.parent
|
|
492
|
-
//@ts-ignore This might be a left over from the old code?
|
|
493
|
-
delete props.parent
|
|
494
|
-
|
|
495
|
-
this.props.raw = data
|
|
496
|
-
|
|
497
|
-
const propKeys = Object.keys(props)
|
|
498
|
-
const length = propKeys.length
|
|
499
|
-
|
|
500
|
-
for (let i = 0; i < length; i++) {
|
|
501
|
-
const key = propKeys[i]
|
|
502
|
-
const value = props[key]
|
|
503
|
-
if (value !== undefined) {
|
|
504
|
-
this.props[key] = unpackTransition(value)
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// correct for default white nodes (but not for text nodes)
|
|
509
|
-
if (this.props.props['color'] === undefined && '__textnode' in props === false) {
|
|
510
|
-
this.props.props['color'] = 0
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
this.node = props.__textnode
|
|
514
|
-
? renderer.createTextNode({ ...textDefaults, ...this.props.props })
|
|
515
|
-
: renderer.createNode(this.props.props)
|
|
516
|
-
|
|
517
|
-
if (props['@loaded'] !== undefined && typeof props['@loaded'] === 'function') {
|
|
518
|
-
this.node.on('loaded', (el, { type, dimensions }) => {
|
|
519
|
-
props['@loaded']({ w: dimensions.width, h: dimensions.height, type }, this)
|
|
520
|
-
})
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (props['@error'] !== undefined && typeof props['@error'] === 'function') {
|
|
524
|
-
this.node.on('failed', (el, error) => {
|
|
525
|
-
props['@error'](error, this)
|
|
526
|
-
})
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (props.__layout === true) {
|
|
530
|
-
this.triggerLayout = layoutFn.bind(this)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
if (this.config.parent.props !== undefined && this.config.parent.props.__layout === true) {
|
|
534
|
-
this.config.parent.triggerLayout(this.config.parent.props)
|
|
535
|
-
this.node.on('loaded', () => {
|
|
536
|
-
this.config.parent.triggerLayout(this.config.parent.props)
|
|
537
|
-
})
|
|
538
|
-
}
|
|
539
|
-
},
|
|
540
|
-
/**
|
|
541
|
-
* Set an individual property on the node
|
|
542
|
-
*
|
|
543
|
-
* @this {import('../../component').BlitsElement} this
|
|
544
|
-
*
|
|
545
|
-
* @param {import('../..//component.js').BlitsElementProps} prop
|
|
546
|
-
* @param {any} value
|
|
547
|
-
* @returns {void}
|
|
548
|
-
*/
|
|
549
|
-
set(prop, value) {
|
|
550
|
-
if (value === undefined) return
|
|
551
|
-
// @ts-ignore
|
|
552
|
-
if (this.props.raw[prop] === value) return
|
|
553
|
-
// @ts-ignore
|
|
554
|
-
this.props.raw[prop] = value
|
|
555
|
-
|
|
556
|
-
this.props.props = {}
|
|
557
|
-
// @ts-ignore
|
|
558
|
-
this.props[prop] = unpackTransition(value)
|
|
559
|
-
|
|
560
|
-
const propsKeys = Object.keys(this.props.props)
|
|
561
|
-
|
|
562
|
-
if (propsKeys.length === 1) {
|
|
563
|
-
if (isTransition(value) === true) {
|
|
564
|
-
return this.animate(propsKeys[0], this.props.props[propsKeys[0]], value.transition)
|
|
565
|
-
}
|
|
566
|
-
// set the prop to the value on the node
|
|
567
|
-
this.node[propsKeys[0]] = this.props.props[propsKeys[0]]
|
|
568
|
-
} else {
|
|
569
|
-
for (let i = 0; i < propsKeys.length; i++) {
|
|
570
|
-
// todo: fix code duplication
|
|
571
|
-
if (isTransition(value) === true) {
|
|
572
|
-
return this.animate(propsKeys[i], this.props.props[propsKeys[i]], value.transition)
|
|
573
|
-
}
|
|
574
|
-
// set the prop to the value on the node
|
|
575
|
-
this.node[propsKeys[i]] = this.props.props[propsKeys[i]]
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (this.config.parent.props && this.config.parent.props.__layout === true) {
|
|
580
|
-
this.config.parent.triggerLayout(this.config.parent.props)
|
|
581
|
-
}
|
|
582
|
-
},
|
|
583
|
-
animate(prop, value, transition) {
|
|
584
|
-
// check if a transition is already scheduled to run on the same prop
|
|
585
|
-
// and cancels it if it does
|
|
586
|
-
if (
|
|
587
|
-
this.scheduledTransitions[prop] !== undefined &&
|
|
588
|
-
this.scheduledTransitions[prop].f.state === 'scheduled'
|
|
589
|
-
) {
|
|
590
|
-
this.scheduledTransitions[prop].f.stop()
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// if current value is the same as the value to animate to, instantly resolve
|
|
594
|
-
if (this.node[prop] === value) return
|
|
595
|
-
|
|
596
|
-
const props = {}
|
|
597
|
-
props[prop] = value
|
|
598
|
-
|
|
599
|
-
// construct animate function
|
|
600
|
-
const f = this.node.animate(props, {
|
|
601
|
-
duration:
|
|
602
|
-
typeof transition === 'object'
|
|
603
|
-
? 'duration' in transition
|
|
604
|
-
? transition.duration
|
|
605
|
-
: 300
|
|
606
|
-
: 300,
|
|
607
|
-
easing:
|
|
608
|
-
typeof transition === 'object'
|
|
609
|
-
? 'easing' in transition
|
|
610
|
-
? transition.easing
|
|
611
|
-
: 'ease'
|
|
612
|
-
: 'ease',
|
|
613
|
-
delay: typeof transition === 'object' ? ('delay' in transition ? transition.delay : 0) : 0,
|
|
614
|
-
})
|
|
615
|
-
|
|
616
|
-
// capture the current value to be used in the transition start
|
|
617
|
-
const startValue = this.node[prop]
|
|
618
|
-
|
|
619
|
-
// schedule the transition for this prop, so it can be canceled /
|
|
620
|
-
// removed if another transition for the same prop starts in the mean time
|
|
621
|
-
this.scheduledTransitions[prop] = {
|
|
622
|
-
v: props[prop],
|
|
623
|
-
f,
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
if (transition.start !== undefined && typeof transition.start === 'function') {
|
|
627
|
-
// fire transition start callback when animation really starts (depending on specified delay)
|
|
628
|
-
f.once('animating', () => {
|
|
629
|
-
transition.start.call(this.component, this, prop, startValue)
|
|
630
|
-
})
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
if (this.config.parent.props && this.config.parent.props.__layout === true) {
|
|
634
|
-
f.on('tick', () => {
|
|
635
|
-
this.config.parent.triggerLayout(this.config.parent.props)
|
|
636
|
-
})
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if (transition.progress !== undefined && typeof transition.progress === 'function') {
|
|
640
|
-
let prevProgress = 0
|
|
641
|
-
f.on('tick', (_node, { progress }) => {
|
|
642
|
-
transition.progress.call(this.component, this, prop, progress, prevProgress)
|
|
643
|
-
prevProgress = progress
|
|
644
|
-
})
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
f.once('stopped', () => {
|
|
648
|
-
if (
|
|
649
|
-
this.scheduledTransitions[prop] !== undefined &&
|
|
650
|
-
this.scheduledTransitions[prop].canceled === true
|
|
651
|
-
) {
|
|
652
|
-
return
|
|
653
|
-
}
|
|
654
|
-
// fire transition end callback when animation ends (if specified)
|
|
655
|
-
if (this.node !== undefined && transition.end && typeof transition.end === 'function') {
|
|
656
|
-
transition.end.call(this.component, this, prop, this.node[prop])
|
|
657
|
-
}
|
|
658
|
-
// remove the prop from scheduled transitions
|
|
659
|
-
delete this.scheduledTransitions[prop]
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
// start animation
|
|
663
|
-
f.start()
|
|
664
|
-
},
|
|
665
|
-
destroy() {
|
|
666
|
-
if (this.node === null) return
|
|
667
|
-
|
|
668
|
-
Log.debug('Deleting Node', this.nodeId)
|
|
669
|
-
// Clearing transition end callback functions
|
|
670
|
-
const transitionProps = Object.keys(this.scheduledTransitions)
|
|
671
|
-
for (let i = 0; i < transitionProps.length; i++) {
|
|
672
|
-
const transition = this.scheduledTransitions[transitionProps[i]]
|
|
673
|
-
if (transition !== undefined) {
|
|
674
|
-
transition.canceled = true
|
|
675
|
-
if (transition.f !== undefined) transition.f.stop()
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// not setting to null and deleting,
|
|
680
|
-
// because transition stopped might still be fired (maybe a renderer fix resolves that)
|
|
681
|
-
this.scheduledTransitions = {}
|
|
682
|
-
|
|
683
|
-
this.component = null
|
|
684
|
-
delete this.component
|
|
685
|
-
|
|
686
|
-
this.config = null
|
|
687
|
-
delete this.config
|
|
688
|
-
|
|
689
|
-
this.counter = null
|
|
690
|
-
delete this.counter
|
|
691
|
-
|
|
692
|
-
this.props.raw = {}
|
|
693
|
-
this.props.element = null
|
|
694
|
-
this.props.props = null
|
|
695
|
-
this.props = {}
|
|
696
|
-
delete this.props
|
|
697
|
-
|
|
698
|
-
this.triggerLayout = null
|
|
699
|
-
delete this.triggerLayout
|
|
700
|
-
|
|
701
|
-
this.forComponent = null
|
|
702
|
-
delete this.forComponent
|
|
703
|
-
|
|
704
|
-
this.node.destroy()
|
|
705
|
-
this.node = null
|
|
706
|
-
},
|
|
707
|
-
get nodeId() {
|
|
708
|
-
return this.node && this.node.id
|
|
709
|
-
},
|
|
710
|
-
get ref() {
|
|
711
|
-
return this.props.ref || null
|
|
712
|
-
},
|
|
713
|
-
get parent() {
|
|
714
|
-
return this.node && this.node.parent
|
|
715
|
-
},
|
|
716
|
-
get children() {
|
|
717
|
-
return this.component[symbols.getChildren]().filter((child) => {
|
|
718
|
-
return child.parent === (this[symbols.isSlot] ? this.node.children[0] : this.node)
|
|
719
|
-
})
|
|
720
|
-
},
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* Returns a new Blits Element
|
|
725
|
-
*
|
|
726
|
-
* @param {Object} config - The configuration object for the element
|
|
727
|
-
* @param {import('../../component.js').BlitsComponent} component - The component to which the element belongs
|
|
728
|
-
* @returns {import('../../component.js').BlitsElement} - The new Blits Element
|
|
729
|
-
*/
|
|
730
|
-
export default (config, component) => {
|
|
731
|
-
if (textDefaults === null) {
|
|
732
|
-
textDefaults = {
|
|
733
|
-
fontSize: 32,
|
|
734
|
-
fontFamily: Settings.get('defaultFont', 'sans-serif'),
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
return Object.assign(Object.create(Element), {
|
|
738
|
-
props: Object.assign(Object.create(propsTransformer), { props: {} }),
|
|
739
|
-
scheduledTransitions: {},
|
|
740
|
-
config,
|
|
741
|
-
component,
|
|
742
|
-
counter: counter++,
|
|
743
|
-
})
|
|
744
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 Comcast Cable Communications Management, LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { renderer } from './launch.js'
|
|
19
|
+
import colors from '../../lib/colors/colors.js'
|
|
20
|
+
|
|
21
|
+
import { Log } from '../../lib/log.js'
|
|
22
|
+
import symbols from '../../lib/symbols.js'
|
|
23
|
+
import Settings from '../../settings.js'
|
|
24
|
+
|
|
25
|
+
// temporary counter to work around shader caching issues
|
|
26
|
+
let counter = 0
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a padding object from a value and direction.
|
|
30
|
+
* @param {number|object|string|undefined} padding - The padding value.
|
|
31
|
+
* @param {string} direction - The layout direction ('vertical' or 'horizontal').
|
|
32
|
+
* @returns {{start: number, end: number, oppositeStart: number, oppositeEnd: number}} The padding object.
|
|
33
|
+
*/
|
|
34
|
+
const createPaddingObject = (padding, direction) => {
|
|
35
|
+
if (padding === undefined) {
|
|
36
|
+
return { start: 0, end: 0, oppositeStart: 0, oppositeEnd: 0 }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof padding === 'number') {
|
|
40
|
+
return { start: padding, end: padding, oppositeStart: padding, oppositeEnd: padding }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// todo: do we need to do this runtime every time? or can we optimize this?
|
|
44
|
+
if (isObjectString(padding) === true) {
|
|
45
|
+
padding = parseToObject(padding)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof padding === 'object') {
|
|
49
|
+
const {
|
|
50
|
+
top = undefined,
|
|
51
|
+
right = undefined,
|
|
52
|
+
bottom = undefined,
|
|
53
|
+
left = undefined,
|
|
54
|
+
x = 0,
|
|
55
|
+
y = 0,
|
|
56
|
+
} = padding
|
|
57
|
+
|
|
58
|
+
// use specific values if provided, otherwise fall back to x or y
|
|
59
|
+
return direction === 'vertical'
|
|
60
|
+
? {
|
|
61
|
+
start: top !== undefined ? top : y,
|
|
62
|
+
end: bottom !== undefined ? bottom : y,
|
|
63
|
+
oppositeStart: left !== undefined ? left : x,
|
|
64
|
+
oppositeEnd: right !== undefined ? right : x,
|
|
65
|
+
}
|
|
66
|
+
: {
|
|
67
|
+
start: left !== undefined ? left : x,
|
|
68
|
+
end: right !== undefined ? right : x,
|
|
69
|
+
oppositeStart: top !== undefined ? top : y,
|
|
70
|
+
oppositeEnd: bottom !== undefined ? bottom : y,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { start: 0, end: 0, oppositeStart: 0, oppositeEnd: 0 }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Layout function for arranging children in a layout container.
|
|
78
|
+
* @param {object} config - The layout configuration object.
|
|
79
|
+
* @this {import('../../component.js').BlitsElement}
|
|
80
|
+
*/
|
|
81
|
+
const layoutFn = function (config) {
|
|
82
|
+
const position = config.direction === 'vertical' ? 'y' : 'x'
|
|
83
|
+
const oppositePosition = config.direction === 'vertical' ? 'x' : 'y'
|
|
84
|
+
const oppositeMount = config.direction === 'vertical' ? 'mountX' : 'mountY'
|
|
85
|
+
const dimension = config.direction === 'vertical' ? 'height' : 'width'
|
|
86
|
+
const oppositeDimension = config.direction === 'vertical' ? 'width' : 'height'
|
|
87
|
+
const padding = createPaddingObject(config.padding, config.direction)
|
|
88
|
+
|
|
89
|
+
let offset = padding.start
|
|
90
|
+
|
|
91
|
+
const children = this.node.children
|
|
92
|
+
const childrenLength = children.length
|
|
93
|
+
let otherDimension = 0
|
|
94
|
+
const gap = config.gap || 0
|
|
95
|
+
for (let i = 0; i < childrenLength; i++) {
|
|
96
|
+
if (this.children[i] !== undefined && this.children[i].props.raw.show === false) {
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
const node = children[i]
|
|
100
|
+
node[position] = offset
|
|
101
|
+
node[oppositePosition] = padding.oppositeStart
|
|
102
|
+
// todo: temporary text check, due to 1px width of empty text node
|
|
103
|
+
if (dimension === 'width') {
|
|
104
|
+
offset += node.width + (node.width !== ('text' in node ? 1 : 0) ? gap : 0)
|
|
105
|
+
} else {
|
|
106
|
+
offset +=
|
|
107
|
+
'text' in node
|
|
108
|
+
? node.width > 1
|
|
109
|
+
? node.height + gap
|
|
110
|
+
: 0
|
|
111
|
+
: node.height !== 0
|
|
112
|
+
? node.height + gap
|
|
113
|
+
: 0
|
|
114
|
+
}
|
|
115
|
+
otherDimension = Math.max(
|
|
116
|
+
otherDimension,
|
|
117
|
+
node[oppositeDimension] + padding.oppositeStart + padding.oppositeEnd
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
// adjust the size of the layout container
|
|
121
|
+
this.node[dimension] = offset - gap + padding.end
|
|
122
|
+
this.node[oppositeDimension] = otherDimension
|
|
123
|
+
|
|
124
|
+
const align = {
|
|
125
|
+
start: 0,
|
|
126
|
+
end: 1,
|
|
127
|
+
center: 0.5,
|
|
128
|
+
}[config['align-items'] || 'start']
|
|
129
|
+
|
|
130
|
+
if (align !== 0) {
|
|
131
|
+
for (let i = 0; i < childrenLength; i++) {
|
|
132
|
+
const node = children[i]
|
|
133
|
+
node[oppositePosition] = otherDimension * align
|
|
134
|
+
node[oppositeMount] = align
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// emit an updated event
|
|
139
|
+
if (config['@updated'] !== undefined) {
|
|
140
|
+
config['@updated']({ w: this.node.width, h: this.node.height }, this)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// trigger layout on parent if parent is a layout
|
|
144
|
+
if (this.config.parent && this.config.parent.props.__layout === true) {
|
|
145
|
+
this.config.parent.triggerLayout(this.config.parent.props)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Checks if a value is a transition object.
|
|
151
|
+
* @param {any} value - The value to check.
|
|
152
|
+
* @returns {boolean} True if the value is a transition object, false otherwise.
|
|
153
|
+
*/
|
|
154
|
+
const isTransition = (value) => {
|
|
155
|
+
return value !== null && typeof value === 'object' && 'transition' in value === true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Checks if a string is an object string (starts and ends with curly braces).
|
|
160
|
+
* @param {string} str - The string to check.
|
|
161
|
+
* @returns {boolean} True if the string is an object string, false otherwise.
|
|
162
|
+
*/
|
|
163
|
+
const isObjectString = (str) => {
|
|
164
|
+
return typeof str === 'string' && str.startsWith('{') && str.endsWith('}')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parses a string into an object, converting single quotes to double and adding quotes to keys.
|
|
169
|
+
* @param {string} str - The string to parse.
|
|
170
|
+
* @returns {object} The parsed object.
|
|
171
|
+
*/
|
|
172
|
+
const parseToObject = (str) => {
|
|
173
|
+
return JSON.parse(str.replace(/'/g, '"').replace(/([\w-_]+)\s*:/g, '"$1":'))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Parses a percentage string or returns the value as-is.
|
|
178
|
+
* @param {string|number} v - The value to parse (may be a percentage string).
|
|
179
|
+
* @param {string} base - The base property name ('width' or 'height').
|
|
180
|
+
* @returns {number|string} The parsed value as a number or the original value.
|
|
181
|
+
* @this {{ element: { config: { parent?: { node?: Record<string, number> } } } }}
|
|
182
|
+
*/
|
|
183
|
+
const parsePercentage = function (v, base) {
|
|
184
|
+
if (typeof v !== 'string') {
|
|
185
|
+
return v
|
|
186
|
+
} else if (v.indexOf('%') === v.length - 1) {
|
|
187
|
+
return (
|
|
188
|
+
(this.element.config.parent &&
|
|
189
|
+
(this.element.config.parent.node[base] || 0) * (parseFloat(v) / 100)) ||
|
|
190
|
+
0
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
return v
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Unpacks a transition value from an object or returns the value as-is.
|
|
198
|
+
* @param {any} v - The value to unpack.
|
|
199
|
+
* @returns {any} The unpacked value.
|
|
200
|
+
*/
|
|
201
|
+
const unpackTransition = (v) => {
|
|
202
|
+
if (typeof v !== 'object' || v === null) return v
|
|
203
|
+
if (v.constructor === Object) {
|
|
204
|
+
if ('value' in v === true) {
|
|
205
|
+
return v.value
|
|
206
|
+
}
|
|
207
|
+
if ('transition' in v === true) {
|
|
208
|
+
return unpackTransition(v.transition)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return v
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const colorMap = {
|
|
215
|
+
top: 'colorTop',
|
|
216
|
+
bottom: 'colorBottom',
|
|
217
|
+
left: 'colorLeft',
|
|
218
|
+
right: 'colorRight',
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Default text settings for text nodes (initialized on first use).
|
|
223
|
+
* @type {object|null}
|
|
224
|
+
*/
|
|
225
|
+
let textDefaults = null
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @typedef {import('../../component.js').BlitsElement} BlitsElement
|
|
229
|
+
*
|
|
230
|
+
* This is always a BlitsElement
|
|
231
|
+
* @this {BlitsElement} this
|
|
232
|
+
*/
|
|
233
|
+
const propsTransformer = {
|
|
234
|
+
set parent(v) {
|
|
235
|
+
this.props['parent'] = v === 'root' ? renderer.root : v.node
|
|
236
|
+
},
|
|
237
|
+
set rotation(v) {
|
|
238
|
+
this.props['rotation'] = v * (Math.PI / 180)
|
|
239
|
+
},
|
|
240
|
+
set w(v) {
|
|
241
|
+
this.props['width'] = parsePercentage.call(this, v, 'width')
|
|
242
|
+
},
|
|
243
|
+
set width(v) {
|
|
244
|
+
this.props['width'] = parsePercentage.call(this, v, 'width')
|
|
245
|
+
},
|
|
246
|
+
set h(v) {
|
|
247
|
+
this.props['height'] = parsePercentage.call(this, v, 'height')
|
|
248
|
+
},
|
|
249
|
+
set height(v) {
|
|
250
|
+
this.props['height'] = parsePercentage.call(this, v, 'height')
|
|
251
|
+
},
|
|
252
|
+
set x(v) {
|
|
253
|
+
this.props['x'] = parsePercentage.call(this, v, 'width')
|
|
254
|
+
},
|
|
255
|
+
set y(v) {
|
|
256
|
+
this.props['y'] = parsePercentage.call(this, v, 'height')
|
|
257
|
+
},
|
|
258
|
+
set z(v) {
|
|
259
|
+
this.props['zIndex'] = v
|
|
260
|
+
},
|
|
261
|
+
set zIndex(v) {
|
|
262
|
+
this.props['zIndex'] = v
|
|
263
|
+
},
|
|
264
|
+
set color(v) {
|
|
265
|
+
if (typeof v === 'string' && v.startsWith('{') === false) {
|
|
266
|
+
this.props['color'] = colors.normalize(v)
|
|
267
|
+
} else if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
268
|
+
this.props['color'] = 0
|
|
269
|
+
Object.entries(v).forEach((color) => {
|
|
270
|
+
this.props[colorMap[color[0]]] = colors.normalize(color[1])
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
set src(v) {
|
|
275
|
+
this.props['src'] = v
|
|
276
|
+
if (this.raw['color'] === undefined) {
|
|
277
|
+
this.props['color'] = this.props['src'] ? 0xffffffff : 0x00000000
|
|
278
|
+
}
|
|
279
|
+
// apply auto sizing when no width or height specified
|
|
280
|
+
if (!('w' in this.raw) && !('w' in this.raw) && !('h' in this.raw) && !('height' in this.raw)) {
|
|
281
|
+
this.props['autosize'] = true
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
set texture(v) {
|
|
285
|
+
this.props['texture'] = v
|
|
286
|
+
|
|
287
|
+
if (this.raw['color'] === undefined && (v === null || v === undefined)) {
|
|
288
|
+
this.props['color'] = 0x00000000
|
|
289
|
+
} else if (this.raw['color'] === undefined) {
|
|
290
|
+
this.props['color'] = 0xffffffff
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
set fit(v) {
|
|
294
|
+
const resizeMode = {}
|
|
295
|
+
|
|
296
|
+
if (v === 'cover' || v === 'contain') {
|
|
297
|
+
this.props['textureOptions'] = { resizeMode: { type: v } }
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
302
|
+
resizeMode['type'] = v.type || 'cover'
|
|
303
|
+
|
|
304
|
+
if (typeof v.position === 'number') {
|
|
305
|
+
resizeMode['clipY'] = resizeMode['clipX'] = v.position
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (typeof v.position === 'object') {
|
|
309
|
+
resizeMode['clipX'] = 'x' in v.position === true ? v.position.x : null
|
|
310
|
+
resizeMode['clipY'] = 'y' in v.position === true ? v.position.y : null
|
|
311
|
+
}
|
|
312
|
+
this.props['textureOptions'] = { resizeMode }
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
set rtt(v) {
|
|
316
|
+
this.props['rtt'] = v
|
|
317
|
+
if (this.raw['color'] === undefined) {
|
|
318
|
+
this.props['color'] = v === true ? 0xffffffff : 0x00000000
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
set mount(v) {
|
|
322
|
+
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
323
|
+
if ('x' in v === true) {
|
|
324
|
+
this.props['mountX'] = v.x
|
|
325
|
+
}
|
|
326
|
+
if ('y' in v === true) {
|
|
327
|
+
this.props['mountY'] = v.y
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
this.props['mountX'] = v
|
|
331
|
+
this.props['mountY'] = v
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
set pivot(v) {
|
|
335
|
+
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
336
|
+
if ('x' in v === true) {
|
|
337
|
+
this.props['pivotX'] = v.x
|
|
338
|
+
}
|
|
339
|
+
if ('y' in v === true) {
|
|
340
|
+
this.props['pivotY'] = v.y
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
this.props['pivotX'] = v
|
|
344
|
+
this.props['pivotY'] = v
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
set scale(v) {
|
|
348
|
+
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
349
|
+
if ('x' in v === true) {
|
|
350
|
+
this.props['scaleX'] = v.x
|
|
351
|
+
}
|
|
352
|
+
if ('y' in v === true) {
|
|
353
|
+
this.props['scaleY'] = v.y
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
this.props['scale'] = v
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
set show(v) {
|
|
360
|
+
if (v) {
|
|
361
|
+
this.props['alpha'] = this.raw['alpha'] !== undefined ? this.raw['alpha'] : 1
|
|
362
|
+
} else {
|
|
363
|
+
this.props['alpha'] = 0
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
set alpha(v) {
|
|
367
|
+
if (this.raw['show'] === undefined || this.raw['show'] == true) {
|
|
368
|
+
this.props['alpha'] = v
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
set shader(v) {
|
|
372
|
+
if (v !== null) {
|
|
373
|
+
this.props['shader'] = renderer.createShader(v.type, v.props)
|
|
374
|
+
} else {
|
|
375
|
+
this.props['shader'] = renderer.createShader('DefaultShader')
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
set effects(v) {
|
|
379
|
+
for (let i = 0; i < v.length; i++) {
|
|
380
|
+
if (v[i].props && v[i].props.color) {
|
|
381
|
+
v[i].props.color = colors.normalize(v[i].props.color)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (this.element.node === undefined) {
|
|
385
|
+
this.props['shader'] = renderer.createShader('DynamicShader', {
|
|
386
|
+
effects: v.map((effect) => {
|
|
387
|
+
return renderer.createEffect(effect.type, effect.props)
|
|
388
|
+
}),
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
set clipping(v) {
|
|
393
|
+
this.props['clipping'] = v
|
|
394
|
+
},
|
|
395
|
+
set overflow(v) {
|
|
396
|
+
this.props['clipping'] = !!!v
|
|
397
|
+
},
|
|
398
|
+
set font(v) {
|
|
399
|
+
this.props['fontFamily'] = v
|
|
400
|
+
},
|
|
401
|
+
set size(v) {
|
|
402
|
+
this.props['fontSize'] = v
|
|
403
|
+
},
|
|
404
|
+
set wordwrap(v) {
|
|
405
|
+
Log.warn('The wordwrap attribute is deprecated, use maxwidth instead')
|
|
406
|
+
this.props['width'] = v
|
|
407
|
+
this.props['contain'] = 'width'
|
|
408
|
+
},
|
|
409
|
+
set maxwidth(v) {
|
|
410
|
+
this.props['width'] = v
|
|
411
|
+
this.props['contain'] = 'width'
|
|
412
|
+
},
|
|
413
|
+
set maxheight(v) {
|
|
414
|
+
this.props['height'] = v
|
|
415
|
+
this.props['contain'] = 'both'
|
|
416
|
+
},
|
|
417
|
+
set contain(v) {
|
|
418
|
+
this.props['contain'] = v
|
|
419
|
+
},
|
|
420
|
+
set maxlines(v) {
|
|
421
|
+
this.props['maxLines'] = v
|
|
422
|
+
},
|
|
423
|
+
set textoverflow(v) {
|
|
424
|
+
this.props['overflowSuffix'] = v === false ? ' ' : v === true ? undefined : v
|
|
425
|
+
},
|
|
426
|
+
set letterspacing(v) {
|
|
427
|
+
this.props['letterSpacing'] = v || 1
|
|
428
|
+
},
|
|
429
|
+
set lineheight(v) {
|
|
430
|
+
this.props['lineHeight'] = v
|
|
431
|
+
},
|
|
432
|
+
set align(v) {
|
|
433
|
+
this.props['textAlign'] = v
|
|
434
|
+
},
|
|
435
|
+
set content(v) {
|
|
436
|
+
this.props['text'] = '' + v
|
|
437
|
+
},
|
|
438
|
+
set placement(v) {
|
|
439
|
+
let x, y
|
|
440
|
+
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
441
|
+
if ('x' in v === true) {
|
|
442
|
+
x = v.x
|
|
443
|
+
}
|
|
444
|
+
if ('y' in v === true) {
|
|
445
|
+
y = v.y
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
v === 'center' || v === 'right' ? (x = v) : (y = v)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Set X position
|
|
452
|
+
if (x === 'center') {
|
|
453
|
+
this.x = '50%'
|
|
454
|
+
this.props['mountX'] = 0.5
|
|
455
|
+
} else if (x === 'right') {
|
|
456
|
+
this.x = '100%'
|
|
457
|
+
this.props['mountX'] = 1
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Set Y position
|
|
461
|
+
if (y === 'middle') {
|
|
462
|
+
this.y = '50%'
|
|
463
|
+
this.props['mountY'] = 0.5
|
|
464
|
+
} else if (y === 'bottom') {
|
|
465
|
+
this.y = '100%'
|
|
466
|
+
this.props['mountY'] = 1
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
set 'inspector-data'(v) {
|
|
470
|
+
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
471
|
+
this.props['data'] = v
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const Element = {
|
|
477
|
+
/**
|
|
478
|
+
* Populates the element with data
|
|
479
|
+
* @param {import('../../component.js').BlitsElementProps} data
|
|
480
|
+
*/
|
|
481
|
+
populate(data) {
|
|
482
|
+
const props = data
|
|
483
|
+
props['node'] = this.config.node
|
|
484
|
+
|
|
485
|
+
if (props[symbols.isSlot] === true) {
|
|
486
|
+
this[symbols.isSlot] = true
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
this.props.element = this
|
|
490
|
+
|
|
491
|
+
this.props['parent'] = props['parent'] || this.config.parent
|
|
492
|
+
//@ts-ignore This might be a left over from the old code?
|
|
493
|
+
delete props.parent
|
|
494
|
+
|
|
495
|
+
this.props.raw = data
|
|
496
|
+
|
|
497
|
+
const propKeys = Object.keys(props)
|
|
498
|
+
const length = propKeys.length
|
|
499
|
+
|
|
500
|
+
for (let i = 0; i < length; i++) {
|
|
501
|
+
const key = propKeys[i]
|
|
502
|
+
const value = props[key]
|
|
503
|
+
if (value !== undefined) {
|
|
504
|
+
this.props[key] = unpackTransition(value)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// correct for default white nodes (but not for text nodes)
|
|
509
|
+
if (this.props.props['color'] === undefined && '__textnode' in props === false) {
|
|
510
|
+
this.props.props['color'] = 0
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this.node = props.__textnode
|
|
514
|
+
? renderer.createTextNode({ ...textDefaults, ...this.props.props })
|
|
515
|
+
: renderer.createNode(this.props.props)
|
|
516
|
+
|
|
517
|
+
if (props['@loaded'] !== undefined && typeof props['@loaded'] === 'function') {
|
|
518
|
+
this.node.on('loaded', (el, { type, dimensions }) => {
|
|
519
|
+
props['@loaded']({ w: dimensions.width, h: dimensions.height, type }, this)
|
|
520
|
+
})
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (props['@error'] !== undefined && typeof props['@error'] === 'function') {
|
|
524
|
+
this.node.on('failed', (el, error) => {
|
|
525
|
+
props['@error'](error, this)
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (props.__layout === true) {
|
|
530
|
+
this.triggerLayout = layoutFn.bind(this)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (this.config.parent.props !== undefined && this.config.parent.props.__layout === true) {
|
|
534
|
+
this.config.parent.triggerLayout(this.config.parent.props)
|
|
535
|
+
this.node.on('loaded', () => {
|
|
536
|
+
this.config.parent.triggerLayout(this.config.parent.props)
|
|
537
|
+
})
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
/**
|
|
541
|
+
* Set an individual property on the node
|
|
542
|
+
*
|
|
543
|
+
* @this {import('../../component').BlitsElement} this
|
|
544
|
+
*
|
|
545
|
+
* @param {import('../..//component.js').BlitsElementProps} prop
|
|
546
|
+
* @param {any} value
|
|
547
|
+
* @returns {void}
|
|
548
|
+
*/
|
|
549
|
+
set(prop, value) {
|
|
550
|
+
if (value === undefined) return
|
|
551
|
+
// @ts-ignore
|
|
552
|
+
if (this.props.raw[prop] === value) return
|
|
553
|
+
// @ts-ignore
|
|
554
|
+
this.props.raw[prop] = value
|
|
555
|
+
|
|
556
|
+
this.props.props = {}
|
|
557
|
+
// @ts-ignore
|
|
558
|
+
this.props[prop] = unpackTransition(value)
|
|
559
|
+
|
|
560
|
+
const propsKeys = Object.keys(this.props.props)
|
|
561
|
+
|
|
562
|
+
if (propsKeys.length === 1) {
|
|
563
|
+
if (isTransition(value) === true) {
|
|
564
|
+
return this.animate(propsKeys[0], this.props.props[propsKeys[0]], value.transition)
|
|
565
|
+
}
|
|
566
|
+
// set the prop to the value on the node
|
|
567
|
+
this.node[propsKeys[0]] = this.props.props[propsKeys[0]]
|
|
568
|
+
} else {
|
|
569
|
+
for (let i = 0; i < propsKeys.length; i++) {
|
|
570
|
+
// todo: fix code duplication
|
|
571
|
+
if (isTransition(value) === true) {
|
|
572
|
+
return this.animate(propsKeys[i], this.props.props[propsKeys[i]], value.transition)
|
|
573
|
+
}
|
|
574
|
+
// set the prop to the value on the node
|
|
575
|
+
this.node[propsKeys[i]] = this.props.props[propsKeys[i]]
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (this.config.parent.props && this.config.parent.props.__layout === true) {
|
|
580
|
+
this.config.parent.triggerLayout(this.config.parent.props)
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
animate(prop, value, transition) {
|
|
584
|
+
// check if a transition is already scheduled to run on the same prop
|
|
585
|
+
// and cancels it if it does
|
|
586
|
+
if (
|
|
587
|
+
this.scheduledTransitions[prop] !== undefined &&
|
|
588
|
+
this.scheduledTransitions[prop].f.state === 'scheduled'
|
|
589
|
+
) {
|
|
590
|
+
this.scheduledTransitions[prop].f.stop()
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// if current value is the same as the value to animate to, instantly resolve
|
|
594
|
+
if (this.node[prop] === value) return
|
|
595
|
+
|
|
596
|
+
const props = {}
|
|
597
|
+
props[prop] = value
|
|
598
|
+
|
|
599
|
+
// construct animate function
|
|
600
|
+
const f = this.node.animate(props, {
|
|
601
|
+
duration:
|
|
602
|
+
typeof transition === 'object'
|
|
603
|
+
? 'duration' in transition
|
|
604
|
+
? transition.duration
|
|
605
|
+
: 300
|
|
606
|
+
: 300,
|
|
607
|
+
easing:
|
|
608
|
+
typeof transition === 'object'
|
|
609
|
+
? 'easing' in transition
|
|
610
|
+
? transition.easing
|
|
611
|
+
: 'ease'
|
|
612
|
+
: 'ease',
|
|
613
|
+
delay: typeof transition === 'object' ? ('delay' in transition ? transition.delay : 0) : 0,
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
// capture the current value to be used in the transition start
|
|
617
|
+
const startValue = this.node[prop]
|
|
618
|
+
|
|
619
|
+
// schedule the transition for this prop, so it can be canceled /
|
|
620
|
+
// removed if another transition for the same prop starts in the mean time
|
|
621
|
+
this.scheduledTransitions[prop] = {
|
|
622
|
+
v: props[prop],
|
|
623
|
+
f,
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (transition.start !== undefined && typeof transition.start === 'function') {
|
|
627
|
+
// fire transition start callback when animation really starts (depending on specified delay)
|
|
628
|
+
f.once('animating', () => {
|
|
629
|
+
transition.start.call(this.component, this, prop, startValue)
|
|
630
|
+
})
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (this.config.parent.props && this.config.parent.props.__layout === true) {
|
|
634
|
+
f.on('tick', () => {
|
|
635
|
+
this.config.parent.triggerLayout(this.config.parent.props)
|
|
636
|
+
})
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (transition.progress !== undefined && typeof transition.progress === 'function') {
|
|
640
|
+
let prevProgress = 0
|
|
641
|
+
f.on('tick', (_node, { progress }) => {
|
|
642
|
+
transition.progress.call(this.component, this, prop, progress, prevProgress)
|
|
643
|
+
prevProgress = progress
|
|
644
|
+
})
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
f.once('stopped', () => {
|
|
648
|
+
if (
|
|
649
|
+
this.scheduledTransitions[prop] !== undefined &&
|
|
650
|
+
this.scheduledTransitions[prop].canceled === true
|
|
651
|
+
) {
|
|
652
|
+
return
|
|
653
|
+
}
|
|
654
|
+
// fire transition end callback when animation ends (if specified)
|
|
655
|
+
if (this.node !== undefined && transition.end && typeof transition.end === 'function') {
|
|
656
|
+
transition.end.call(this.component, this, prop, this.node[prop])
|
|
657
|
+
}
|
|
658
|
+
// remove the prop from scheduled transitions
|
|
659
|
+
delete this.scheduledTransitions[prop]
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
// start animation
|
|
663
|
+
f.start()
|
|
664
|
+
},
|
|
665
|
+
destroy() {
|
|
666
|
+
if (this.node === null) return
|
|
667
|
+
|
|
668
|
+
Log.debug('Deleting Node', this.nodeId)
|
|
669
|
+
// Clearing transition end callback functions
|
|
670
|
+
const transitionProps = Object.keys(this.scheduledTransitions)
|
|
671
|
+
for (let i = 0; i < transitionProps.length; i++) {
|
|
672
|
+
const transition = this.scheduledTransitions[transitionProps[i]]
|
|
673
|
+
if (transition !== undefined) {
|
|
674
|
+
transition.canceled = true
|
|
675
|
+
if (transition.f !== undefined) transition.f.stop()
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// not setting to null and deleting,
|
|
680
|
+
// because transition stopped might still be fired (maybe a renderer fix resolves that)
|
|
681
|
+
this.scheduledTransitions = {}
|
|
682
|
+
|
|
683
|
+
this.component = null
|
|
684
|
+
delete this.component
|
|
685
|
+
|
|
686
|
+
this.config = null
|
|
687
|
+
delete this.config
|
|
688
|
+
|
|
689
|
+
this.counter = null
|
|
690
|
+
delete this.counter
|
|
691
|
+
|
|
692
|
+
this.props.raw = {}
|
|
693
|
+
this.props.element = null
|
|
694
|
+
this.props.props = null
|
|
695
|
+
this.props = {}
|
|
696
|
+
delete this.props
|
|
697
|
+
|
|
698
|
+
this.triggerLayout = null
|
|
699
|
+
delete this.triggerLayout
|
|
700
|
+
|
|
701
|
+
this.forComponent = null
|
|
702
|
+
delete this.forComponent
|
|
703
|
+
|
|
704
|
+
this.node.destroy()
|
|
705
|
+
this.node = null
|
|
706
|
+
},
|
|
707
|
+
get nodeId() {
|
|
708
|
+
return this.node && this.node.id
|
|
709
|
+
},
|
|
710
|
+
get ref() {
|
|
711
|
+
return this.props.ref || null
|
|
712
|
+
},
|
|
713
|
+
get parent() {
|
|
714
|
+
return this.node && this.node.parent
|
|
715
|
+
},
|
|
716
|
+
get children() {
|
|
717
|
+
return this.component[symbols.getChildren]().filter((child) => {
|
|
718
|
+
return child.parent === (this[symbols.isSlot] ? this.node.children[0] : this.node)
|
|
719
|
+
})
|
|
720
|
+
},
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Returns a new Blits Element
|
|
725
|
+
*
|
|
726
|
+
* @param {Object} config - The configuration object for the element
|
|
727
|
+
* @param {import('../../component.js').BlitsComponent} component - The component to which the element belongs
|
|
728
|
+
* @returns {import('../../component.js').BlitsElement} - The new Blits Element
|
|
729
|
+
*/
|
|
730
|
+
export default (config, component) => {
|
|
731
|
+
if (textDefaults === null) {
|
|
732
|
+
textDefaults = {
|
|
733
|
+
fontSize: 32,
|
|
734
|
+
fontFamily: Settings.get('defaultFont', 'sans-serif'),
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return Object.assign(Object.create(Element), {
|
|
738
|
+
props: Object.assign(Object.create(propsTransformer), { props: {} }),
|
|
739
|
+
scheduledTransitions: {},
|
|
740
|
+
config,
|
|
741
|
+
component,
|
|
742
|
+
counter: counter++,
|
|
743
|
+
})
|
|
744
|
+
}
|