@procore/text-editor 0.0.1 → 0.0.3

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/codemod/__fixtures__/hammer.config.mjs +7 -0
  3. package/codemod/__fixtures__/procore.config.js +7 -0
  4. package/codemod/__fixtures__/procore.config.ternary.js +18 -0
  5. package/codemod/text-editor-migrate.js +383 -3
  6. package/codemod/text-editor-migrate.test.js +86 -0
  7. package/dist/{src/TextEditor → TextEditor}/TextEditor.d.ts +1 -1
  8. package/dist/TextEditor/TextEditor.js +2 -12
  9. package/dist/TextEditor/TextEditor.js.map +1 -1
  10. package/dist/TextEditor/TextEditor.styles.js +1 -1
  11. package/dist/{src/TextEditor → TextEditor}/TextEditor.types.d.ts +0 -38
  12. package/dist/TextEditor/TextEditor.types.js.map +1 -1
  13. package/dist/TextEditor/TextEditorProvider.js +1 -1
  14. package/dist/TextEditor/TextEditorProvider.js.map +1 -1
  15. package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js +2 -0
  16. package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js.map +1 -1
  17. package/dist/TextEditor/useTabAsNavigation.js +2 -1
  18. package/dist/TextEditor/useTabAsNavigation.js.map +1 -1
  19. package/dist/TextEditor/utils/config.js +5 -2
  20. package/dist/TextEditor/utils/config.js.map +1 -1
  21. package/dist/TextEditorOutput/TextEditorOutput.styles.js +1 -1
  22. package/dist/_typedoc/TextEditor/TextEditor.types.json +16 -68
  23. package/dist/_typedoc/TextEditor/TextEditorProvider.types.json +2 -2
  24. package/dist/_typedoc/TextEditorOutput/TextEditorOutput.types.json +3 -3
  25. package/dist/_typedoc/deprecations.json +1 -1
  26. package/package.json +12 -16
  27. package/dist/codemod/__fixtures__/src/components/ComplexEditor.d.ts +0 -2
  28. package/dist/codemod/__fixtures__/src/components/FormWithRichText.d.ts +0 -2
  29. package/dist/codemod/__fixtures__/src/components/MultilineFormRichText.d.ts +0 -2
  30. package/dist/codemod/__fixtures__/src/components/NoTextEditor.d.ts +0 -2
  31. package/dist/codemod/__fixtures__/src/components/ReadOnlyRichText.d.ts +0 -2
  32. package/dist/codemod/__fixtures__/src/components/SimpleEditor.d.ts +0 -2
  33. package/dist/codemod/__fixtures__/src/components/TypeImportEditor.d.ts +0 -2
  34. /package/dist/{src/TextEditor → TextEditor}/EditorError.d.ts +0 -0
  35. /package/dist/{src/TextEditor → TextEditor}/StickyToolbar/index.d.ts +0 -0
  36. /package/dist/{src/TextEditor → TextEditor}/StickyToolbar/useStickyToolbar.d.ts +0 -0
  37. /package/dist/{src/TextEditor → TextEditor}/StickyToolbar/useStickyToolbar.types.d.ts +0 -0
  38. /package/dist/{src/TextEditor → TextEditor}/TextEditor.styles.d.ts +0 -0
  39. /package/dist/{src/TextEditor → TextEditor}/TextEditorProvider.d.ts +0 -0
  40. /package/dist/{src/TextEditor → TextEditor}/TextEditorProvider.types.d.ts +0 -0
  41. /package/dist/{src/TextEditor → TextEditor}/index.d.ts +0 -0
  42. /package/dist/{src/TextEditor → TextEditor}/license_key.d.ts +0 -0
  43. /package/dist/{src/TextEditor → TextEditor}/plugins/CutPlugin/CutCommand.d.ts +0 -0
  44. /package/dist/{src/TextEditor → TextEditor}/plugins/CutPlugin/CutPlugin.d.ts +0 -0
  45. /package/dist/{src/TextEditor → TextEditor}/plugins/CutPlugin/index.d.ts +0 -0
  46. /package/dist/{src/TextEditor → TextEditor}/plugins/IndentPaddingToMarginPlugin/IndentPaddingToMarginPlugin.d.ts +0 -0
  47. /package/dist/{src/TextEditor → TextEditor}/plugins/IndentPaddingToMarginPlugin/index.d.ts +0 -0
  48. /package/dist/{src/TextEditor → TextEditor}/plugins/PasteAsTextPlugin/PasteAsTextCommand.d.ts +0 -0
  49. /package/dist/{src/TextEditor → TextEditor}/plugins/PasteAsTextPlugin/PasteAsTextPlugin.d.ts +0 -0
  50. /package/dist/{src/TextEditor → TextEditor}/plugins/PasteAsTextPlugin/index.d.ts +0 -0
  51. /package/dist/{src/TextEditor → TextEditor}/plugins/PastePlugin/PasteCommand.d.ts +0 -0
  52. /package/dist/{src/TextEditor → TextEditor}/plugins/PastePlugin/PastePlugin.d.ts +0 -0
  53. /package/dist/{src/TextEditor → TextEditor}/plugins/PastePlugin/index.d.ts +0 -0
  54. /package/dist/{src/TextEditor → TextEditor}/plugins/TabSpacesPlugin/TabSpacesPlugin.d.ts +0 -0
  55. /package/dist/{src/TextEditor → TextEditor}/plugins/TabSpacesPlugin/index.d.ts +0 -0
  56. /package/dist/{src/TextEditor → TextEditor}/textEditorTheming/icons.d.ts +0 -0
  57. /package/dist/{src/TextEditor → TextEditor}/textEditorTheming/index.d.ts +0 -0
  58. /package/dist/{src/TextEditor → TextEditor}/textEditorTheming/textEditorTheming.styles.d.ts +0 -0
  59. /package/dist/{src/TextEditor → TextEditor}/useCKEditorCss.d.ts +0 -0
  60. /package/dist/{src/TextEditor → TextEditor}/useTabAsNavigation.d.ts +0 -0
  61. /package/dist/{src/TextEditor → TextEditor}/utils/config.d.ts +0 -0
  62. /package/dist/{src/TextEditor → TextEditor}/utils/index.d.ts +0 -0
  63. /package/dist/{src/TextEditor → TextEditor}/utils/locale.d.ts +0 -0
  64. /package/dist/{src/TextEditor → TextEditor}/utils/plugins.d.ts +0 -0
  65. /package/dist/{src/TextEditorOutput → TextEditorOutput}/TextEditorOutput.d.ts +0 -0
  66. /package/dist/{src/TextEditorOutput → TextEditorOutput}/TextEditorOutput.styles.d.ts +0 -0
  67. /package/dist/{src/TextEditorOutput → TextEditorOutput}/TextEditorOutput.types.d.ts +0 -0
  68. /package/dist/{src/TextEditorOutput → TextEditorOutput}/TextEditorOutput.utils.d.ts +0 -0
  69. /package/dist/{src/TextEditorOutput → TextEditorOutput}/index.d.ts +0 -0
  70. /package/dist/{src/_storyHelpers → _storyHelpers}/constants.d.ts +0 -0
  71. /package/dist/{src/_utils → _utils}/propsTypedoc.d.ts +0 -0
  72. /package/dist/{src/index.d.ts → index.d.ts} +0 -0
  73. /package/dist/{src/stories → stories}/util.d.ts +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @procore/text-editor
