Skip to content

Commit d666c08

Browse files
Merge pull request #926 from sketch-hq/release/2025.1-dfea066b
Update with `release/2025.1-dfea066b`
2 parents ee247bc + e475240 commit d666c08

30 files changed

+640
-179
lines changed

.eslintrc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
"NSArray": "readonly",
3131
"NSMakePoint": "readonly",
3232
"CGRectMake": "readonly",
33-
"MSArtboardGroup": "readonly",
3433
"MSPage": "readonly",
3534
"BCSketchInfo": "readonly",
3635
"log": "readonly",
@@ -51,6 +50,7 @@
5150
"MSDefaultStyle": "readonly",
5251
"MSLayer": "readonly",
5352
"MSDocumentData": "readonly",
53+
"MSDetachedSymbol": "readonly",
5454
"MSSymbolMaster": "readonly",
5555
"MSSymbolInstance": "readonly",
5656
"MSOverridePoint": "readonly",
@@ -72,7 +72,6 @@
7272
"MSForeignSymbolProvider": "readonly",
7373
"MSForeignObjectCollector": "readonly",
7474
"MSStyleShadow": "readonly",
75-
"MSStyleInnerShadow": "readonly",
7675
"MSGradientStop": "readonly",
7776
"MSImmutableGradientStop": "readonly",
7877
"MSGradient": "readonly",
@@ -95,7 +94,6 @@
9594
"MSSharedStyle": "readonly",
9695
"MSStyle": "readonly",
9796
"MSDataOverride": "readonly",
98-
"MSImmutableArtboardGroup": "readonly",
9997
"MSImmutableLayerGroup": "readonly",
10098
"MSImmutableHotspotLayer": "readonly",
10199
"MSImmutableBitmapLayer": "readonly",
@@ -104,6 +102,7 @@
104102
"MSImmutableShapeGroup": "readonly",
105103
"MSStyledLayer": "readonly",
106104
"MSImmutableStyledLayer": "readonly",
105+
"MSImmutableDetachedSymbol": "readonly",
107106
"MSImmutableSymbolInstance": "readonly",
108107
"MSImmutableSymbolMaster": "readonly",
109108
"MSImmutableTextLayer": "readonly",
@@ -185,6 +184,6 @@
185184
"NSUUID": "readonly",
186185
"NSError": "readonly",
187186
"NSMapTable": "readonly",
188-
"MSTextAlignmentConverter": "readonly",
187+
"MSTextAlignmentConverter": "readonly"
189188
}
190189
}

