@atlaskit/datetime-picker 13.11.2 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/datetime-picker
2
2
 
3
+ ## 14.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#133339](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/133339)
8
+ [`eb8297ababb74`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/eb8297ababb74) -
9
+ Refactors the API and ratchets down on props in the `selectProps` in favor of top-level
10
+ alternatives. This ensures that there is one way to do each things and there is a clear
11
+ encapsulation of what prop goes to which picker. A codemod is included which will automate all of
12
+ the changes for you.
13
+
14
+ ## 13.11.3
15
+
16
+ ### Patch Changes
17
+
18
+ - [#132041](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/132041)
19
+ [`ce77b41a18a18`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/ce77b41a18a18) -
20
+ Refactoring internal state management within the pickers.
21
+
3
22
  ## 13.11.2
4
23
 
5
24
  ### Patch Changes
@@ -0,0 +1,432 @@
1
+ import { type API, type FileInfo, type JSCodeshift, type Options } from 'jscodeshift';
2
+ import { type Collection } from 'jscodeshift/src/Collection';
3
+
4
+ import {
5
+ addJSXAttributeToJSXElement,
6
+ getImportDeclarationCollection,
7
+ getImportSpecifierCollection,
8
+ getImportSpecifierName,
9
+ getJSXAttributeByName,
10
+ getJSXAttributesByName,
11
+ hasImportDeclaration,
12
+ removeJSXAttributeByName,
13
+ removeJSXAttributeObjectPropertyByName,
14
+ } from './utils/helpers';
15
+
16
+ type ToPickerFormula = {
17
+ oldPropName: string;
18
+ destination: string[];
19
+ };
20
+
21
+ type FromPickerFormula = {
22
+ source: string[];
23
+ newPropName: string;
24
+ };
25
+
26
+ const importPath = '@atlaskit/datetime-picker';
27
+
28
+ const dateTimePickerImportName = 'DateTimePicker';
29
+ export const datePickerImportName = 'DatePicker';
30
+ export const timePickerImportName = 'TimePicker';
31
+
32
+ const pickerAndSelectPropNames = [
33
+ ['datePickerSelectProps', 'datePickerProps'],
34
+ ['timePickerSelectProps', 'timePickerProps'],
35
+ ];
36
+
37
+ export const dtpPropsToMoveIntoPickerProps: ToPickerFormula[] = [
38
+ {
39
+ oldPropName: 'dateFormat',
40
+ destination: ['datePickerProps', 'dateFormat'],
41
+ },
42
+ {
43
+ oldPropName: 'times',
44
+ destination: ['timePickerProps', 'times'],
45
+ },
46
+ {
47
+ oldPropName: 'timeFormat',
48
+ destination: ['timePickerProps', 'timeFormat'],
49
+ },
50
+ {
51
+ oldPropName: 'timeIsEditable',
52
+ destination: ['timePickerProps', 'timeIsEditable'],
53
+ },
54
+ ];
55
+
56
+ // For the pickers, not for DTP
57
+ export const selectPropsToMoveIntoProps: FromPickerFormula[] = [
58
+ {
59
+ source: ['selectProps', 'aria-describedby'],
60
+ newPropName: 'aria-describedby',
61
+ },
62
+ {
63
+ source: ['selectProps', 'aria-label'],
64
+ newPropName: 'label',
65
+ },
66
+ {
67
+ source: ['selectProps', 'inputId'],
68
+ newPropName: 'id',
69
+ },
70
+ {
71
+ source: ['selectProps', 'placeholder'],
72
+ newPropName: 'placeholder',
73
+ },
74
+ ];
75
+
76
+ /**
77
+ * Move *pickerSelectProps into their respectice *pickerProps as a property
78
+ * named `selectProps`.
79
+ */
80
+ function moveDateTimePickerSelectProps(j: JSCodeshift, collection: Collection<any>) {
81
+ const importDeclarationCollection = getImportDeclarationCollection(j, collection, importPath);
82
+ const importSpecifierCollection = getImportSpecifierCollection(
83
+ j,
84
+ importDeclarationCollection,
85
+ dateTimePickerImportName,
86
+ );
87
+ const importSpecifierName = getImportSpecifierName(importSpecifierCollection);
88
+
89
+ // If our component is not present, quit early
90
+ if (importSpecifierName === null) {
91
+ return;
92
+ }
93
+
94
+ collection.findJSXElements(dateTimePickerImportName).forEach((jsxElementPath) => {
95
+ // For each picker,
96
+ pickerAndSelectPropNames.forEach((migration) => {
97
+ const [selectPropsName, pickerPropsName] = migration;
98
+
99
+ const datePickerPropsAttr = getJSXAttributeByName(j, jsxElementPath, pickerPropsName);
100
+ const datePickerSelectPropsAttr = getJSXAttributeByName(j, jsxElementPath, selectPropsName);
101
+
102
+ // If there are both the *selectProps and the *pickerProps,
103
+ if (datePickerPropsAttr && datePickerSelectPropsAttr) {
104
+ // Get the select props value
105
+ // @ts-ignore -- Property 'expression' does not exist on type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment'. Property 'expression' does not exist on type 'Literal'.
106
+ let datePickerSelectPropsExpression = datePickerSelectPropsAttr?.value?.expression;
107
+ let datePickerSelectProps;
108
+
109
+ if (datePickerSelectPropsExpression?.properties) {
110
+ datePickerSelectProps = j.objectExpression(datePickerSelectPropsExpression.properties);
111
+ } else if (datePickerSelectPropsExpression) {
112
+ datePickerSelectProps = datePickerSelectPropsExpression;
113
+ } else {
114
+ // This is nothing we can use, so skip
115
+ return;
116
+ }
117
+
118
+ // This property definitely exists! Just can't figure out how to type properly. Please forgive me.
119
+ // @ts-ignore -- Property 'expression' does not exist on type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment'. Property 'expression' does not exist on type 'Literal'.
120
+ const datePickerProps = datePickerPropsAttr.value?.expression.properties;
121
+ // Make a new selectProps property that will go inside the *pickerProps
122
+ const selectPropsProperty = j.property(
123
+ 'init',
124
+ j.identifier('selectProps'),
125
+ datePickerSelectProps,
126
+ );
127
+
128
+ // Add this to the component
129
+ datePickerProps.push(selectPropsProperty);
130
+ // If there is not a *pickerProps prop
131
+ } else if (datePickerSelectPropsAttr) {
132
+ // Create one and put the selectProps inside of it
133
+ const newAttr = j.jsxAttribute(
134
+ j.jsxIdentifier(pickerPropsName),
135
+ j.jsxExpressionContainer(
136
+ j.objectExpression([
137
+ j.property(
138
+ 'init',
139
+ j.identifier('selectProps'),
140
+ // @ts-ignore -- Property 'expression' does not exist on type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment'. Property 'expression' does not exist on type 'Literal'.
141
+ datePickerSelectPropsAttr.value.expression,
142
+ ),
143
+ ]),
144
+ ),
145
+ );
146
+ addJSXAttributeToJSXElement(j, jsxElementPath, newAttr);
147
+ }
148
+
149
+ // Remove the *selectProps attribute
150
+ removeJSXAttributeByName(j, jsxElementPath, selectPropsName);
151
+ });
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Move props out of the *pickerProps into their respective top-level props.
157
+ */
158
+ function moveDateTimePickerSelectPropsToPickerProps(
159
+ j: JSCodeshift,
160
+ collection: Collection<any>,
161
+ formulas: FromPickerFormula[],
162
+ ) {
163
+ const importDeclarationCollection = getImportDeclarationCollection(j, collection, importPath);
164
+ const importSpecifierCollection = getImportSpecifierCollection(
165
+ j,
166
+ importDeclarationCollection,
167
+ dateTimePickerImportName,
168
+ );
169
+ const importSpecifierName = getImportSpecifierName(importSpecifierCollection);
170
+
171
+ // If we can't find our import specifier, exit early
172
+ if (importSpecifierName === null) {
173
+ return;
174
+ }
175
+
176
+ // For each component,
177
+ collection.findJSXElements(dateTimePickerImportName).forEach((jsxElementPath) => {
178
+ pickerAndSelectPropNames.forEach((migration) => {
179
+ const pickerPropsName = migration[1];
180
+ // For each picker prop to move into the top level,
181
+ formulas.forEach((formula) => {
182
+ const { source, newPropName: newPropertyName } = formula;
183
+ const [selectPropsName, oldPropertyName] = source;
184
+
185
+ // Get the relevant *picker props where the source prop is found
186
+ const pickerPropsAttr = getJSXAttributeByName(j, jsxElementPath, pickerPropsName);
187
+
188
+ // If no *picker props, return early since there's nothing to do
189
+ if (!pickerPropsAttr) {
190
+ return;
191
+ }
192
+
193
+ // @ts-ignore -- Property 'expression' does not exist on type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment'. Property 'expression' does not exist on type 'Literal'.
194
+ const pickerPropsProperties = pickerPropsAttr.value?.expression.properties;
195
+ const existingNewProperty = pickerPropsProperties.find(
196
+ (property: any) => (property.key?.name || property.key?.value) === newPropertyName,
197
+ );
198
+
199
+ // If the new property we're migrating to exists, leave it alone so that
200
+ // manual remediation can be done. Both shouldn't exist, anyway.
201
+ if (existingNewProperty) {
202
+ return;
203
+ }
204
+
205
+ // Get the selectProps property
206
+ const selectPropsProperty = pickerPropsProperties.find(
207
+ (property: any) => (property.key?.name || property.key?.value) === selectPropsName,
208
+ );
209
+
210
+ // If no *picker.selectProps, return early since there's nothing to do
211
+ if (!selectPropsProperty) {
212
+ return;
213
+ }
214
+
215
+ const selectPropsProperties = selectPropsProperty.value?.properties;
216
+
217
+ // If selectProps has no properties, quit early
218
+ if (!selectPropsProperties) {
219
+ return;
220
+ }
221
+
222
+ // Get old property's node
223
+ const oldProp = selectPropsProperties.find((prop: any) => {
224
+ return prop.key && (prop.key.name || prop.key.value) === oldPropertyName;
225
+ });
226
+
227
+ // If the old property does not exist within the *pickerProps, return early
228
+ if (!oldProp) {
229
+ return;
230
+ }
231
+
232
+ // Add the new attribute to the component
233
+ const newProp = j.objectProperty(j.jsxIdentifier(`"${newPropertyName}"`), oldProp.value);
234
+ pickerPropsProperties.push(newProp);
235
+
236
+ // Remove the old object property
237
+ selectPropsProperties.splice(selectPropsProperties.indexOf(oldProp), 1);
238
+
239
+ if (selectPropsProperties.length === 0) {
240
+ pickerPropsProperties.splice(pickerPropsProperties.indexOf(selectPropsProperty), 1);
241
+ }
242
+ });
243
+ });
244
+ });
245
+ }
246
+
247
+ /**
248
+ * Move props out of the *pickerProps into their respective top-level props.
249
+ */
250
+ function moveSelectPropsToTop(
251
+ j: JSCodeshift,
252
+ collection: Collection<any>,
253
+ formulas: FromPickerFormula[],
254
+ ) {
255
+ const importDeclarationCollection = getImportDeclarationCollection(j, collection, importPath);
256
+ [datePickerImportName, timePickerImportName].forEach((pickerImportName) => {
257
+ const importSpecifierCollection = getImportSpecifierCollection(
258
+ j,
259
+ importDeclarationCollection,
260
+ pickerImportName,
261
+ );
262
+ const importSpecifierName = getImportSpecifierName(importSpecifierCollection);
263
+
264
+ // If we can't find our import specifier, exit early
265
+ if (importSpecifierName === null) {
266
+ return;
267
+ }
268
+
269
+ // For each component,
270
+ collection.findJSXElements(pickerImportName).forEach((jsxElementPath) => {
271
+ // For each picker prop to move into the top level,
272
+ formulas.forEach((formula) => {
273
+ const { source, newPropName } = formula;
274
+ const [selectPropsName, oldPropertyName] = source;
275
+
276
+ // Get the relevant *picker props where the source prop is found
277
+ const selectProps = getJSXAttributeByName(j, jsxElementPath, selectPropsName);
278
+
279
+ // If no date picker props, return early since there's nothing to do
280
+ if (!selectProps) {
281
+ return;
282
+ }
283
+
284
+ // If the new prop we're migrating to exists, leave it alone so that
285
+ // manual remediation can be done. Both shouldn't exist, anyway.
286
+ const existingNewPropAttr = getJSXAttributeByName(j, jsxElementPath, newPropName);
287
+ if (existingNewPropAttr) {
288
+ return;
289
+ }
290
+
291
+ // @ts-ignore -- Property 'expression' does not exist on type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment'. Property 'expression' does not exist on type 'Literal'.
292
+ let selectPropProperties = selectProps.value?.expression.properties;
293
+
294
+ // If the new prop does not yet exist and we have *pickerProps,
295
+ if (selectPropProperties) {
296
+ // Get old property's node
297
+ const oldProp = selectPropProperties.find((prop: any) => {
298
+ return prop.key && (prop.key.name || prop.key.value) === oldPropertyName;
299
+ });
300
+
301
+ // If the old property does exist within the *pickerProps,
302
+ if (oldProp) {
303
+ const newValue = oldProp.value.value
304
+ ? j.literal(oldProp.value.value)
305
+ : j.jsxExpressionContainer(oldProp.value);
306
+ // Add the new attribute to the component
307
+ const newProp = j.jsxAttribute(j.jsxIdentifier(newPropName), newValue);
308
+ addJSXAttributeToJSXElement(j, jsxElementPath, newProp);
309
+ }
310
+ }
311
+
312
+ // Remove the old object property
313
+ removeJSXAttributeObjectPropertyByName(j, jsxElementPath, selectPropsName, oldPropertyName);
314
+ });
315
+ });
316
+ });
317
+ }
318
+
319
+ function moveDateTimePickerProps(
320
+ j: JSCodeshift,
321
+ collection: Collection<any>,
322
+ formulas: ToPickerFormula[],
323
+ ) {
324
+ const importDeclarationCollection = getImportDeclarationCollection(j, collection, importPath);
325
+ const importSpecifierCollection = getImportSpecifierCollection(
326
+ j,
327
+ importDeclarationCollection,
328
+ dateTimePickerImportName,
329
+ );
330
+ const importSpecifierName = getImportSpecifierName(importSpecifierCollection);
331
+
332
+ // If we can't find our component, quit early
333
+ if (importSpecifierName === null) {
334
+ return;
335
+ }
336
+
337
+ // For each datetime picker import,
338
+ collection.findJSXElements(dateTimePickerImportName).forEach((jsxElementPath) => {
339
+ formulas.forEach((formula) => {
340
+ const { oldPropName, destination } = formula;
341
+ const [pickerPropsName, propertyName] = destination;
342
+
343
+ const attributes = j(jsxElementPath).find(j.JSXAttribute).nodes();
344
+
345
+ // Find the old prop
346
+ const oldPropAttr = attributes?.find(
347
+ (attr) => (attr.name?.name || attr.name) === oldPropName,
348
+ );
349
+ // Find the *pickerProps prop if it exists
350
+ const datePickerPickerPropsAttr = attributes?.find(
351
+ (attr) => attr.name && attr.name.name === pickerPropsName,
352
+ );
353
+
354
+ // Boolean attributes have `null` value
355
+ if (!oldPropAttr || oldPropAttr.value === undefined) {
356
+ return;
357
+ }
358
+
359
+ // Create one and put the old value inside of it
360
+ let newValue = oldPropAttr.value;
361
+
362
+ // Remove surrounding brackets when placed in object
363
+ if (newValue === null) {
364
+ newValue = j.booleanLiteral(true);
365
+ } else if (newValue?.type === 'JSXExpressionContainer') {
366
+ // @ts-ignore Type 'JSXEmptyExpression | ExpressionKind' is not assignable to type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment'. Type 'Identifier' is not assignable to type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment'. Property 'openingElement' is missing in type 'Identifier' but required in type 'JSXElement'.
367
+ newValue = newValue.expression;
368
+ // If boolean attribute without explicit value, make it `true`
369
+ }
370
+
371
+ // Just type guarding for TS
372
+ if (!newValue) {
373
+ return;
374
+ }
375
+
376
+ if (datePickerPickerPropsAttr) {
377
+ // @ts-ignore -- Property 'expression' does not exist on type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment'. Property 'expression' does not exist on type 'Literal'.
378
+ const pickerPropsObj = datePickerPickerPropsAttr.value?.expression.properties;
379
+
380
+ // Create a new *pickerProps property and put the old prop inside of it
381
+ // as an object property
382
+ const newPickerProperty = j.property('init', j.identifier(propertyName), newValue);
383
+
384
+ // Add this new property to the existing one
385
+ pickerPropsObj.push(newPickerProperty);
386
+ // Remove the old one
387
+ attributes?.splice(attributes.indexOf(datePickerPickerPropsAttr), 1);
388
+
389
+ // If there is no *pickerProps,
390
+ } else {
391
+ const newProp = j.jsxAttribute(
392
+ j.jsxIdentifier(pickerPropsName),
393
+ j.jsxExpressionContainer(
394
+ // Adding `true` for boolean attributes that have no value;
395
+ // This definitely exists on here. Can't coerce right. May god have mercy on my soul for this ts-ignore.
396
+ // @ts-ignore Argument of type 'LiteralKind | JSXElement | JSXExpressionContainer | JSXFragment | null | undefined' is not assignable to parameter of type 'Literal | JSXText | StringLiteral | NumericLiteral | BigIntLiteral | NullLiteral | BooleanLiteral | ... 53 more ... | TSParameterProperty'. Type 'undefined' is not assignable to type 'Literal | JSXText | StringLiteral | NumericLiteral | BigIntLiteral | NullLiteral | BooleanLiteral | ... 53 more ... | TSParameterProperty'.
397
+ j.objectExpression([j.property('init', j.identifier(propertyName), newValue)]),
398
+ ),
399
+ );
400
+ addJSXAttributeToJSXElement(j, jsxElementPath, newProp);
401
+ }
402
+
403
+ // Get the old prop we're migrating from
404
+ getJSXAttributesByName(j, jsxElementPath, oldPropName).forEach((jsxAttribute) => {
405
+ // Remove the original old prop we're migrating from
406
+ j(jsxAttribute).remove();
407
+ });
408
+ });
409
+ });
410
+ }
411
+
412
+ export default function transformer(fileInfo: FileInfo, { jscodeshift: j }: API, options: Options) {
413
+ const { source } = fileInfo;
414
+ const collection = j(source);
415
+
416
+ // If our component is not here, skip the file
417
+ if (!hasImportDeclaration(j, collection, importPath)) {
418
+ return source;
419
+ }
420
+
421
+ // Move specific DTP props into *pickerProps
422
+ moveDateTimePickerProps(j, collection, dtpPropsToMoveIntoPickerProps);
423
+ // Move all *selectProps into the related *pickerProps.selectProps
424
+ moveDateTimePickerSelectProps(j, collection);
425
+ // Move all relevant *selectProps to *pickerProps
426
+ moveDateTimePickerSelectPropsToPickerProps(j, collection, selectPropsToMoveIntoProps);
427
+
428
+ // Move specific *pickerProps entries into top level
429
+ moveSelectPropsToTop(j, collection, selectPropsToMoveIntoProps);
430
+
431
+ return collection.toSource(options.printOptions || { quote: 'single' });
432
+ }