Skip to content

Commit

Permalink
Merge pull request #254 from lightning-js/dev
Browse files Browse the repository at this point in the history
Release 1.15.0
  • Loading branch information
michielvandergeest authored Jan 14, 2025
2 parents eb5baea + 97e6c59 commit 0cec2aa
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 15 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## v1.15.0

_14 jan 2025_

- Added `placement`-attribute for easily aligning Elements to predefined locations (i.e. `center`, `right`, `middle`, `bottom`)
- Removed automatic injection of `index` variable into forloop-scope to prevent unexpected naming collisions


## v1.14.1

_8 jan 2025_
Expand Down
32 changes: 32 additions & 0 deletions docs/essentials/element_attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,38 @@ Besides using values in pixels (i.e. `w="100" h="300"`), you can also specify _p
The percentage value specified for `w` and `x` will be calculated as the percentage of the _width_ (`w`) of the parent element.
And the percentage specified for `h` and `y` will use the _height_ (`h`) of the parent element as the base of the percentage calculation.

### Predefined placement options

In addition to the absolute positions that Blits and Lightning use, there are a few predefined placement options available. By adding the `placement`-attribute to an Element we can easily align it to the _center_, _left_, _right_, _top_, _bottom_, or even a combination like `{y: 'middle', x: 'left'}`, without having to calculate the positions yourself.


On the `x`-axis the following _placement_ options can be used: `left` (default), `center` and `right`. On the `y`-axis the _placement_ attribute accepts `top` (default), `middle` and `bottom`.

The _placement_ attribute also accepts and object with an `x` and a `y` key, to specify a placement for both axes.

The placement of an Element is calculated based on the dimensions of it's direct parent. This means that the containing Element _must_ have it's own dimensions (i.e. a `w` and a `h` attribute).

```xml
<Element w="300" h="300">
<!-- x placement -->
<Element w="40" h="40" placement="left" color="#818cf8" />
<Element w="40" h="40" placement="center" color="#2563eb" />
<Element w="40" h="40" placement="right" color="#1e40af" />

<!-- y placement -->
<Element w="40" h="40" x="54" placement="top" color="#f472b6" />
<Element w="40" h="40" x="54" placement="middle" color="#db2777" />
<Element w="40" h="40" x="54" placement="bottom" color="#be185d" />

<!-- x/y placement -->
<Element w="40" h="40" placement="{x: 'center', y: 'bottom'}" color="#a3e635" />
<Element w="40" h="40" placement="{x: 'right', y: 'middle'}" color="#65a30d" />
<Element w="40" h="40" placement="{x: 'center', y: 'middle'}" color="#4d7c0f" />
</Element>
```

> note: the `x` or `y` attributes on the Element are ignored for those axes defined in the `placement`-attribute
## Colors

By default, Elements have a transparent background color. The `color` attribute can be used to give an Element a color.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lightningjs/blits",
"version": "1.14.1",
"version": "1.15.0",
"description": "Blits: The Lightning 3 App Development Framework",
"bin": "bin/index.js",
"exports": {
Expand Down
31 changes: 31 additions & 0 deletions src/engines/L3/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,37 @@ const propsTransformer = {
set content(v) {
this.props['text'] = '' + v
},
set placement(v) {
let x, y
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
if ('x' in v === true) {
x = v.x
}
if ('y' in v === true) {
y = v.y
}
} else {
v === 'center' || v === 'right' ? (x = v) : (y = v)
}

// Set X position
if (x === 'center') {
this.x = '50%'
this.props['mountX'] = 0.5
} else if (x === 'right') {
this.x = '100%'
this.props['mountX'] = 1
}

// Set Y position
if (y === 'middle') {
this.y = '50%'
this.props['mountY'] = 0.5
} else if (y === 'bottom') {
this.y = '100%'
this.props['mountY'] = 1
}
},
}

const Element = {
Expand Down
142 changes: 138 additions & 4 deletions src/engines/L3/element.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,30 +362,164 @@ test('Element - Set `fit` property should not set not required keys', (assert) =
assert.end()
})