Source/dom/Factory.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const Factory = {
22
_typeToBox: {},
3+
_typeAliases: {},
34
_nativeToBox: {},
45
_typeToNative: {},
56
registerClass(boxedClass, nativeClass) {
@@ -11,12 +12,17 @@ export const Factory = {
1112
}
1213
this._nativeToBox[String(nativeClass.class())] = boxedClass
1314
},
15+
registerAlias(boxedClass, otherBoxedClass) {
16+
this._typeToBox[boxedClass.type] = boxedClass
17+
this._typeAliases[boxedClass.type] = otherBoxedClass
18+
},
1419
create(type, props) {
1520
const _type = type && type.type ? type.type : type
1621
const BoxedClass = this._typeToBox[_type]
1722
if (BoxedClass) {
1823
return new BoxedClass(props)
1924
}
25+
2026
return undefined
2127
},
2228
createNative(type) {

Source/dom/__tests__/find.test.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
11
/* globals expect, test */
22
import { find, Rectangle } from '..'
33

4-
test('should find by type', (_context, document) => {
4+
test('should find Artboard by type', (_context, document) => {
55
// eslint-disable-next-line no-param-reassign
66
document.pages = [
77
{
8-
layers: [{ type: 'Artboard' }, { type: 'Shape' }],
8+
layers: [
9+
{ type: 'Artboard' },
10+
{ type: 'Shape' },
11+
{ type: 'Group', layers: { type: 'Text' } }
12+
]
913
},
1014
]
15+
// expect to find only one artboard
1116
expect(find('Artboard', document).map((x) => x.id)).toEqual([
1217
document.pages[0].layers[0].id,
1318
])
1419
})
1520

21+
test('should find Group by type', (_context, document) => {
22+
document.pages = [
23+
{
24+
layers: [
25+
{ type: 'Artboard', layers: [
26+
{ type: 'Group', layers: { type: 'Text' } }
27+
]},
28+
{ type: 'Shape' },
29+
{ type: 'Group', layers: { type: 'Text' } }
30+
]
31+
},
32+
]
33+
// expect to find only multiple groups including nested
34+
expect(find('Group', document).map((x) => x.id)).toEqual([
35+
document.pages[0].layers[0].layers[0].id, // nested group on artboard
36+
document.pages[0].layers[2].id, // regular group on page
37+
])
38+
})
39+
1640
test('should find by name', (_context, document) => {
1741
// eslint-disable-next-line no-param-reassign
1842
document.pages = [

Source/dom/__tests__/globalAssets.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test('should reset global colors', () => {
2222
})
2323

2424
test('should append global colors', () => {
25-
globalAssets.colors = ['000000']
25+
globalAssets.colors = ['#000000']
2626
globalAssets.colors.push('#FFFFFF')
2727
expect(globalAssets.colors.length).toEqual(2)
2828
expect(globalAssets.colors[1].color).toEqual('#ffffffff')

Source/dom/__tests__/import.test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ test('should create Group from an SVG', () => {
1515
expect(group.layers[0].frame.height).toEqual(100)
1616
})
1717

18-
test('should create group from a PDF', (_context, document) => {
18+
test('should create shape from a PDF', (_context, document) => {
1919
const layer = new Shape({
2020
parent: document.selectedPage,
2121
frame: new Rectangle(0, 0, 200, 100),
@@ -31,10 +31,10 @@ test('should create group from a PDF', (_context, document) => {
3131
formats: 'pdf',
3232
output: null,
3333
})
34-
const page = createLayerFromData(buffer, 'pdf')
35-
expect(page.type).toEqual('Group')
36-
expect(page.frame.width).toEqual(200)
37-
expect(page.frame.height).toEqual(100)
34+
const imported = createLayerFromData(buffer, 'pdf')
35+
expect(imported.type).toEqual('ShapePath')
36+
expect(imported.frame.width).toEqual(200)
37+
expect(imported.frame.height).toEqual(100)
3838
})
3939

4040
test('should create Image from a bitmap', (_context, document) => {

Source/dom/assets/__tests__/ColorAsset.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ test('should create color asset from hex', (_context, document) => {
66

77
const asset = document.colors[0]
88
expect(asset.color).toBe('#ffffffff')
9-
expect(asset.name).toBe(null)
9+
expect(asset.name).toBe('')
1010
})
1111

1212
test('should create color asset from MSColor', (_context, document) => {

Source/dom/assets/__tests__/GradientAsset.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ test('should create gradient asset from dictionary', (_context, document) => {
66

77
const asset = document.gradients[0]
88
expect(asset.gradient.type).toBe('Gradient')
9-
expect(asset.name).toBe(null)
9+
expect(asset.name).toBe('')
1010
})
1111

1212
test('should create gradient asset from MSGradientAsset', (_context, document) => {

Source/dom/find.js

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ const attributesMap = {
3333
operator = '='
3434
}
3535
const predicate = []
36-
const nativeClasses = Factory._typeToNative[value]
36+
const nativeClasses =
37+
Factory._typeToNative[
38+
value in Factory._typeAliases ? Factory._typeAliases[value].type : value
39+
]
3740
if (!nativeClasses) {
3841
throw new Error(`Unknown layer type ${value}`)
3942
}
@@ -139,6 +142,14 @@ export function find(predicate, root) {
139142
},
140143
}
141144

145+
const FilterStragegy = Object.freeze({
146+
None: 'none',
147+
Artboard: 'artboard',
148+
Group: 'group',
149+
})
150+
151+
let filterStragegy = FilterStragegy.None
152+
142153
predicateParts.forEach((part) => {
143154
const matched = Object.keys(matchExpr).some((k) => {
144155
const match = matchExpr[k].exec(part)
@@ -152,10 +163,18 @@ export function find(predicate, root) {
152163
}
153164

154165
if (k === 'TYPE') {
155-
if (match[1] === '*') {
156-
nativePredicateParts.push('TRUEPREDICATE')
157-
} else {
158-
attributesMap.type('=', match[1], mutations)
166+
switch (match[1]) {
167+
case '*':
168+
nativePredicateParts.push('TRUEPREDICATE')
169+
break
170+
// Artboards no longer exist as a dedicated type. All artboards are now layer
171+
// groups with frame behaviour.
172+
case 'Artboard':
173+
case 'Group':
174+
filterStragegy = FilterStragegy[match[1]] // set filter strategy and fallthrough
175+
default:
176+
attributesMap.type('=', match[1], mutations)
177+
break
159178
}
160179
}
161180

@@ -200,7 +219,47 @@ export function find(predicate, root) {
200219
}, NSMutableArray.new())
201220
: root.sketchObject.childrenIncludingSelf(false)
202221

203-
const matches = children.filteredArrayUsingPredicate(nativePredicate)
222+
// Different filter strategies are used for backwards compatibility with plugins and
223+
// scripts that work on the concept of artboards.
224+
// Artboards no longer exist. Instead everything is a group with different behaviours:
225+
// - Regular group: implicit size based on its contents
226+
// - Frames: explicit size, independent of its contents, frames can exist on the canvas
227+
// or inside groups. Its contents resize based on the frame size and resizing
228+
// constraints.
229+
// - Graphics: explicit size, independent of its contents, graphics can exist on the
230+
// canvas or inside groups. Its contents scale based on the group size.
231+
var cb = (s) => {
232+
// By default, return all children
233+
if (s == FilterStragegy.None) {
234+
return () => true
235+
}
236+
237+
// The closest to artboards are canvas frames. These may be a frame or a graphic but
238+
// cannot be a regular group.
239+
const canvasFrames =
240+
root.type == Types.Document
241+
? root.sketchObject.pages().reduce((prev, page) => {
242+
prev.addObjectsFromArray(page.canvasFrames())
243+
return prev
244+
}, NSMutableArray.new())
245+
: root.type == Types.Page
246+
? root.sketchObject.canvasFrames()
247+
: []
248+
249+
switch (s) {
250+
// Only include anything that is a canvas frame
251+
case FilterStragegy.Artboard:
252+
return (v) => canvasFrames.includes(v)
253+
// Only include anything that is not a canvas frame, i.e. a regular group
254+
case FilterStragegy.Group:
255+
return (v) => !canvasFrames.includes(v)
256+
// Should never happen, caught earlier
257+
default:
258+
return () => true
259+
}
260+
}
204261

205-
return toArray(matches).map((x) => wrapObject(x))
262+
return toArray(children.filteredArrayUsingPredicate(nativePredicate))
263+
.filter(cb(filterStragegy))
264+
.map((x) => wrapObject(x))
206265
}

Source/dom/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const { SmartLayout } = require('./models/SmartLayout')
1818
const { Style } = require('./style/Style')
1919

2020
const { Layer } = require('./layers/Layer')
21-
const { Group } = require('./layers/Group')
21+
const { Pin, FlexSizing } = require('./layers/Layer')
22+
const { Group, GroupBehavior } = require('./layers/Group')
2223
const { Text } = require('./layers/Text')
2324
const { Image } = require('./layers/Image')
2425
const { Shape } = require('./layers/Shape')
@@ -60,7 +61,10 @@ const DOM = {
6061
Rectangle,
6162
Style,
6263
Layer,
64+
FlexSizing,
65+
Pin,
6366
Group,
67+
GroupBehavior,
6468
Text,
6569
Image,
6670
Shape,

Source/dom/layers/Artboard.js

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DefinedPropertiesKey } from '../WrappedObject'
2-
import { Group } from './Group'
2+
import { Group, GroupBehavior } from './Group'
33
import { Rectangle } from '../models/Rectangle'
44
import { Types } from '../enums'
55
import { Factory } from '../Factory'
@@ -19,11 +19,18 @@ export class Artboard extends Group {
1919
constructor(artboard = {}) {
2020
if (!artboard.sketchObject) {
2121
// eslint-disable-next-line no-param-reassign
22-
artboard.sketchObject = Factory.createNative(Artboard)
22+
artboard.sketchObject = Factory.createNative(Group)
2323
.alloc()
24-
.initWithFrame(new Rectangle(0, 0, 100, 100).asCGRect())
24+
.initWithFrame_behavior(
25+
new Rectangle(0, 0, 100, 100).asCGRect(),
26+
GroupBehavior.Frame
27+
)
2528
}
2629
super(artboard)
30+
// Mimics behaviour implemented at the controller level where they call
31+
// `MSLayer.adjustAfterInsert()` which will apply the default styling.
32+
this.background.enabled = true
33+
// eslint-enable no-param-reassign
2734
}
2835

2936
/**
@@ -43,11 +50,9 @@ export class Artboard extends Group {
4350

4451
Artboard.type = Types.Artboard
4552
Artboard[DefinedPropertiesKey] = { ...Group[DefinedPropertiesKey] }
46-
Factory.registerClass(Artboard, MSArtboardGroup)
47-
Factory.registerClass(Artboard, MSImmutableArtboardGroup)
53+
Factory.registerAlias(Artboard, Group)
4854

4955
delete Artboard[DefinedPropertiesKey].flow
50-
delete Artboard[DefinedPropertiesKey].style
5156
delete Artboard[DefinedPropertiesKey].locked
5257
delete Artboard[DefinedPropertiesKey].hidden
5358
delete Artboard[DefinedPropertiesKey].transform
@@ -68,13 +73,30 @@ Artboard.define('flowStartPoint', {
6873
Artboard.defineObject('background', {
6974
enabled: {
7075
get() {
71-
return Boolean(Number(this._object.hasBackgroundColor()))
76+
return (
77+
this._object.style &&
78+
this._object.style().fills &&
79+
this._object.style().fills().length > 0
80+
)
7281
},
7382
set(enabled) {
7483
if (this._parent.isImmutable()) {
7584
return
7685
}
77-
this._object.setHasBackgroundColor(enabled)
86+
const style = this._object.style ? this._object.style() : undefined
87+
if (!style) {
88+
return
89+
}
90+
if (enabled) {
91+
const numFills = style.fills ? style.fills().length : 0
92+
if (numFills === 0) {
93+
// Create a default fill if enabling and no fills exist
94+
style.addStylePartOfType(0) // 0 is for fills
95+
}
96+
} else {
97+
// Remove all fills if disabling
98+
style.removeAllStyleFills()
99+
}
78100
},
79101
},
80102
includedInExport: {
@@ -90,13 +112,26 @@ Artboard.defineObject('background', {
90112
},
91113
color: {
92114
get() {
93-
return colorToString(this._object.backgroundColor())
115+
const firstFill = this._object.style
116+
? this._object.style().firstEnabledFill()
117+
: undefined
118+
return firstFill ? colorToString(firstFill.color()) : '#00000000'
94119
},
95120
set(color) {
96121
if (this._parent.isImmutable()) {
97122
return
98123
}
99-
this._object.setBackgroundColor(Color.from(color).toMSColor())
124+
if (!this._object.style) {
125+
return
126+
}
127+
if (
128+
!this._object.style().fills ||
129+
this._object.style().fills().length === 0
130+
) {
131+
this._object.style().addStylePartOfType(0) // Add a fill if none exists
132+
}
133+
const firstFill = this._object.style().firstEnabledFill()
134+
firstFill.color = Color.from(color).toMSColor()
100135
},
101136
},
102137
})

0 commit comments

Comments
 (0)