2
+
3
+ ## 0.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 4656bce: Default provider TAB key as navigation to remove keyboard trap
8
+
9
+ ## 0.0.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 7d01b54: Fix some types and remove legacy tinymce no-ops and focus.
14
+
15
+ ## 0.0.1
16
+
17
+ ### Patch Changes
18
+
19
+ - f95beb2: Initial release of a separate TextEditor package, extracted from core-react.
@@ -5,6 +5,13 @@ export default {
5
5
  webpackOverride(config) {
6
6
  return config
7
7
  },
8
+ moduleFederation: {
9
+ shared: {
10
+ react: {
11
+ singleton: true,
12
+ },
13
+ },
14
+ },
8
15
  },
9
16
  jestOverride: (defaultConfig) => {
10
17
  return coreReactJestConfig({
@@ -5,6 +5,13 @@ module.exports = ({ env }) => ({
5
5
  webpackOverride(config) {
6
6
  return config
7
7
  },
8
+ moduleFederation: {
9
+ shared: {
10
+ react: {
11
+ singleton: true,
12
+ },
13
+ },
14
+ },
8
15
  },
9
16
  jestOverride: (defaultConfig) => {
10
17
  return coreReactJestConfig(defaultConfig)
@@ -0,0 +1,18 @@
1
+ const { coreReactJestConfig } = require('@procore/core-react/jestConfig')
2
+
3
+ module.exports = ({ env }) => ({
4
+ app: {
5
+ webpackOverride(config) {
6
+ return config
7
+ },
8
+ moduleFederation: {
9
+ shared:
10
+ process.env.NODE_ENV === 'production'
11
+ ? { react: deps.react, 'react-dom': deps['react-dom'] }
12
+ : {},
13
+ },
14
+ },
15
+ jestOverride: (defaultConfig) => {
16
+ return coreReactJestConfig(defaultConfig)
17
+ },
18
+ })
@@ -8,6 +8,7 @@
8
8
  * 1. Updates imports from @procore/core-react to @procore/text-editor for TextEditor components
9
9
  * 2. Updates Form.RichText usage to include textEditorComponent prop
10
10
  * 3. Updates Jest configuration to use textEditorJestConfig
11
+ * 4. Adds ckeditor5 singleton to module federation shared config
11
12
  */
12
13
 
13
14
  const fs = require('fs')
@@ -28,9 +29,43 @@ const stats = {
28
29
  importsUpdated: 0,
29
30
  formRichTextUpdated: 0,
30
31
  jestConfigUpdated: 0,
32
+ moduleFederationUpdated: 0,
31
33
  errors: [],
32
34
  }
33
35
 
36
+ /**
37
+ * Check if ckeditor5 is in package.json dependencies
38
+ */
39
+ function checkForCkeditor5(targetPath) {
40
+ let currentDir = targetPath
41
+
42
+ // If targetPath is a file, start from its directory
43
+ if (fs.existsSync(targetPath) && fs.statSync(targetPath).isFile()) {
44
+ currentDir = path.dirname(targetPath)
45
+ }
46
+
47
+ // Walk up the directory tree to find package.json
48
+ while (currentDir !== path.dirname(currentDir)) {
49
+ const packageJsonPath = path.join(currentDir, 'package.json')
50
+ if (fs.existsSync(packageJsonPath)) {
51
+ try {
52
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
53
+ const deps = {
54
+ ...packageJson.dependencies,
55
+ ...packageJson.devDependencies,
56
+ ...packageJson.peerDependencies,
57
+ }
58
+ return !!deps['ckeditor5']
59
+ } catch (error) {
60
+ // Ignore parse errors, continue searching
61
+ }
62
+ }
63
+ currentDir = path.dirname(currentDir)
64
+ }
65
+
66
+ return false
67
+ }
68
+
34
69
  // TextEditor-related exports that should be migrated
35
70
  const TEXT_EDITOR_EXPORTS = [
36
71
  'TextEditor',
@@ -254,6 +289,312 @@ function transformFormRichText(fileContent) {
254
289
  return { content: newContent, modified }
255
290
  }
256
291
 
292
+ /**
293
+ * Add ckeditor5 singleton configuration to a shared object
294
+ * Returns the modified content and whether it was modified
295
+ */
296
+ function addSingletonToSharedObject(content, sharedMatch) {
297
+ const insertPosition = sharedMatch.index + sharedMatch[0].length
298
+
299
+ // Detect indentation by looking at the content after `shared: {` or similar
300
+ // Find the first property inside shared to determine proper indentation
301
+ const afterShared = content.substring(insertPosition)
302
+ const firstPropertyMatch = afterShared.match(/\n(\s+)\w+\s*:/)
303
+
304
+ let propertyIndent = ''
305
+ let indentUnit = ' ' // default to 2 spaces
306
+
307
+ if (firstPropertyMatch) {
308
+ // Use the same indentation as the first property inside shared
309
+ propertyIndent = firstPropertyMatch[1]
310
+ // Detect indent unit from the property indent
311
+ const sharedLineMatch = content
312
+ .substring(0, sharedMatch.index)
313
+ .match(/\n(\s+)(?:shared|\?)/)
314
+ if (sharedLineMatch) {
315
+ const sharedIndent = sharedLineMatch[1]
316
+ // indentUnit is the difference between property indent and shared indent
317
+ if (propertyIndent.length > sharedIndent.length) {
318
+ indentUnit = propertyIndent.substring(sharedIndent.length)
319
+ }
320
+ }
321
+ } else {
322
+ // Fallback: detect from the 'shared' line indentation
323
+ const beforeMatch = content.substring(0, sharedMatch.index)
324
+ const lastNewlineIndex = beforeMatch.lastIndexOf('\n')
325
+ const lineStart =
326
+ lastNewlineIndex >= 0
327
+ ? beforeMatch.substring(lastNewlineIndex + 1)
328
+ : beforeMatch
329
+ const baseIndentMatch = lineStart.match(/^(\s*)/)
330
+ const baseIndent = baseIndentMatch ? baseIndentMatch[1] : ''
331
+
332
+ // Determine if we're using tabs or spaces
333
+ const useTabs = baseIndent.includes('\t')
334
+ indentUnit = useTabs ? '\t' : ' '
335
+ propertyIndent = baseIndent + indentUnit
336
+ }
337
+
338
+ // Build the ckeditor5 configuration
339
+ const ckeditor5Config = `
340
+ ${propertyIndent}ckeditor5: {
341
+ ${propertyIndent}${indentUnit}requiredVersion: deps['ckeditor5'],
342
+ ${propertyIndent}${indentUnit}singleton: true,
343
+ ${propertyIndent}},`
344
+
345
+ // Insert the ckeditor5 configuration after the opening brace
346
+ return (
347
+ content.substring(0, insertPosition) +
348
+ ckeditor5Config +
349
+ content.substring(insertPosition)
350
+ )
351
+ }
352
+
353
+ /**
354
+ * Add deps import/require statement to the file if not already present
355
+ */
356
+ function addDepsImport(content, filePath) {
357
+ // Check if deps variable is already declared
358
+ if (
359
+ content.includes("require('./package.json')") ||
360
+ content.includes("require('./package.json').dependencies") ||
361
+ content.includes("from './package.json'")
362
+ ) {
363
+ return content
364
+ }
365
+
366
+ // Determine if using CommonJS or ES modules
367
+ const isESModule =
368
+ content.includes('export default') ||
369
+ content.includes('export {') ||
370
+ filePath.endsWith('.mjs')
371
+
372
+ if (isESModule) {
373
+ // For ES modules, add import at the top
374
+ const importStatement =
375
+ "import deps from './package.json' with { type: 'json' }\n"
376
+
377
+ // Find the first import or export statement
378
+ const firstImportMatch = content.match(/^(import|export)/m)
379
+ if (firstImportMatch) {
380
+ const insertPos = content.indexOf(firstImportMatch[0])
381
+ return (
382
+ content.substring(0, insertPos) +
383
+ importStatement +
384
+ content.substring(insertPos)
385
+ )
386
+ } else {
387
+ return importStatement + content
388
+ }
389
+ } else {
390
+ // For CommonJS, add require at the top
391
+ const requireStatement =
392
+ "const deps = require('./package.json').dependencies\n"
393
+
394
+ // Find the first require or const statement
395
+ const firstRequireMatch = content.match(/^(const|let|var|require)/m)
396
+ if (firstRequireMatch) {
397
+ const insertPos = content.indexOf(firstRequireMatch[0])
398
+ return (
399
+ content.substring(0, insertPos) +
400
+ requireStatement +
401
+ content.substring(insertPos)
402
+ )
403
+ } else {
404
+ return requireStatement + content
405
+ }
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Find the matching closing brace for an opening brace at the given position
411
+ */
412
+ function findMatchingBrace(content, openBracePos) {
413
+ let depth = 1
414
+ let pos = openBracePos + 1
415
+
416
+ while (pos < content.length && depth > 0) {
417
+ const char = content[pos]
418
+ if (char === '{') {
419
+ depth++
420
+ } else if (char === '}') {
421
+ depth--
422
+ }
423
+ pos++
424
+ }
425
+
426
+ return depth === 0 ? pos - 1 : -1
427
+ }
428
+
429
+ /**
430
+ * Check if an object block contains a 'react' key
431
+ */
432
+ function objectContainsReact(content, openBracePos) {
433
+ const closeBracePos = findMatchingBrace(content, openBracePos)
434
+ if (closeBracePos === -1) return false
435
+
436
+ const objectContent = content.substring(openBracePos, closeBracePos + 1)
437
+ // Check for react as a key (handles react:, 'react':, "react":)
438
+ return /(?:^|[{,\s])react\s*:|['"]react['"]\s*:/.test(objectContent)
439
+ }
440
+
441
+ /**
442
+ * Transform module federation with ternary conditional pattern
443
+ * Handles patterns like:
444
+ * shared: process.env.NODE_ENV === 'production'
445
+ * ? { react: deps.react, 'react-dom': deps['react-dom'] }
446
+ * : {},
447
+ * Only adds ckeditor5 to branches that contain 'react'
448
+ */
449
+ function transformModuleFederationTernary(fileContent, filePath) {
450
+ let newContent = fileContent
451
+
452
+ // Pattern to match ternary conditional for shared - find the opening brace of the consequent
453
+ // Matches: shared: <condition> ? {
454
+ const ternaryRegex =
455
+ /shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?\s*(\{)/g
456
+
457
+ let match
458
+ let offset = 0
459
+
460
+ // Find all ternary patterns and process the consequent (true branch)
461
+ while ((match = ternaryRegex.exec(fileContent)) !== null) {
462
+ const adjustedIndex = match.index + offset
463
+ const bracePos = adjustedIndex + match[0].length - 1 // Position of the opening brace
464
+
465
+ // Only add ckeditor5 if this branch contains 'react'
466
+ if (!objectContainsReact(newContent, bracePos)) {
467
+ continue
468
+ }
469
+
470
+ const adjustedMatch = {
471
+ ...match,
472
+ index: bracePos,
473
+ 0: '{',
474
+ }
475
+
476
+ const beforeLength = newContent.length
477
+ newContent = addSingletonToSharedObject(newContent, adjustedMatch)
478
+ offset += newContent.length - beforeLength
479
+ }
480
+
481
+ // Now find and process the alternate (false branch) - the : { ... } part
482
+ // We need to find the consequent's closing brace first, then find the alternate
483
+ const ternaryRegex2 = /shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?\s*\{/g
484
+
485
+ let match2
486
+ while ((match2 = ternaryRegex2.exec(newContent)) !== null) {
487
+ // Find the opening brace of the consequent
488
+ const consequentOpenBrace = match2.index + match2[0].length - 1
489
+
490
+ // Find the matching closing brace
491
+ const consequentCloseBrace = findMatchingBrace(
492
+ newContent,
493
+ consequentOpenBrace
494
+ )
495
+
496
+ if (consequentCloseBrace === -1) continue
497
+
498
+ // Find the alternate branch - look for : { after the closing brace
499
+ const afterConsequent = newContent.substring(consequentCloseBrace + 1)
500
+ const alternateMatch = afterConsequent.match(/^\s*:\s*(\{)/)
501
+
502
+ if (alternateMatch) {
503
+ const alternateBracePos =
504
+ consequentCloseBrace +
505
+ 1 +
506
+ alternateMatch.index +
507
+ alternateMatch[0].length -
508
+ 1
509
+
510
+ // Only add ckeditor5 if this branch contains 'react'
511
+ if (!objectContainsReact(newContent, alternateBracePos)) {
512
+ continue
513
+ }
514
+
515
+ const adjustedMatch = {
516
+ index: alternateBracePos,
517
+ 0: '{',
518
+ }
519
+
520
+ newContent = addSingletonToSharedObject(newContent, adjustedMatch)
521
+ }
522
+ }
523
+
524
+ // Add deps import if needed only if we made changes
525
+ if (newContent !== fileContent) {
526
+ newContent = addDepsImport(newContent, filePath)
527
+ }
528
+
529
+ const modified = newContent !== fileContent
530
+ if (modified) {
531
+ stats.moduleFederationUpdated++
532
+ }
533
+
534
+ return { content: newContent, modified }
535
+ }
536
+
537
+ /**
538
+ * Transform module federation configuration to add ckeditor5 singleton
539
+ * Handles both hammer.config.* and procore.config.* files
540
+ * Supports both direct object pattern and ternary conditional pattern
541
+ */
542
+ function transformModuleFederation(fileContent, filePath) {
543
+ let modified = false
544
+ let newContent = fileContent
545
+ const fileName = path.basename(filePath)
546
+
547
+ // Only process hammer.config.* or procore.config.* files
548
+ if (
549
+ !fileName.startsWith('hammer.config') &&
550
+ !fileName.startsWith('procore.config')
551
+ ) {
552
+ return { content: newContent, modified }
553
+ }
554
+
555
+ // Check if moduleFederation.shared exists in the file
556
+ if (!newContent.includes('moduleFederation')) {
557
+ return { content: newContent, modified }
558
+ }
559
+
560
+ // Check if shared key exists
561
+ if (!newContent.includes('shared')) {
562
+ return { content: newContent, modified }
563
+ }
564
+
565
+ // Check if ckeditor5 is already configured
566
+ if (newContent.includes('ckeditor5')) {
567
+ return { content: newContent, modified }
568
+ }
569
+
570
+ // Check if this is a ternary conditional pattern
571
+ const ternaryPattern = /shared\s*:\s*[\w.]+\s*===?\s*['"][^'"]+['"]\s*\?/
572
+ if (ternaryPattern.test(newContent)) {
573
+ return transformModuleFederationTernary(newContent, filePath)
574
+ }
575
+
576
+ // Pattern to match shared object in moduleFederation
577
+ // Handles both `shared: {` and `shared:{` patterns
578
+ const sharedObjectRegex = /(shared\s*:\s*\{)/g
579
+
580
+ if (sharedObjectRegex.test(newContent)) {
581
+ // Reset regex lastIndex
582
+ sharedObjectRegex.lastIndex = 0
583
+
584
+ // Find the match and its position
585
+ const match = sharedObjectRegex.exec(newContent)
586
+ if (match) {
587
+ newContent = addSingletonToSharedObject(newContent, match)
588
+ newContent = addDepsImport(newContent, filePath)
589
+
590
+ modified = true
591
+ stats.moduleFederationUpdated++
592
+ }
593
+ }
594
+
595
+ return { content: newContent, modified }
596
+ }
597
+
257
598
  /**
258
599
  * Transform Jest configuration files
259
600
  */
@@ -315,7 +656,8 @@ function processFile(filePath) {
315
656
  // Config file detection
316
657
  const isConfigFile =
317
658
  /^jest\.(config|setup)\.(js|ts|cjs|mjs)$/.test(fileName) ||
318
- /\.config\.(js|ts|cjs|mjs)$/.test(fileName)
659
+ /\.config\.(js|ts|cjs|mjs)$/.test(fileName) ||
660
+ /\.config\..*\.(js|ts|cjs|mjs)$/.test(fileName) // Last one for tests like "procore.config.ternay.js"
319
661
 
320
662
  // Process Jest configuration
321
663
  if (isConfigFile && content.includes('coreReactJestConfig')) {
@@ -326,6 +668,15 @@ function processFile(filePath) {
326
668
  }
327
669
  }
328
670
 
671
+ // Process module federation configuration for hammer.config.* and procore.config.* files
672
+ if (isConfigFile) {
673
+ const modFedResult = transformModuleFederation(newContent, filePath)
674
+ if (modFedResult.modified) {
675
+ newContent = modFedResult.content
676
+ wasModified = true
677
+ }
678
+ }
679
+
329
680
  // For regular JS/TS files, process imports and Form.RichText
330
681
  if (['.js', '.jsx', '.ts', '.tsx'].includes(ext) && !isConfigFile) {
331
682
  // Transform imports
@@ -420,6 +771,7 @@ ${colors.bold}Transformations:${colors.reset}
420
771
  • Updates imports from @procore/core-react to @procore/text-editor
421
772
  • Adds textEditorComponent prop to Form.RichText
422
773
  • Updates Jest configuration to use textEditorJestConfig
774
+ • Adds ckeditor5 singleton to module federation shared config
423
775
 
424
776
  ${colors.bold}Examples:${colors.reset}
425
777
 
@@ -433,6 +785,24 @@ ${colors.bold}Examples:${colors.reset}
433
785
  // Default to current directory if no path provided
434
786
  const targetPath = path.resolve(args[0] || '.')
435
787
 
788
+ // Check if ckeditor5 is installed before running
789
+ if (!checkForCkeditor5(targetPath)) {
790
+ console.log(
791
+ `\n${colors.info}ℹ${colors.reset} ckeditor5 not found in package.json\n`
792
+ )
793
+ console.log(`Are you sure you need a text-editor?\n`)
794
+ console.log(
795
+ ` To use TextEditor components, you need to install ckeditor5 first:\n`
796
+ )
797
+ console.log(` ${colors.info}For libraries:${colors.reset}`)
798
+ console.log(` yarn add ckeditor5@^47 --peer --dev\n`)
799
+ console.log(` ${colors.info}For apps:${colors.reset}`)
800
+ console.log(` yarn add ckeditor5@^47\n`)
801
+ console.log(` After installing, re-run this codemod.\n`)
802
+ console.log(` See README_MIGRATION.md for more details.\n`)
803
+ return
804
+ }
805
+
436
806
  console.log(
437
807
  `\n${colors.bold}Starting TextEditor migration...${colors.reset}\n`
438
808
  )
@@ -488,6 +858,9 @@ ${colors.bold}Examples:${colors.reset}
488
858
  console.log(
489
859
  `${colors.success}✓${colors.reset} Jest configs updated: ${stats.jestConfigUpdated}`
490
860
  )
861
+ console.log(
862
+ `${colors.success}✓${colors.reset} Module federation configs updated: ${stats.moduleFederationUpdated}`
863
+ )
491
864
 
492
865
  if (stats.errors.length > 0) {
493
866
  console.log(`\n${colors.error}Errors encountered:${colors.reset}`)
@@ -500,8 +873,15 @@ ${colors.bold}Examples:${colors.reset}
500
873
 
501
874
  if (stats.filesProcessed > 0) {
502
875
  console.log(`${colors.bold}Next steps:${colors.reset}\n`)
503
- console.log('1. Review the changes made by the codemod')
504
- console.log('2. Run your tests to ensure everything works correctly\n')
876
+ console.log(
877
+ '1. Review the changes made by the codemod. May need to be linted.\n'
878
+ )
879
+ console.log(
880
+ '2. Review previous implementation for additional props and migrate to CKEditor, https://ckeditor.com/docs/ckeditor5/latest/features/index.html\n'
881
+ )
882
+ console.log(
883
+ '3. Run your tests to ensure everything works correctly and test a deploy preview.\n'
884
+ )
505
885
  }
506
886
  }
507
887
 
@@ -169,6 +169,92 @@ describe('text-editor-migrate codemod', () => {
169
169
  })
170
170
  })
171
171
 
172
+ describe('module federation transformations', () => {
173
+ it('should add ckeditor5 to moduleFederation.shared in hammer.config.mjs', () => {
174
+ execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
175
+
176
+ const content = readFile('hammer.config.mjs')
177
+
178
+ expect(content).toContain('ckeditor5:')
179
+ expect(content).toContain("requiredVersion: deps['ckeditor5']")
180
+ expect(content).toContain('singleton: true')
181
+ expect(content).toContain("import deps from './package.json'")
182
+ })
183
+
184
+ it('should add ckeditor5 to moduleFederation.shared in procore.config.js', () => {
185
+ execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
186
+
187
+ const content = readFile('procore.config.js')
188
+
189
+ expect(content).toContain('ckeditor5:')
190
+ expect(content).toContain("requiredVersion: deps['ckeditor5']")
191
+ expect(content).toContain('singleton: true')
192
+ expect(content).toContain(
193
+ "const deps = require('./package.json').dependencies"
194
+ )
195
+ })
196
+
197
+ it('should not add ckeditor5 if already present', () => {
198
+ // First run adds ckeditor5
199
+ execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
200
+ const firstRun = readFile('hammer.config.mjs')
201
+
202
+ // Second run should not duplicate
203
+ execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
204
+ const secondRun = readFile('hammer.config.mjs')
205
+
206
+ expect(firstRun).toBe(secondRun)
207
+ // Count occurrences of ckeditor5 - should only appear once
208
+ const matches = secondRun.match(/ckeditor5:/g)
209
+ expect(matches).toHaveLength(1)
210
+ })
211
+
212
+ it('should add ckeditor5 only to ternary branches that contain react', () => {
213
+ execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
214
+
215
+ const content = readFile('procore.config.ternary.js')
216
+
217
+ // Should have ckeditor5 only in the branch with react (production branch)
218
+ const ckeditor5Matches = content.match(/ckeditor5:/g)
219
+ expect(ckeditor5Matches).toHaveLength(1)
220
+
221
+ // Should have deps import
222
+ expect(content).toContain(
223
+ "const deps = require('./package.json').dependencies"
224
+ )
225
+
226
+ // Should have singleton: true only once
227
+ const singletonMatches = content.match(/singleton:\s*true/g)
228
+ expect(singletonMatches).toHaveLength(1)
229
+ })
230
+
231
+ it('should not modify config files without moduleFederation.shared', () => {
232
+ // Create a config file without moduleFederation
233
+ const noModFedConfig = `
234
+ const { coreReactJestConfig } = require('@procore/core-react/jestConfig')
235
+
236
+ module.exports = {
237
+ app: {
238
+ webpackOverride(config) {
239
+ return config
240
+ },
241
+ },
242
+ }
243
+ `
244
+ fs.writeFileSync(
245
+ path.join(testDir, 'no-mod-fed.config.js'),
246
+ noModFedConfig
247
+ )
248
+
249
+ execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
250
+
251
+ const content = readFile('no-mod-fed.config.js')
252
+
253
+ expect(content).not.toContain('ckeditor5')
254
+ expect(content).not.toContain("require('./package.json')")
255
+ })
256
+ })
257
+
172
258
  describe('edge cases', () => {
173
259
  it('should not modify files without TextEditor usage', () => {
174
260
  execFileSync('node', [codemodPath, testDir], { stdio: 'ignore' })
@@ -41,4 +41,4 @@ import type { TextEditorProps } from './TextEditor.types';
41
41
  * }),
42
42
  * })
43
43
  */
44
- export declare function TextEditor({ disabled, error, value, initialValue, onChange, onInit, onBlur, onKeyDown, config: externalConfig, plugins: stringPlugins, locale: propLocale, onDirty, tinyMCE: _tinyMCE, init: _init, onEditorChange: _onEditorChange, onFocusOut: _onFocusOut, ...restProps }: TextEditorProps): React.JSX.Element | null;
44
+ export declare function TextEditor({ disabled, error, value, initialValue, onChange, onInit, onBlur, onKeyDown, config: externalConfig, plugins: stringPlugins, locale: propLocale, onDirty, ...restProps }: TextEditorProps): React.JSX.Element | null;
@@ -1,5 +1,5 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
- var _excluded = ["disabled", "error", "value", "initialValue", "onChange", "onInit", "onBlur", "onKeyDown", "config", "plugins", "locale", "onDirty", "tinyMCE", "init", "onEditorChange", "onFocusOut"];
2
+ var _excluded = ["disabled", "error", "value", "initialValue", "onChange", "onInit", "onBlur", "onKeyDown", "config", "plugins", "locale", "onDirty"];
3
3
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
4
4
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5
5
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
@@ -17,7 +17,7 @@ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t =
17
17
  import { CKEditor } from '@ckeditor/ckeditor5-react';
18
18
  import { ClassicEditor } from 'ckeditor5';
19
19
  import React from 'react';
20
- import { UNSAFE_useOverridableFocusScope, useI18nContext, useZIndexContext } from '@procore/core-react';
20
+ import { useI18nContext, useZIndexContext } from '@procore/core-react';
21
21
  import { useStickyToolbar } from './StickyToolbar';
22
22
  import { GlobalEditorStyles, StyledTextEditor } from './TextEditor.styles';
23
23
  import { TextEditorContext } from './TextEditorProvider';
@@ -80,10 +80,6 @@ export function TextEditor(_ref) {
80
80
  stringPlugins = _ref.plugins,
81
81
  propLocale = _ref.locale,
82
82
  onDirty = _ref.onDirty,
83
- _tinyMCE = _ref.tinyMCE,
84
- _init = _ref.init,
85
- _onEditorChange = _ref.onEditorChange,
86
- _onFocusOut = _ref.onFocusOut,
87
83
  restProps = _objectWithoutProperties(_ref, _excluded);
88
84
  var _useCKEditorCss = useCKEditorCss(),
89
85
  cssLoading = _useCKEditorCss.isLoading;
@@ -135,12 +131,6 @@ export function TextEditor(_ref) {
135
131
  var finalConfig = _objectSpread(_objectSpread({}, configWithPlugins), externalResult !== null && externalResult !== void 0 ? externalResult : {});
136
132
  return finalConfig;
137
133
  }, [externalConfig, locale, stringPlugins]);
138
- var focusScope = UNSAFE_useOverridableFocusScope();
139
- React.useEffect(function () {
140
- focusScope.setProps({
141
- contain: false
142
- });
143
- }, [focusScope]);
144
134
  var bindKeyDownHandler = React.useCallback(function (editor) {
145
135
  if (!onKeyDown) {
146
136
  return;