test('Element - Set `float` property with value `center`', (assert) => {
test('Element - Set `placement` property with value `center`', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('float', 'center')
el.set('placement', 'center')

assert.equal(el.node['mountX'], 0.5, 'Node mountX parameter should set to 0.5')
assert.equal(el.props.props['mountX'], 0.5, 'Props mountX parameter should set to 0.5')
assert.equal(el.node['x'], 960, 'Node x parameter should set to half of parent node width, 960')
assert.equal(el.props.props['x'], 960, 'props x parameter should be set to 960')
assert.equal(
el.node['y'],
undefined,
'Node Y parameter should not be modified from default value'
)

assert.end()
})

test('Element - Set `float` property with value `right`', (assert) => {
test('Element - Set `placement` property with value `right`', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('float', 'right')
el.set('placement', 'right')

assert.equal(el.node['mountX'], 1, 'Node mountX parameter should set to 1')
assert.equal(el.props.props['mountX'], 1, 'Props mountX parameter should set to 1')
assert.equal(el.node['x'], 1920, 'Node x parameter should set to parent node full width, 1920')
assert.equal(el.props.props['x'], 1920, 'props x parameter should be set to 1920')
assert.equal(
el.node['y'],
undefined,
'Node Y parameter should not be modified from default value'
)

assert.end()
})

test('Element - Set `placement` property with value `middle`', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('placement', 'middle')

assert.equal(el.node['mountY'], 0.5, 'Node mountY parameter should set to 0.5')
assert.equal(el.props.props['mountY'], 0.5, 'Props mountY parameter should set to 0.5')
assert.equal(el.node['y'], 540, 'Node Y parameter should set to half of parent node height, 540')
assert.equal(el.props.props['y'], 540, 'props Y parameter should be set to 540')
assert.equal(
el.node['x'],
undefined,
'Node X parameter should not be modified from default value'
)

assert.end()
})

test('Element - Set `placement` property with value `bottom`', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('placement', 'bottom')

assert.equal(el.node['mountY'], 1, 'Node mountY parameter should set to 1')
assert.equal(el.props.props['mountY'], 1, 'Props mountY parameter should set to 1')
assert.equal(el.node['y'], 1080, 'Node Y parameter should set to parent node full height, 1080')
assert.equal(el.props.props['y'], 1080, 'props Y parameter should be set to 1080')
assert.equal(
el.node['x'],
undefined,
'Node X parameter should not be modified from default value'
)

assert.end()
})

test('Element - Set `placement` property with value `bottom` & x = 300', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('placement', 'bottom')

assert.equal(el.node['mountY'], 1, 'Node mountY parameter should set to 1')
assert.equal(el.props.props['mountY'], 1, 'Props mountY parameter should set to 1')
assert.equal(el.node['y'], 1080, 'Node Y parameter should set to parent node full height, 1080')
assert.equal(el.props.props['y'], 1080, 'props Y parameter should be set to 1080')

el.set('x', 300)

assert.equal(el.node['x'], 300, 'Node x parameter should set custom value 300')

assert.end()
})

test('Element - Set `placement` property with object value `{x:"center", y:"middle"}`', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('placement', "{x:'center', y:'middle'}")

assert.equal(el.node['mountX'], 0.5, 'Node mountX parameter should set to 0.5')
assert.equal(el.props.props['mountX'], 0.5, 'Props mountX parameter should set to 0.5')
assert.equal(el.node['x'], 960, 'Node x parameter should set to half of parent node width, 960')

assert.equal(el.node['mountY'], 0.5, 'Node mountY parameter should set to 0.5')
assert.equal(el.props.props['mountY'], 0.5, 'Props mountY parameter should set to 0.5')
assert.equal(el.node['y'], 540, 'Node Y parameter should set to half of parent node height, 540')

assert.end()
})

test('Element - Set `placement` property with object value `{x:"center", y:"bottom"}`', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('placement', "{x:'center', y:'bottom'}")

assert.equal(el.node['mountX'], 0.5, 'Node mountX parameter should set to 0.5')
assert.equal(el.props.props['mountX'], 0.5, 'Props mountX parameter should set to 0.5')
assert.equal(el.node['x'], 960, 'Node x parameter should set to half of parent node width, 960')

assert.equal(el.node['mountY'], 1, 'Node mountY parameter should set to 1')
assert.equal(el.props.props['mountY'], 1, 'Props mountY parameter should set to 1')
assert.equal(el.node['y'], 1080, 'Node Y parameter should set to parent node full height , 1080')

assert.end()
})

