@stylexjs/shared 0.1.0-beta.1

Sign up to get free protection for your applications and to get access to all the features.
package/.babelrc ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "assumptions": {
3
+ "iterableIsArray": true
4
+ },
5
+ "presets": [
6
+ ["@babel/preset-env", {
7
+ "exclude": [
8
+ "@babel/plugin-transform-typeof-symbol"
9
+ ],
10
+ "targets": "defaults"
11
+ }],
12
+ "@babel/preset-flow",
13
+ "@babel/preset-react"
14
+ ]
15
+ }
16
+
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ *
9
+ */
10
+
11
+ import styleXCreate from '../src/stylex-create';
12
+
13
+ describe('stylex-create-test', () => {
14
+ test('color:red', () => {
15
+ expect(
16
+ styleXCreate({
17
+ default: {
18
+ backgroundColor: 'red',
19
+ color: 'blue',
20
+ },
21
+ })
22
+ ).toMatchInlineSnapshot(`
23
+ [
24
+ {
25
+ "default": {
26
+ "backgroundColor": "xrkmrrc",
27
+ "color": "xju2f9n",
28
+ },
29
+ },
30
+ {
31
+ "xju2f9n": {
32
+ "ltr": ".xju2f9n{color:blue}",
33
+ "priority": 1,
34
+ "rtl": null,
35
+ },
36
+ "xrkmrrc": {
37
+ "ltr": ".xrkmrrc{background-color:red}",
38
+ "priority": 1,
39
+ "rtl": null,
40
+ },
41
+ },
42
+ ]
43
+ `);
44
+ });
45
+
46
+ test('padding shorthand', () => {
47
+ expect(
48
+ styleXCreate({
49
+ short: {
50
+ padding: 'calc((100% - 50px) * 0.5) var(--rightpadding, 20px)',
51
+ paddingTop: 0,
52
+ },
53
+ })
54
+ ).toMatchInlineSnapshot(`
55
+ [
56
+ {
57
+ "short": {
58
+ "paddingBottom": "x18xuxqe",
59
+ "paddingEnd": "xcrpjku",
60
+ "paddingStart": "xyv1419",
61
+ "paddingTop": "xexx8yu",
62
+ },
63
+ },
64
+ {
65
+ "x18xuxqe": {
66
+ "ltr": ".x18xuxqe{padding-bottom:calc((100% - 50px) * .5)}",
67
+ "priority": 1,
68
+ "rtl": null,
69
+ },
70
+ "xcrpjku": {
71
+ "ltr": ".xcrpjku{padding-right:var(--rightpadding,20px)}",
72
+ "priority": 1,
73
+ "rtl": ".xcrpjku{padding-left:var(--rightpadding,20px)}",
74
+ },
75
+ "xexx8yu": {
76
+ "ltr": ".xexx8yu{padding-top:0}",
77
+ "priority": 1,
78
+ "rtl": null,
79
+ },
80
+ "xyv1419": {
81
+ "ltr": ".xyv1419{padding-left:var(--rightpadding,20px)}",
82
+ "priority": 1,
83
+ "rtl": ".xyv1419{padding-right:var(--rightpadding,20px)}",
84
+ },
85
+ },
86
+ ]
87
+ `);
88
+ });
89
+
90
+ test('transforms style object with custom property', () => {
91
+ expect(
92
+ styleXCreate({
93
+ default: {
94
+ '--background-color': 'red',
95
+ },
96
+ })
97
+ ).toMatchInlineSnapshot(`
98
+ [
99
+ {
100
+ "default": {
101
+ "--background-color": "xgau0yw",
102
+ },
103
+ },
104
+ {
105
+ "xgau0yw": {
106
+ "ltr": ".xgau0yw{--background-color:red}",
107
+ "priority": 1,
108
+ "rtl": null,
109
+ },
110
+ },
111
+ ]
112
+ `);
113
+ });
114
+
115
+ test('transforms style object with custom propety as value', () => {
116
+ expect(
117
+ styleXCreate({
118
+ default: {
119
+ '--final-color': 'var(--background-color)',
120
+ },
121
+ })
122
+ ).toMatchInlineSnapshot(`
123
+ [
124
+ {
125
+ "default": {
126
+ "--final-color": "x13tgbkp",
127
+ },
128
+ },
129
+ {
130
+ "x13tgbkp": {
131
+ "ltr": ".x13tgbkp{--final-color:var(--background-color)}",
132
+ "priority": 1,
133
+ "rtl": null,
134
+ },
135
+ },
136
+ ]
137
+ `);
138
+ });
139
+
140
+ test('transforms multiple namespaces', () => {
141
+ expect(
142
+ styleXCreate({
143
+ default: {
144
+ backgroundColor: 'red',
145
+ },
146
+
147
+ default2: {
148
+ color: 'blue',
149
+ },
150
+ })
151
+ ).toMatchInlineSnapshot(`
152
+ [
153
+ {
154
+ "default": {
155
+ "backgroundColor": "xrkmrrc",
156
+ },
157
+ "default2": {
158
+ "color": "xju2f9n",
159
+ },
160
+ },
161
+ {
162
+ "xju2f9n": {
163
+ "ltr": ".xju2f9n{color:blue}",
164
+ "priority": 1,
165
+ "rtl": null,
166
+ },
167
+ "xrkmrrc": {
168
+ "ltr": ".xrkmrrc{background-color:red}",
169
+ "priority": 1,
170
+ "rtl": null,
171
+ },
172
+ },
173
+ ]
174
+ `);
175
+ });
176
+
177
+ test('does not transform attr() value', () => {
178
+ expect(
179
+ styleXCreate({
180
+ default: {
181
+ content: 'attr(some-attribute)',
182
+ },
183
+ })
184
+ ).toMatchInlineSnapshot(`
185
+ [
186
+ {
187
+ "default": {
188
+ "content": "xd71okc",
189
+ },
190
+ },
191
+ {
192
+ "xd71okc": {
193
+ "ltr": ".xd71okc{content:attr(some-attribute)}",
194
+ "priority": 1,
195
+ "rtl": null,
196
+ },
197
+ },
198
+ ]
199
+ `);
200
+ });
201
+
202
+ test('transforms nested pseudo-class to CSS', () => {
203
+ expect(
204
+ styleXCreate({
205
+ default: {
206
+ ':hover': {
207
+ backgroundColor: 'red',
208
+ color: 'blue',
209
+ },
210
+ },
211
+ })
212
+ ).toMatchInlineSnapshot(`
213
+ [
214
+ {
215
+ "default": {
216
+ ":hover_backgroundColor": "x1gykpug",
217
+ ":hover_color": "x17z2mba",
218
+ },
219
+ },
220
+ {
221
+ "x17z2mba": {
222
+ "ltr": ".x17z2mba:hover{color:blue}",
223
+ "priority": 8,
224
+ "rtl": null,
225
+ },
226
+ "x1gykpug": {
227
+ "ltr": ".x1gykpug:hover{background-color:red}",
228
+ "priority": 8,
229
+ "rtl": null,
230
+ },
231
+ },
232
+ ]
233
+ `);
234
+ });
235
+
236
+ // This API will not launch as an array, but internally we can continue to use arrays
237
+ test('transforms array values as fallbacks', () => {
238
+ expect(
239
+ styleXCreate({
240
+ default: {
241
+ position: ['sticky', 'fixed'],
242
+ },
243
+ })
244
+ ).toMatchInlineSnapshot(`
245
+ [
246
+ {
247
+ "default": {
248
+ "position": "x1ruww2u",
249
+ },
250
+ },
251
+ {
252
+ "x1ruww2u": {
253
+ "ltr": ".x1ruww2u{position:sticky;position:fixed}",
254
+ "priority": 1,
255
+ "rtl": null,
256
+ },
257
+ },
258
+ ]
259
+ `);
260
+ });
261
+
262
+ test('tranforms valid shorthands', () => {
263
+ expect(
264
+ styleXCreate({
265
+ default: {
266
+ overflow: 'hidden',
267
+ borderStyle: 'dashed',
268
+ borderWidth: 1,
269
+ },
270
+ })
271
+ ).toMatchInlineSnapshot(`
272
+ [
273
+ {
274
+ "default": {
275
+ "borderBottomStyle": "xpvcztv",
276
+ "borderBottomWidth": "xso031l",
277
+ "borderEndStyle": "x157eodl",
278
+ "borderEndWidth": "xm81vs4",
279
+ "borderStartStyle": "x1q04ism",
280
+ "borderStartWidth": "xy80clv",
281
+ "borderTopStyle": "xlya59e",
282
+ "borderTopWidth": "x178xt8z",
283
+ "overflowX": "x6ikm8r",
284
+ "overflowY": "x10wlt62",
285
+ },
286
+ },
287
+ {
288
+ "x10wlt62": {
289
+ "ltr": ".x10wlt62{overflow-y:hidden}",
290
+ "priority": 1,
291
+ "rtl": null,
292
+ },
293
+ "x157eodl": {
294
+ "ltr": ".x157eodl{border-right-style:dashed}",
295
+ "priority": 1,
296
+ "rtl": ".x157eodl{border-left-style:dashed}",
297
+ },
298
+ "x178xt8z": {
299
+ "ltr": ".x178xt8z{border-top-width:1px}",
300
+ "priority": 1,
301
+ "rtl": null,
302
+ },
303
+ "x1q04ism": {
304
+ "ltr": ".x1q04ism{border-left-style:dashed}",
305
+ "priority": 1,
306
+ "rtl": ".x1q04ism{border-right-style:dashed}",
307
+ },
308
+ "x6ikm8r": {
309
+ "ltr": ".x6ikm8r{overflow-x:hidden}",
310
+ "priority": 1,
311
+ "rtl": null,
312
+ },
313
+ "xlya59e": {
314
+ "ltr": ".xlya59e{border-top-style:dashed}",
315
+ "priority": 1,
316
+ "rtl": null,
317
+ },
318
+ "xm81vs4": {
319
+ "ltr": ".xm81vs4{border-right-width:1px}",
320
+ "priority": 1,
321
+ "rtl": ".xm81vs4{border-left-width:1px}",
322
+ },
323
+ "xpvcztv": {
324
+ "ltr": ".xpvcztv{border-bottom-style:dashed}",
325
+ "priority": 1,
326
+ "rtl": null,
327
+ },
328
+ "xso031l": {
329
+ "ltr": ".xso031l{border-bottom-width:1px}",
330
+ "priority": 1,
331
+ "rtl": null,
332
+ },
333
+ "xy80clv": {
334
+ "ltr": ".xy80clv{border-left-width:1px}",
335
+ "priority": 1,
336
+ "rtl": ".xy80clv{border-right-width:1px}",
337
+ },
338
+ },
339
+ ]
340
+ `);
341
+ });
342
+
343
+ test('transforms media queries', () => {
344
+ expect(
345
+ styleXCreate({
346
+ default: {
347
+ backgroundColor: 'red',
348
+ '@media (min-width: 1000px)': {
349
+ backgroundColor: 'blue',
350
+ },
351
+
352
+ '@media (min-width: 2000px)': {
353
+ backgroundColor: 'purple',
354
+ },
355
+ },
356
+ })
357
+ ).toMatchInlineSnapshot(`
358
+ [
359
+ {
360
+ "default": {
361
+ "@media (min-width: 1000px)_backgroundColor": "xc445zv",
362
+ "@media (min-width: 2000px)_backgroundColor": "x1ssfqz5",
363
+ "backgroundColor": "xrkmrrc",
364
+ },
365
+ },
366
+ {
367
+ "x1ssfqz5": {
368
+ "ltr": "@media (min-width: 2000px){.x1ssfqz5.x1ssfqz5{background-color:purple}}",
369
+ "priority": 2,
370
+ "rtl": null,
371
+ },
372
+ "xc445zv": {
373
+ "ltr": "@media (min-width: 1000px){.xc445zv.xc445zv{background-color:blue}}",
374
+ "priority": 2,
375
+ "rtl": null,
376
+ },
377
+ "xrkmrrc": {
378
+ "ltr": ".xrkmrrc{background-color:red}",
379
+ "priority": 1,
380
+ "rtl": null,
381
+ },
382
+ },
383
+ ]
384
+ `);
385
+ });
386
+ });
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ *
9
+ */
10
+
11
+ import styleXKeyframes from '../src/stylex-keyframes';
12
+
13
+ describe('stylex-keyframes test', () => {
14
+ test('converts keyframes to CSS', () => {
15
+ expect(
16
+ styleXKeyframes({
17
+ from: {
18
+ backgroundColor: 'red',
19
+ },
20
+
21
+ to: {
22
+ backgroundColor: 'blue',
23
+ },
24
+ })
25
+ ).toMatchInlineSnapshot(`
26
+ [
27
+ "xbopttm-B",
28
+ {
29
+ "ltr": "@keyframes xbopttm-B{from{background-color:red;}to{background-color:blue;}}",
30
+ "priority": 1,
31
+ "rtl": null,
32
+ },
33
+ ]
34
+ `);
35
+ });
36
+
37
+ test('generates RTL-specific keyframes', () => {
38
+ expect(
39
+ styleXKeyframes({
40
+ from: {
41
+ start: 0,
42
+ },
43
+
44
+ to: {
45
+ start: 500,
46
+ },
47
+ })
48
+ ).toMatchInlineSnapshot(`
49
+ [
50
+ "x1lvx8r0-B",
51
+ {
52
+ "ltr": "@keyframes x1lvx8r0-B{from{left:0;}to{left:500px;}}",
53
+ "priority": 1,
54
+ "rtl": "@keyframes x1lvx8r0-B{from{right:0;}to{right:500px;}}",
55
+ },
56
+ ]
57
+ `);
58
+ });
59
+ });
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = convertToClassName;
7
+ var _hash = _interopRequireDefault(require("./hash"));
8
+ var _dashify = _interopRequireDefault(require("./utils/dashify"));
9
+ var _transformValue = _interopRequireDefault(require("./transform-value"));
10
+ var _generateCssRule = _interopRequireDefault(require("./generate-css-rule"));
11
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
+ /**
13
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
14
+ *
15
+ * This source code is licensed under the MIT license found in the
16
+ * LICENSE file in the root directory of this source tree.
17
+ *
18
+ *
19
+ */
20
+
21
+ // This function takes a single style rule and transforms it into a CSS rule.
22
+ // [color: 'red'] => ['color', 'classname-for-color-red', CSSRULE{ltr, rtl, priority}]
23
+ //
24
+ // It converts the camelCased style key to a dash-separated key.
25
+ // Handles RTL-flipping
26
+ // Hashes to get a className
27
+ // Returns the final key, className a CSS Rule
28
+ function convertToClassName(objEntry, pseudo) {
29
+ let {
30
+ stylexSheetName = '<>',
31
+ classNamePrefix = 'x'
32
+ } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
33
+ const [key, rawValue] = objEntry;
34
+ const dashedKey = (0, _dashify.default)(key);
35
+ const value = Array.isArray(rawValue) ? rawValue.map(eachValue => (0, _transformValue.default)(key, eachValue)) : (0, _transformValue.default)(key, rawValue);
36
+ const stringToHash = Array.isArray(value) ? dashedKey + value.join(', ') + (pseudo ?? 'null') : dashedKey + value + (pseudo ?? 'null');
37
+ const className = classNamePrefix + (0, _hash.default)(stylexSheetName + stringToHash);
38
+ const cssRules = (0, _generateCssRule.default)(className, dashedKey, value, pseudo);
39
+ return [key, className, cssRules];
40
+ }
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = flatMapExpandedShorthands;
7
+ var _postcssValueParser = _interopRequireDefault(require("postcss-value-parser"));
8
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
+ /**
10
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
11
+ *
12
+ * This source code is licensed under the MIT license found in the
13
+ * LICENSE file in the root directory of this source tree.
14
+ *
15
+ *
16
+ */
17
+
18
+ function printNode(node) {
19
+ switch (node.type) {
20
+ case 'word':
21
+ case 'string':
22
+ return `${node.value}`;
23
+ case 'function':
24
+ return `${node.value}(${node.nodes.map(printNode).join('')})`;
25
+ default:
26
+ return node.value;
27
+ }
28
+ }
29
+
30
+ // Using split(' ') Isn't enough bcause of values like calc.
31
+ function splitValue(str) {
32
+ if (Array.isArray(str)) {
33
+ return str;
34
+ }
35
+ const parsed = (0, _postcssValueParser.default)(str.trim());
36
+ const nodes = parsed.nodes.filter(node => node.type !== 'space' && node.type !== 'div').map(printNode);
37
+ if (nodes.length > 1 && nodes[nodes.length - 1].toLowerCase() === '!important') {
38
+ return nodes.slice(0, nodes.length - 1).map(node => node + ' !important');
39
+ }
40
+ return nodes;
41
+ }
42
+
43
+ // TODO: to be added later.
44
+ // const aliases = {
45
+ // marginInlineStart: (rawValue) => [['marginStart', rawValue]],
46
+ // marginInlineEnd: (rawValue) => [['marginEnd', rawValue]],
47
+ // marginInline: (rawValue) => [
48
+ // ['marginStart', rawValue],
49
+ // ['marginEnd', rawValue],
50
+ // ],
51
+ // paddingInlineStart: (rawValue) => [['paddingStart', rawValue]],
52
+ // paddingInlineEnd: (rawValue) => [['paddingEnd', rawValue]],
53
+ // paddingInline: (rawValue) => [
54
+ // ['paddingStart', rawValue],
55
+ // ['paddingEnd', rawValue],
56
+ // ],
57
+ // // 'borderInlineStart': (rawValue) => [['borderStart', rawValue]],
58
+ // // 'borderInlineEnd': (rawValue) => [['borderEnd', rawValue]],
59
+ // // // This will need to change.
60
+ // // 'borderInline': (rawValue) => [
61
+ // // ['borderStart', rawValue],
62
+ // // ['borderEnd', rawValue],
63
+ // // ],
64
+ // };
65
+
66
+ /**
67
+ * Shorthand properties:
68
+ * - [x] all - Should be banned
69
+ * - [ ] animation
70
+ * - [ ] background
71
+ * - [-] border
72
+ * - [x] border-block-end
73
+ * - [x] border-block-start
74
+ * - [ ] border-bottom
75
+ * - [x] border-color
76
+ * - [x] border-image
77
+ * - [x] border-inline-end
78
+ * - [x] border-inline-start
79
+ * - [ ] border-left
80
+ * - [x] border-radius
81
+ * - [ ] border-right
82
+ * - [x] border-style
83
+ * - [ ] border-top
84
+ * - [x] border-width
85
+ * - [ ] column-rule
86
+ * - [ ] columns
87
+ * - [ ] flex
88
+ * - [ ] flex-flow
89
+ * - [ ] font
90
+ * - [ ] gap
91
+ * - [ ] grid
92
+ * - [ ] grid-area
93
+ * - [ ] grid-column
94
+ * - [ ] grid-row
95
+ * - [ ] grid-template
96
+ * - [ ] list-style
97
+ * - [x] margin
98
+ * - [ ] mask
99
+ * - [ ] offset
100
+ * - [ ] outline
101
+ * - [x] overflow
102
+ * - [x] padding
103
+ * - [ ] place-content
104
+ * - [ ] place-items
105
+ * - [ ] place-self
106
+ * - [ ] scroll-margin
107
+ * - [ ] scroll-padding
108
+ * - [ ] text-decoration
109
+ * - [ ] text-emphasis
110
+ * - [ ] transition
111
+ */
112
+
113
+ const expansions = {
114
+ // ...aliases,
115
+ border: rawValue => {
116
+ return [['borderTop', rawValue], ['borderEnd', rawValue], ['borderBottom', rawValue], ['borderStart', rawValue]];
117
+ },
118
+ /*
119
+ // Add this later, as this will be a breaking change
120
+ border: (rawValue: string) => {
121
+ if (typeof rawValue === 'number') {
122
+ return expansions.borderWidth(rawValue);
123
+ }
124
+ const [width, style, color] = splitValue(rawValue);
125
+ return [
126
+ ...expansions.borderWidth(width),
127
+ ...expansions.borderStyle(style),
128
+ ...expansions.borderColor(color),
129
+ ];
130
+ }
131
+ */
132
+ borderColor: rawValue => {
133
+ const [top, right = top, bottom = top, left = right] = splitValue(rawValue);
134
+ return [['borderTopColor', top], ['borderEndColor', right], ['borderBottomColor', bottom], ['borderStartColor', left]];
135
+ },
136
+ borderHorizontal: rawValue => {
137
+ return [['borderStart', rawValue], ['borderEnd', rawValue]];
138
+ },
139
+ borderStyle: rawValue => {
140
+ const [top, right = top, bottom = top, left = right] = splitValue(rawValue);
141
+ return [['borderTopStyle', top], ['borderEndStyle', right], ['borderBottomStyle', bottom], ['borderStartStyle', left]];
142
+ },
143
+ borderVertical: rawValue => {
144
+ return [['borderTop', rawValue], ['borderBottom', rawValue]];
145
+ },
146
+ borderWidth: rawValue => {
147
+ const [top, right = top, bottom = top, left = right] = typeof rawValue === 'number' ? [rawValue] : splitValue(rawValue);
148
+ return [['borderTopWidth', top], ['borderEndWidth', right], ['borderBottomWidth', bottom], ['borderStartWidth', left]];
149
+ },
150
+ borderRadius: rawValue => {
151
+ const [top, right = top, bottom = top, left = right] = typeof rawValue === 'string' ? splitValue(rawValue) : typeof rawValue === 'number' ? [rawValue] : rawValue; // remove
152
+
153
+ return [['borderTopStartRadius', top], ['borderTopEndRadius', right], ['borderBottomEndRadius', bottom], ['borderBottomStartRadius', left]];
154
+ },
155
+ margin: rawValue => {
156
+ const [top, right = top, bottom = top, left = right] = typeof rawValue === 'number' ? [rawValue] : splitValue(rawValue);
157
+ return [['marginTop', top], ['marginEnd', right], ['marginBottom', bottom], ['marginStart', left]];
158
+ },
159
+ marginHorizontal: rawValue => {
160
+ return [['marginStart', rawValue], ['marginEnd', rawValue]];
161
+ },
162
+ marginVertical: rawValue => {
163
+ return [['marginTop', rawValue], ['marginBottom', rawValue]];
164
+ },
165
+ overflow: rawValue => {
166
+ const [x, y = x] = splitValue(rawValue);
167
+ return [['overflowX', x], ['overflowY', y]];
168
+ },
169
+ padding: rawValue => {
170
+ const [top, right = top, bottom = top, left = right] = typeof rawValue === 'number' ? [rawValue] : splitValue(rawValue);
171
+ return [['paddingTop', top], ['paddingEnd', right], ['paddingBottom', bottom], ['paddingStart', left]];
172
+ },
173
+ paddingHorizontal: rawValue => {
174
+ return [['paddingStart', rawValue], ['paddingEnd', rawValue]];
175
+ },
176
+ paddingVertical: rawValue => {
177
+ return [['paddingTop', rawValue], ['paddingBottom', rawValue]];
178
+ }
179
+ };
180
+ function flatMapExpandedShorthands(objEntry) {
181
+ const [key, value] = objEntry;
182
+ const expansion = expansions[key];
183
+ if (expansion) {
184
+ if (Array.isArray(value)) {
185
+ throw new Error('Cannot use fallbacks for shorthands. Use the expansion instead.');
186
+ }
187
+ return expansion(value);
188
+ }
189
+ return [objEntry];
190
+ }