test('Element - Set `placement` property with object value `{x:"right", y:"middle"}`', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('placement', "{x:'right', y:'middle'}")

assert.equal(el.node['mountX'], 1, 'Node mountX parameter should set to 1')
assert.equal(el.props.props['mountX'], 1, 'Props mountX parameter should set to 1')
assert.equal(el.node['x'], 1920, 'Node x parameter should set to parent node full width, 1920')

assert.equal(el.node['mountY'], 0.5, 'Node mountY parameter should set to 0.5')
assert.equal(el.props.props['mountY'], 0.5, 'Props mountY parameter should set to 0.5')
assert.equal(el.node['y'], 540, 'Node Y parameter should set to half of parent node height, 540')

assert.end()
})

test('Element - Set `placement` property with object value `{x:"right", y:"bottom"}`', (assert) => {
assert.capture(renderer, 'createNode', () => new EventEmitter())
const el = createElement()

el.set('placement', "{x:'right', y:'bottom'}")

assert.equal(el.node['mountX'], 1, 'Node mountX parameter should set to 1')
assert.equal(el.props.props['mountX'], 1, 'Props mountX parameter should set to 1')
assert.equal(el.node['x'], 1920, 'Node x parameter should set to parent node full width, 1920')

assert.equal(el.node['mountY'], 1, 'Node mountY parameter should set to 1')
assert.equal(el.props.props['mountY'], 1, 'Props mountY parameter should set to 1')
assert.equal(el.node['y'], 1080, 'Node Y parameter should set to parent node height, 1080')

assert.end()
})
Expand Down
33 changes: 25 additions & 8 deletions src/lib/codegenerator/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ const generateForLoopCode = function (templateObject, parent) {
const result = regex.exec(forLoop)

// can be improved with a smarter regex
const [item, index = 'index'] = result[1]
const [item, index] = result[1]
.replace('(', '')
.replace(')', '')
.split(/\s*,\s*/)
Expand All @@ -306,12 +306,15 @@ const generateForLoopCode = function (templateObject, parent) {
ctx.renderCode.push(`parent = ${parent}`)
}

const indexRegex = new RegExp(`\\$${index}(?!['\\w])`)
const indexResult = indexRegex.exec(key)
if (Array.isArray(indexResult)) {
ctx.renderCode.push(
`console.warn(" Using '${index}' in the key, like key=${key}, is not recommended")`
)
// If the index variable is not defined, the key attribute would not reference it.
if (index !== undefined) {
const indexRegex = new RegExp(`\\$${index}(?!['\\w])`)
const indexResult = indexRegex.exec(key)
if (Array.isArray(indexResult)) {
ctx.renderCode.push(
`console.warn(" Using '${index}' in the key, like key=${key}, is not recommended")`
)
}
}

const forStartCounter = counter
Expand All @@ -324,7 +327,14 @@ const generateForLoopCode = function (templateObject, parent) {
let l = rawCollection.length
while(l--) {
const ${item} = rawCollection[l]
`)
// push reference of index variable
if (index !== undefined) {
ctx.renderCode.push(`
const ${index} = l
`)
}
ctx.renderCode.push(`
keys.add('' + ${interpolate(key, '') || 'l'})
}
`)
Expand All @@ -341,8 +351,15 @@ const generateForLoopCode = function (templateObject, parent) {
for(let __index = 0; __index < length; __index++) {
const scope = Object.create(component)
parent = ${parent}
scope['${index}'] = __index
scope['${item}'] = rawCollection[__index]
`)
// If the index variable is declared, include it in the scope object
if (index !== '') {
ctx.renderCode.push(`
scope['${index}'] = __index
`)
}
ctx.renderCode.push(`
scope['key'] = '' + ${forKey || '__index'}
`)
if ('ref' in templateObject && templateObject.ref.indexOf('$') === -1) {
Expand Down

0 comments on commit 0cec2aa

Please sign in to comment.