@wordpress/format-library 4.27.1 → 4.28.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/bold/index.js +1 -2
  3. package/build/bold/index.js.map +1 -1
  4. package/build/code/index.js +1 -2
  5. package/build/code/index.js.map +1 -1
  6. package/build/default-formats.js +1 -2
  7. package/build/default-formats.js.map +1 -1
  8. package/build/default-formats.native.js +1 -2
  9. package/build/default-formats.native.js.map +1 -1
  10. package/build/image/index.js +1 -2
  11. package/build/image/index.js.map +1 -1
  12. package/build/italic/index.js +1 -2
  13. package/build/italic/index.js.map +1 -1
  14. package/build/keyboard/index.js +1 -2
  15. package/build/keyboard/index.js.map +1 -1
  16. package/build/language/index.js +1 -2
  17. package/build/language/index.js.map +1 -1
  18. package/build/link/index.js +84 -24
  19. package/build/link/index.js.map +1 -1
  20. package/build/link/index.native.js +1 -2
  21. package/build/link/index.native.js.map +1 -1
  22. package/build/link/inline.js +59 -64
  23. package/build/link/inline.js.map +1 -1
  24. package/build/link/modal-screens/link-picker-screen.native.js +1 -2
  25. package/build/link/modal-screens/link-picker-screen.native.js.map +1 -1
  26. package/build/link/modal-screens/link-settings-screen.native.js +1 -2
  27. package/build/link/modal-screens/link-settings-screen.native.js.map +1 -1
  28. package/build/link/modal-screens/screens.native.js +1 -2
  29. package/build/link/modal-screens/screens.native.js.map +1 -1
  30. package/build/link/modal.native.js +1 -2
  31. package/build/link/modal.native.js.map +1 -1
  32. package/build/link/use-link-instance-key.js +1 -2
  33. package/build/link/use-link-instance-key.js.map +1 -1
  34. package/build/strikethrough/index.js +1 -2
  35. package/build/strikethrough/index.js.map +1 -1
  36. package/build/subscript/index.js +1 -2
  37. package/build/subscript/index.js.map +1 -1
  38. package/build/superscript/index.js +1 -2
  39. package/build/superscript/index.js.map +1 -1
  40. package/build/text-color/index.js +4 -6
  41. package/build/text-color/index.js.map +1 -1
  42. package/build/text-color/index.native.js +1 -2
  43. package/build/text-color/index.native.js.map +1 -1
  44. package/build/underline/index.js +1 -2
  45. package/build/underline/index.js.map +1 -1
  46. package/build/unknown/index.js +1 -2
  47. package/build/unknown/index.js.map +1 -1
  48. package/build-module/link/index.js +85 -24
  49. package/build-module/link/index.js.map +1 -1
  50. package/build-module/link/inline.js +54 -58
  51. package/build-module/link/inline.js.map +1 -1
  52. package/package.json +14 -14
  53. package/src/link/index.js +95 -35
  54. package/src/link/inline.js +65 -73
@@ -1 +1 @@
1
- {"version":3,"names":["useMemo","useRef","createInterpolateElement","__","sprintf","speak","Popover","prependHTTP","create","insert","isCollapsed","applyFormat","removeFormat","slice","replace","split","concat","useAnchor","__experimentalLinkControl","LinkControl","store","blockEditorStore","useCachedTruthy","useSelect","createLinkFormat","isValidHref","getFormatBoundary","link","settings","LINK_SETTINGS","DEFAULT_LINK_SETTINGS","id","title","InlineLinkUI","isActive","activeAttributes","addingLink","value","onChange","stopAddingLink","contentRef","richLinkTextValue","getRichTextValueFromSelection","richTextText","text","createPageEntity","userCanCreatePages","select","getSettings","_settings","__experimentalCreatePageEntity","__experimentalUserCanCreatePages","linkValue","url","type","opensInNewTab","target","nofollow","rel","includes","removeLink","newValue","onChangeLink","nextValue","hasLink","isNewLink","newUrl","linkFormat","undefined","String","opensInNewWindow","newText","toInsert","length","boundary","valBefore","valAfter","start","newValAfter","returnFocusToRichText","popoverAnchor","editableContentElement","current","cachedRect","getBoundingClientRect","focusOnMount","handleCreate","pageTitle","page","status","rendered","kind","createButtonText","searchTerm","mark","createElement","anchor","onClose","onFocusOutside","placement","offset","shift","onRemove","forceIsEditingLink","hasRichPreviews","createSuggestion","withCreateSuggestion","createSuggestionButtonText","hasTextControl","showInitialSuggestions","suggestionsQuery","initialSuggestionsSearchOptions","subtype","perPage","textStart","textEnd","end"],"sources":["@wordpress/format-library/src/link/inline.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { useMemo, useRef, createInterpolateElement } from '@wordpress/element';\nimport { __, sprintf } from '@wordpress/i18n';\nimport { speak } from '@wordpress/a11y';\nimport { Popover } from '@wordpress/components';\nimport { prependHTTP } from '@wordpress/url';\nimport {\n\tcreate,\n\tinsert,\n\tisCollapsed,\n\tapplyFormat,\n\tremoveFormat,\n\tslice,\n\treplace,\n\tsplit,\n\tconcat,\n\tuseAnchor,\n} from '@wordpress/rich-text';\nimport {\n\t__experimentalLinkControl as LinkControl,\n\tstore as blockEditorStore,\n\tuseCachedTruthy,\n} from '@wordpress/block-editor';\nimport { useSelect } from '@wordpress/data';\n\n/**\n * Internal dependencies\n */\nimport { createLinkFormat, isValidHref, getFormatBoundary } from './utils';\nimport { link as settings } from './index';\n\nconst LINK_SETTINGS = [\n\t...LinkControl.DEFAULT_LINK_SETTINGS,\n\t{\n\t\tid: 'nofollow',\n\t\ttitle: __( 'Mark as nofollow' ),\n\t},\n];\n\nfunction InlineLinkUI( {\n\tisActive,\n\tactiveAttributes,\n\taddingLink,\n\tvalue,\n\tonChange,\n\tstopAddingLink,\n\tcontentRef,\n} ) {\n\tconst richLinkTextValue = getRichTextValueFromSelection( value, isActive );\n\n\t// Get the text content minus any HTML tags.\n\tconst richTextText = richLinkTextValue.text;\n\n\tconst { createPageEntity, userCanCreatePages } = useSelect( ( select ) => {\n\t\tconst { getSettings } = select( blockEditorStore );\n\t\tconst _settings = getSettings();\n\n\t\treturn {\n\t\t\tcreatePageEntity: _settings.__experimentalCreatePageEntity,\n\t\t\tuserCanCreatePages: _settings.__experimentalUserCanCreatePages,\n\t\t};\n\t}, [] );\n\n\tconst linkValue = useMemo(\n\t\t() => ( {\n\t\t\turl: activeAttributes.url,\n\t\t\ttype: activeAttributes.type,\n\t\t\tid: activeAttributes.id,\n\t\t\topensInNewTab: activeAttributes.target === '_blank',\n\t\t\tnofollow: activeAttributes.rel?.includes( 'nofollow' ),\n\t\t\ttitle: richTextText,\n\t\t} ),\n\t\t[\n\t\t\tactiveAttributes.id,\n\t\t\tactiveAttributes.rel,\n\t\t\tactiveAttributes.target,\n\t\t\tactiveAttributes.type,\n\t\t\tactiveAttributes.url,\n\t\t\trichTextText,\n\t\t]\n\t);\n\n\tfunction removeLink() {\n\t\tconst newValue = removeFormat( value, 'core/link' );\n\t\tonChange( newValue );\n\t\tstopAddingLink();\n\t\tspeak( __( 'Link removed.' ), 'assertive' );\n\t}\n\n\tfunction onChangeLink( nextValue ) {\n\t\tconst hasLink = linkValue?.url;\n\t\tconst isNewLink = ! hasLink;\n\n\t\t// Merge the next value with the current link value.\n\t\tnextValue = {\n\t\t\t...linkValue,\n\t\t\t...nextValue,\n\t\t};\n\n\t\tconst newUrl = prependHTTP( nextValue.url );\n\t\tconst linkFormat = createLinkFormat( {\n\t\t\turl: newUrl,\n\t\t\ttype: nextValue.type,\n\t\t\tid:\n\t\t\t\tnextValue.id !== undefined && nextValue.id !== null\n\t\t\t\t\t? String( nextValue.id )\n\t\t\t\t\t: undefined,\n\t\t\topensInNewWindow: nextValue.opensInNewTab,\n\t\t\tnofollow: nextValue.nofollow,\n\t\t} );\n\n\t\tconst newText = nextValue.title || newUrl;\n\n\t\tif ( isCollapsed( value ) && ! isActive ) {\n\t\t\t// Scenario: we don't have any actively selected text or formats.\n\t\t\tconst toInsert = applyFormat(\n\t\t\t\tcreate( { text: newText } ),\n\t\t\t\tlinkFormat,\n\t\t\t\t0,\n\t\t\t\tnewText.length\n\t\t\t);\n\t\t\tonChange( insert( value, toInsert ) );\n\t\t} else {\n\t\t\t// Scenario: we have any active text selection or an active format.\n\t\t\tlet newValue;\n\n\t\t\tif ( newText === richTextText ) {\n\t\t\t\t// If we're not updating the text then ignore.\n\t\t\t\tnewValue = applyFormat( value, linkFormat );\n\t\t\t} else {\n\t\t\t\t// Create new RichText value for the new text in order that we\n\t\t\t\t// can apply formats to it.\n\t\t\t\tnewValue = create( { text: newText } );\n\n\t\t\t\t// Apply the new Link format to this new text value.\n\t\t\t\tnewValue = applyFormat(\n\t\t\t\t\tnewValue,\n\t\t\t\t\tlinkFormat,\n\t\t\t\t\t0,\n\t\t\t\t\tnewText.length\n\t\t\t\t);\n\n\t\t\t\t// Get the boundaries of the active link format.\n\t\t\t\tconst boundary = getFormatBoundary( value, {\n\t\t\t\t\ttype: 'core/link',\n\t\t\t\t} );\n\n\t\t\t\t// Split the value at the start of the active link format.\n\t\t\t\t// Passing \"start\" as the 3rd parameter is required to ensure\n\t\t\t\t// the second half of the split value is split at the format's\n\t\t\t\t// start boundary and avoids relying on the value's \"end\" property\n\t\t\t\t// which may not correspond correctly.\n\t\t\t\tconst [ valBefore, valAfter ] = split(\n\t\t\t\t\tvalue,\n\t\t\t\t\tboundary.start,\n\t\t\t\t\tboundary.start\n\t\t\t\t);\n\n\t\t\t\t// Update the original (full) RichTextValue replacing the\n\t\t\t\t// target text with the *new* RichTextValue containing:\n\t\t\t\t// 1. The new text content.\n\t\t\t\t// 2. The new link format.\n\t\t\t\t// As \"replace\" will operate on the first match only, it is\n\t\t\t\t// run only against the second half of the value which was\n\t\t\t\t// split at the active format's boundary. This avoids a bug\n\t\t\t\t// with incorrectly targetted replacements.\n\t\t\t\t// See: https://github.com/WordPress/gutenberg/issues/41771.\n\t\t\t\t// Note original formats will be lost when applying this change.\n\t\t\t\t// That is expected behaviour.\n\t\t\t\t// See: https://github.com/WordPress/gutenberg/pull/33849#issuecomment-936134179.\n\t\t\t\tconst newValAfter = replace( valAfter, richTextText, newValue );\n\n\t\t\t\tnewValue = concat( valBefore, newValAfter );\n\t\t\t}\n\n\t\t\tonChange( newValue );\n\t\t}\n\n\t\t// Focus should only be returned to the rich text on submit if this link is not\n\t\t// being created for the first time. If it is then focus should remain within the\n\t\t// Link UI because it should remain open for the user to modify the link they have\n\t\t// just created.\n\t\tif ( ! isNewLink ) {\n\t\t\tconst returnFocusToRichText = true;\n\t\t\tstopAddingLink( returnFocusToRichText );\n\t\t}\n\n\t\tif ( ! isValidHref( newUrl ) ) {\n\t\t\tspeak(\n\t\t\t\t__(\n\t\t\t\t\t'Warning: the link has been inserted but may have errors. Please test it.'\n\t\t\t\t),\n\t\t\t\t'assertive'\n\t\t\t);\n\t\t} else if ( isActive ) {\n\t\t\tspeak( __( 'Link edited.' ), 'assertive' );\n\t\t} else {\n\t\t\tspeak( __( 'Link inserted.' ), 'assertive' );\n\t\t}\n\t}\n\n\tconst popoverAnchor = useAnchor( {\n\t\teditableContentElement: contentRef.current,\n\t\tsettings,\n\t} );\n\n\t// As you change the link by interacting with the Link UI\n\t// the return value of document.getSelection jumps to the field you're editing,\n\t// not the highlighted text. Given that useAnchor uses document.getSelection,\n\t// it will return null, since it can't find the <mark> element within the Link UI.\n\t// This caches the last truthy value of the selection anchor reference.\n\t// This ensures the Popover is positioned correctly on initial submission of the link.\n\tconst cachedRect = useCachedTruthy( popoverAnchor.getBoundingClientRect() );\n\tpopoverAnchor.getBoundingClientRect = () => cachedRect;\n\n\t// Focus should only be moved into the Popover when the Link is being created or edited.\n\t// When the Link is in \"preview\" mode focus should remain on the rich text because at\n\t// this point the Link dialog is informational only and thus the user should be able to\n\t// continue editing the rich text.\n\t// Ref used because the focusOnMount prop shouldn't evolve during render of a Popover\n\t// otherwise it causes a render of the content.\n\tconst focusOnMount = useRef( addingLink ? 'firstElement' : false );\n\n\tasync function handleCreate( pageTitle ) {\n\t\tconst page = await createPageEntity( {\n\t\t\ttitle: pageTitle,\n\t\t\tstatus: 'draft',\n\t\t} );\n\n\t\treturn {\n\t\t\tid: page.id,\n\t\t\ttype: page.type,\n\t\t\ttitle: page.title.rendered,\n\t\t\turl: page.link,\n\t\t\tkind: 'post-type',\n\t\t};\n\t}\n\n\tfunction createButtonText( searchTerm ) {\n\t\treturn createInterpolateElement(\n\t\t\tsprintf(\n\t\t\t\t/* translators: %s: search term. */\n\t\t\t\t__( 'Create page: <mark>%s</mark>' ),\n\t\t\t\tsearchTerm\n\t\t\t),\n\t\t\t{ mark: <mark /> }\n\t\t);\n\t}\n\n\treturn (\n\t\t<Popover\n\t\t\tanchor={ popoverAnchor }\n\t\t\tfocusOnMount={ focusOnMount.current }\n\t\t\tonClose={ stopAddingLink }\n\t\t\tonFocusOutside={ () => stopAddingLink( false ) }\n\t\t\tplacement=\"bottom\"\n\t\t\toffset={ 10 }\n\t\t\tshift\n\t\t>\n\t\t\t<LinkControl\n\t\t\t\tvalue={ linkValue }\n\t\t\t\tonChange={ onChangeLink }\n\t\t\t\tonRemove={ removeLink }\n\t\t\t\tforceIsEditingLink={ addingLink }\n\t\t\t\thasRichPreviews\n\t\t\t\tcreateSuggestion={ createPageEntity && handleCreate }\n\t\t\t\twithCreateSuggestion={ userCanCreatePages }\n\t\t\t\tcreateSuggestionButtonText={ createButtonText }\n\t\t\t\thasTextControl\n\t\t\t\tsettings={ LINK_SETTINGS }\n\t\t\t\tshowInitialSuggestions={ true }\n\t\t\t\tsuggestionsQuery={ {\n\t\t\t\t\t// always show Pages as initial suggestions\n\t\t\t\t\tinitialSuggestionsSearchOptions: {\n\t\t\t\t\t\ttype: 'post',\n\t\t\t\t\t\tsubtype: 'page',\n\t\t\t\t\t\tperPage: 20,\n\t\t\t\t\t},\n\t\t\t\t} }\n\t\t\t/>\n\t\t</Popover>\n\t);\n}\n\nfunction getRichTextValueFromSelection( value, isActive ) {\n\t// Default to the selection ranges on the RichTextValue object.\n\tlet textStart = value.start;\n\tlet textEnd = value.end;\n\n\t// If the format is currently active then the rich text value\n\t// should always be taken from the bounds of the active format\n\t// and not the selected text.\n\tif ( isActive ) {\n\t\tconst boundary = getFormatBoundary( value, {\n\t\t\ttype: 'core/link',\n\t\t} );\n\n\t\ttextStart = boundary.start;\n\n\t\t// Text *selection* always extends +1 beyond the edge of the format.\n\t\t// We account for that here.\n\t\ttextEnd = boundary.end + 1;\n\t}\n\n\t// Get a RichTextValue containing the selected text content.\n\treturn slice( value, textStart, textEnd );\n}\n\nexport default InlineLinkUI;\n"],"mappings":";AAAA;AACA;AACA;AACA,SAASA,OAAO,EAAEC,MAAM,EAAEC,wBAAwB,QAAQ,oBAAoB;AAC9E,SAASC,EAAE,EAAEC,OAAO,QAAQ,iBAAiB;AAC7C,SAASC,KAAK,QAAQ,iBAAiB;AACvC,SAASC,OAAO,QAAQ,uBAAuB;AAC/C,SAASC,WAAW,QAAQ,gBAAgB;AAC5C,SACCC,MAAM,EACNC,MAAM,EACNC,WAAW,EACXC,WAAW,EACXC,YAAY,EACZC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,MAAM,EACNC,SAAS,QACH,sBAAsB;AAC7B,SACCC,yBAAyB,IAAIC,WAAW,EACxCC,KAAK,IAAIC,gBAAgB,EACzBC,eAAe,QACT,yBAAyB;AAChC,SAASC,SAAS,QAAQ,iBAAiB;;AAE3C;AACA;AACA;AACA,SAASC,gBAAgB,EAAEC,WAAW,EAAEC,iBAAiB,QAAQ,SAAS;AAC1E,SAASC,IAAI,IAAIC,QAAQ,QAAQ,SAAS;AAE1C,MAAMC,aAAa,GAAG,CACrB,GAAGV,WAAW,CAACW,qBAAqB,EACpC;EACCC,EAAE,EAAE,UAAU;EACdC,KAAK,EAAE7B,EAAE,CAAE,kBAAmB;AAC/B,CAAC,CACD;AAED,SAAS8B,YAAYA,CAAE;EACtBC,QAAQ;EACRC,gBAAgB;EAChBC,UAAU;EACVC,KAAK;EACLC,QAAQ;EACRC,cAAc;EACdC;AACD,CAAC,EAAG;EACH,MAAMC,iBAAiB,GAAGC,6BAA6B,CAAEL,KAAK,EAAEH,QAAS,CAAC;;EAE1E;EACA,MAAMS,YAAY,GAAGF,iBAAiB,CAACG,IAAI;EAE3C,MAAM;IAAEC,gBAAgB;IAAEC;EAAmB,CAAC,GAAGvB,SAAS,CAAIwB,MAAM,IAAM;IACzE,MAAM;MAAEC;IAAY,CAAC,GAAGD,MAAM,CAAE1B,gBAAiB,CAAC;IAClD,MAAM4B,SAAS,GAAGD,WAAW,CAAC,CAAC;IAE/B,OAAO;MACNH,gBAAgB,EAAEI,SAAS,CAACC,8BAA8B;MAC1DJ,kBAAkB,EAAEG,SAAS,CAACE;IAC/B,CAAC;EACF,CAAC,EAAE,EAAG,CAAC;EAEP,MAAMC,SAAS,GAAGpD,OAAO,CACxB,OAAQ;IACPqD,GAAG,EAAElB,gBAAgB,CAACkB,GAAG;IACzBC,IAAI,EAAEnB,gBAAgB,CAACmB,IAAI;IAC3BvB,EAAE,EAAEI,gBAAgB,CAACJ,EAAE;IACvBwB,aAAa,EAAEpB,gBAAgB,CAACqB,MAAM,KAAK,QAAQ;IACnDC,QAAQ,EAAEtB,gBAAgB,CAACuB,GAAG,EAAEC,QAAQ,CAAE,UAAW,CAAC;IACtD3B,KAAK,EAAEW;EACR,CAAC,CAAE,EACH,CACCR,gBAAgB,CAACJ,EAAE,EACnBI,gBAAgB,CAACuB,GAAG,EACpBvB,gBAAgB,CAACqB,MAAM,EACvBrB,gBAAgB,CAACmB,IAAI,EACrBnB,gBAAgB,CAACkB,GAAG,EACpBV,YAAY,CAEd,CAAC;EAED,SAASiB,UAAUA,CAAA,EAAG;IACrB,MAAMC,QAAQ,GAAGjD,YAAY,CAAEyB,KAAK,EAAE,WAAY,CAAC;IACnDC,QAAQ,CAAEuB,QAAS,CAAC;IACpBtB,cAAc,CAAC,CAAC;IAChBlC,KAAK,CAAEF,EAAE,CAAE,eAAgB,CAAC,EAAE,WAAY,CAAC;EAC5C;EAEA,SAAS2D,YAAYA,CAAEC,SAAS,EAAG;IAClC,MAAMC,OAAO,GAAGZ,SAAS,EAAEC,GAAG;IAC9B,MAAMY,SAAS,GAAG,CAAED,OAAO;;IAE3B;IACAD,SAAS,GAAG;MACX,GAAGX,SAAS;MACZ,GAAGW;IACJ,CAAC;IAED,MAAMG,MAAM,GAAG3D,WAAW,CAAEwD,SAAS,CAACV,GAAI,CAAC;IAC3C,MAAMc,UAAU,GAAG3C,gBAAgB,CAAE;MACpC6B,GAAG,EAAEa,MAAM;MACXZ,IAAI,EAAES,SAAS,CAACT,IAAI;MACpBvB,EAAE,EACDgC,SAAS,CAAChC,EAAE,KAAKqC,SAAS,IAAIL,SAAS,CAAChC,EAAE,KAAK,IAAI,GAChDsC,MAAM,CAAEN,SAAS,CAAChC,EAAG,CAAC,GACtBqC,SAAS;MACbE,gBAAgB,EAAEP,SAAS,CAACR,aAAa;MACzCE,QAAQ,EAAEM,SAAS,CAACN;IACrB,CAAE,CAAC;IAEH,MAAMc,OAAO,GAAGR,SAAS,CAAC/B,KAAK,IAAIkC,MAAM;IAEzC,IAAKxD,WAAW,CAAE2B,KAAM,CAAC,IAAI,CAAEH,QAAQ,EAAG;MACzC;MACA,MAAMsC,QAAQ,GAAG7D,WAAW,CAC3BH,MAAM,CAAE;QAAEoC,IAAI,EAAE2B;MAAQ,CAAE,CAAC,EAC3BJ,UAAU,EACV,CAAC,EACDI,OAAO,CAACE,MACT,CAAC;MACDnC,QAAQ,CAAE7B,MAAM,CAAE4B,KAAK,EAAEmC,QAAS,CAAE,CAAC;IACtC,CAAC,MAAM;MACN;MACA,IAAIX,QAAQ;MAEZ,IAAKU,OAAO,KAAK5B,YAAY,EAAG;QAC/B;QACAkB,QAAQ,GAAGlD,WAAW,CAAE0B,KAAK,EAAE8B,UAAW,CAAC;MAC5C,CAAC,MAAM;QACN;QACA;QACAN,QAAQ,GAAGrD,MAAM,CAAE;UAAEoC,IAAI,EAAE2B;QAAQ,CAAE,CAAC;;QAEtC;QACAV,QAAQ,GAAGlD,WAAW,CACrBkD,QAAQ,EACRM,UAAU,EACV,CAAC,EACDI,OAAO,CAACE,MACT,CAAC;;QAED;QACA,MAAMC,QAAQ,GAAGhD,iBAAiB,CAAEW,KAAK,EAAE;UAC1CiB,IAAI,EAAE;QACP,CAAE,CAAC;;QAEH;QACA;QACA;QACA;QACA;QACA,MAAM,CAAEqB,SAAS,EAAEC,QAAQ,CAAE,GAAG7D,KAAK,CACpCsB,KAAK,EACLqC,QAAQ,CAACG,KAAK,EACdH,QAAQ,CAACG,KACV,CAAC;;QAED;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,MAAMC,WAAW,GAAGhE,OAAO,CAAE8D,QAAQ,EAAEjC,YAAY,EAAEkB,QAAS,CAAC;QAE/DA,QAAQ,GAAG7C,MAAM,CAAE2D,SAAS,EAAEG,WAAY,CAAC;MAC5C;MAEAxC,QAAQ,CAAEuB,QAAS,CAAC;IACrB;;IAEA;IACA;IACA;IACA;IACA,IAAK,CAAEI,SAAS,EAAG;MAClB,MAAMc,qBAAqB,GAAG,IAAI;MAClCxC,cAAc,CAAEwC,qBAAsB,CAAC;IACxC;IAEA,IAAK,CAAEtD,WAAW,CAAEyC,MAAO,CAAC,EAAG;MAC9B7D,KAAK,CACJF,EAAE,CACD,0EACD,CAAC,EACD,WACD,CAAC;IACF,CAAC,MAAM,IAAK+B,QAAQ,EAAG;MACtB7B,KAAK,CAAEF,EAAE,CAAE,cAAe,CAAC,EAAE,WAAY,CAAC;IAC3C,CAAC,MAAM;MACNE,KAAK,CAAEF,EAAE,CAAE,gBAAiB,CAAC,EAAE,WAAY,CAAC;IAC7C;EACD;EAEA,MAAM6E,aAAa,GAAG/D,SAAS,CAAE;IAChCgE,sBAAsB,EAAEzC,UAAU,CAAC0C,OAAO;IAC1CtD;EACD,CAAE,CAAC;;EAEH;EACA;EACA;EACA;EACA;EACA;EACA,MAAMuD,UAAU,GAAG7D,eAAe,CAAE0D,aAAa,CAACI,qBAAqB,CAAC,CAAE,CAAC;EAC3EJ,aAAa,CAACI,qBAAqB,GAAG,MAAMD,UAAU;;EAEtD;EACA;EACA;EACA;EACA;EACA;EACA,MAAME,YAAY,GAAGpF,MAAM,CAAEmC,UAAU,GAAG,cAAc,GAAG,KAAM,CAAC;EAElE,eAAekD,YAAYA,CAAEC,SAAS,EAAG;IACxC,MAAMC,IAAI,GAAG,MAAM3C,gBAAgB,CAAE;MACpCb,KAAK,EAAEuD,SAAS;MAChBE,MAAM,EAAE;IACT,CAAE,CAAC;IAEH,OAAO;MACN1D,EAAE,EAAEyD,IAAI,CAACzD,EAAE;MACXuB,IAAI,EAAEkC,IAAI,CAAClC,IAAI;MACftB,KAAK,EAAEwD,IAAI,CAACxD,KAAK,CAAC0D,QAAQ;MAC1BrC,GAAG,EAAEmC,IAAI,CAAC7D,IAAI;MACdgE,IAAI,EAAE;IACP,CAAC;EACF;EAEA,SAASC,gBAAgBA,CAAEC,UAAU,EAAG;IACvC,OAAO3F,wBAAwB,CAC9BE,OAAO,EACN;IACAD,EAAE,CAAE,8BAA+B,CAAC,EACpC0F,UACD,CAAC,EACD;MAAEC,IAAI,EAAEC,aAAA,aAAO;IAAE,CAClB,CAAC;EACF;EAEA,OACCA,aAAA,CAACzF,OAAO;IACP0F,MAAM,EAAGhB,aAAe;IACxBK,YAAY,EAAGA,YAAY,CAACH,OAAS;IACrCe,OAAO,EAAG1D,cAAgB;IAC1B2D,cAAc,EAAGA,CAAA,KAAM3D,cAAc,CAAE,KAAM,CAAG;IAChD4D,SAAS,EAAC,QAAQ;IAClBC,MAAM,EAAG,EAAI;IACbC,KAAK;EAAA,GAELN,aAAA,CAAC5E,WAAW;IACXkB,KAAK,EAAGe,SAAW;IACnBd,QAAQ,EAAGwB,YAAc;IACzBwC,QAAQ,EAAG1C,UAAY;IACvB2C,kBAAkB,EAAGnE,UAAY;IACjCoE,eAAe;IACfC,gBAAgB,EAAG5D,gBAAgB,IAAIyC,YAAc;IACrDoB,oBAAoB,EAAG5D,kBAAoB;IAC3C6D,0BAA0B,EAAGf,gBAAkB;IAC/CgB,cAAc;IACdhF,QAAQ,EAAGC,aAAe;IAC1BgF,sBAAsB,EAAG,IAAM;IAC/BC,gBAAgB,EAAG;MAClB;MACAC,+BAA+B,EAAE;QAChCzD,IAAI,EAAE,MAAM;QACZ0D,OAAO,EAAE,MAAM;QACfC,OAAO,EAAE;MACV;IACD;EAAG,CACH,CACO,CAAC;AAEZ;AAEA,SAASvE,6BAA6BA,CAAEL,KAAK,EAAEH,QAAQ,EAAG;EACzD;EACA,IAAIgF,SAAS,GAAG7E,KAAK,CAACwC,KAAK;EAC3B,IAAIsC,OAAO,GAAG9E,KAAK,CAAC+E,GAAG;;EAEvB;EACA;EACA;EACA,IAAKlF,QAAQ,EAAG;IACf,MAAMwC,QAAQ,GAAGhD,iBAAiB,CAAEW,KAAK,EAAE;MAC1CiB,IAAI,EAAE;IACP,CAAE,CAAC;IAEH4D,SAAS,GAAGxC,QAAQ,CAACG,KAAK;;IAE1B;IACA;IACAsC,OAAO,GAAGzC,QAAQ,CAAC0C,GAAG,GAAG,CAAC;EAC3B;;EAEA;EACA,OAAOvG,KAAK,CAAEwB,KAAK,EAAE6E,SAAS,EAAEC,OAAQ,CAAC;AAC1C;AAEA,eAAelF,YAAY"}
1
+ {"version":3,"names":["useMemo","createInterpolateElement","__","sprintf","speak","Popover","prependHTTP","create","insert","isCollapsed","applyFormat","removeFormat","slice","replace","split","concat","useAnchor","__experimentalLinkControl","LinkControl","store","blockEditorStore","useCachedTruthy","useSelect","createLinkFormat","isValidHref","getFormatBoundary","link","settings","LINK_SETTINGS","DEFAULT_LINK_SETTINGS","id","title","InlineLinkUI","isActive","activeAttributes","value","onChange","onFocusOutside","stopAddingLink","contentRef","richLinkTextValue","getRichTextValueFromSelection","richTextText","text","createPageEntity","userCanCreatePages","select","getSettings","_settings","__experimentalCreatePageEntity","__experimentalUserCanCreatePages","linkValue","url","type","opensInNewTab","target","nofollow","rel","includes","removeLink","newValue","onChangeLink","nextValue","hasLink","isNewLink","newUrl","linkFormat","undefined","String","opensInNewWindow","newText","inserted","start","end","length","boundary","valBefore","valAfter","newValAfter","popoverAnchor","editableContentElement","current","cachedRect","getBoundingClientRect","handleCreate","pageTitle","page","status","rendered","kind","createButtonText","searchTerm","mark","createElement","anchor","onClose","placement","offset","shift","onRemove","hasRichPreviews","createSuggestion","withCreateSuggestion","createSuggestionButtonText","hasTextControl","showInitialSuggestions","suggestionsQuery","initialSuggestionsSearchOptions","subtype","perPage","textStart","textEnd"],"sources":["@wordpress/format-library/src/link/inline.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { useMemo, createInterpolateElement } from '@wordpress/element';\nimport { __, sprintf } from '@wordpress/i18n';\nimport { speak } from '@wordpress/a11y';\nimport { Popover } from '@wordpress/components';\nimport { prependHTTP } from '@wordpress/url';\nimport {\n\tcreate,\n\tinsert,\n\tisCollapsed,\n\tapplyFormat,\n\tremoveFormat,\n\tslice,\n\treplace,\n\tsplit,\n\tconcat,\n\tuseAnchor,\n} from '@wordpress/rich-text';\nimport {\n\t__experimentalLinkControl as LinkControl,\n\tstore as blockEditorStore,\n\tuseCachedTruthy,\n} from '@wordpress/block-editor';\nimport { useSelect } from '@wordpress/data';\n\n/**\n * Internal dependencies\n */\nimport { createLinkFormat, isValidHref, getFormatBoundary } from './utils';\nimport { link as settings } from './index';\n\nconst LINK_SETTINGS = [\n\t...LinkControl.DEFAULT_LINK_SETTINGS,\n\t{\n\t\tid: 'nofollow',\n\t\ttitle: __( 'Mark as nofollow' ),\n\t},\n];\n\nfunction InlineLinkUI( {\n\tisActive,\n\tactiveAttributes,\n\tvalue,\n\tonChange,\n\tonFocusOutside,\n\tstopAddingLink,\n\tcontentRef,\n} ) {\n\tconst richLinkTextValue = getRichTextValueFromSelection( value, isActive );\n\n\t// Get the text content minus any HTML tags.\n\tconst richTextText = richLinkTextValue.text;\n\n\tconst { createPageEntity, userCanCreatePages } = useSelect( ( select ) => {\n\t\tconst { getSettings } = select( blockEditorStore );\n\t\tconst _settings = getSettings();\n\n\t\treturn {\n\t\t\tcreatePageEntity: _settings.__experimentalCreatePageEntity,\n\t\t\tuserCanCreatePages: _settings.__experimentalUserCanCreatePages,\n\t\t};\n\t}, [] );\n\n\tconst linkValue = useMemo(\n\t\t() => ( {\n\t\t\turl: activeAttributes.url,\n\t\t\ttype: activeAttributes.type,\n\t\t\tid: activeAttributes.id,\n\t\t\topensInNewTab: activeAttributes.target === '_blank',\n\t\t\tnofollow: activeAttributes.rel?.includes( 'nofollow' ),\n\t\t\ttitle: richTextText,\n\t\t} ),\n\t\t[\n\t\t\tactiveAttributes.id,\n\t\t\tactiveAttributes.rel,\n\t\t\tactiveAttributes.target,\n\t\t\tactiveAttributes.type,\n\t\t\tactiveAttributes.url,\n\t\t\trichTextText,\n\t\t]\n\t);\n\n\tfunction removeLink() {\n\t\tconst newValue = removeFormat( value, 'core/link' );\n\t\tonChange( newValue );\n\t\tstopAddingLink();\n\t\tspeak( __( 'Link removed.' ), 'assertive' );\n\t}\n\n\tfunction onChangeLink( nextValue ) {\n\t\tconst hasLink = linkValue?.url;\n\t\tconst isNewLink = ! hasLink;\n\n\t\t// Merge the next value with the current link value.\n\t\tnextValue = {\n\t\t\t...linkValue,\n\t\t\t...nextValue,\n\t\t};\n\n\t\tconst newUrl = prependHTTP( nextValue.url );\n\t\tconst linkFormat = createLinkFormat( {\n\t\t\turl: newUrl,\n\t\t\ttype: nextValue.type,\n\t\t\tid:\n\t\t\t\tnextValue.id !== undefined && nextValue.id !== null\n\t\t\t\t\t? String( nextValue.id )\n\t\t\t\t\t: undefined,\n\t\t\topensInNewWindow: nextValue.opensInNewTab,\n\t\t\tnofollow: nextValue.nofollow,\n\t\t} );\n\n\t\tconst newText = nextValue.title || newUrl;\n\n\t\t// Scenario: we have any active text selection or an active format.\n\t\tlet newValue;\n\t\tif ( isCollapsed( value ) && ! isActive ) {\n\t\t\t// Scenario: we don't have any actively selected text or formats.\n\t\t\tconst inserted = insert( value, newText );\n\n\t\t\tnewValue = applyFormat(\n\t\t\t\tinserted,\n\t\t\t\tlinkFormat,\n\t\t\t\tvalue.start,\n\t\t\t\tvalue.end + newText.length\n\t\t\t);\n\t\t} else if ( newText === richTextText ) {\n\t\t\tnewValue = applyFormat( value, linkFormat );\n\t\t} else {\n\t\t\t// Scenario: Editing an existing link.\n\n\t\t\t// Create new RichText value for the new text in order that we\n\t\t\t// can apply formats to it.\n\t\t\tnewValue = create( { text: newText } );\n\t\t\t// Apply the new Link format to this new text value.\n\t\t\tnewValue = applyFormat( newValue, linkFormat, 0, newText.length );\n\n\t\t\t// Get the boundaries of the active link format.\n\t\t\tconst boundary = getFormatBoundary( value, {\n\t\t\t\ttype: 'core/link',\n\t\t\t} );\n\n\t\t\t// Split the value at the start of the active link format.\n\t\t\t// Passing \"start\" as the 3rd parameter is required to ensure\n\t\t\t// the second half of the split value is split at the format's\n\t\t\t// start boundary and avoids relying on the value's \"end\" property\n\t\t\t// which may not correspond correctly.\n\t\t\tconst [ valBefore, valAfter ] = split(\n\t\t\t\tvalue,\n\t\t\t\tboundary.start,\n\t\t\t\tboundary.start\n\t\t\t);\n\n\t\t\t// Update the original (full) RichTextValue replacing the\n\t\t\t// target text with the *new* RichTextValue containing:\n\t\t\t// 1. The new text content.\n\t\t\t// 2. The new link format.\n\t\t\t// As \"replace\" will operate on the first match only, it is\n\t\t\t// run only against the second half of the value which was\n\t\t\t// split at the active format's boundary. This avoids a bug\n\t\t\t// with incorrectly targetted replacements.\n\t\t\t// See: https://github.com/WordPress/gutenberg/issues/41771.\n\t\t\t// Note original formats will be lost when applying this change.\n\t\t\t// That is expected behaviour.\n\t\t\t// See: https://github.com/WordPress/gutenberg/pull/33849#issuecomment-936134179.\n\t\t\tconst newValAfter = replace( valAfter, richTextText, newValue );\n\n\t\t\tnewValue = concat( valBefore, newValAfter );\n\t\t}\n\n\t\tonChange( newValue );\n\n\t\t// Focus should only be returned to the rich text on submit if this link is not\n\t\t// being created for the first time. If it is then focus should remain within the\n\t\t// Link UI because it should remain open for the user to modify the link they have\n\t\t// just created.\n\t\tif ( ! isNewLink ) {\n\t\t\tstopAddingLink();\n\t\t}\n\n\t\tif ( ! isValidHref( newUrl ) ) {\n\t\t\tspeak(\n\t\t\t\t__(\n\t\t\t\t\t'Warning: the link has been inserted but may have errors. Please test it.'\n\t\t\t\t),\n\t\t\t\t'assertive'\n\t\t\t);\n\t\t} else if ( isActive ) {\n\t\t\tspeak( __( 'Link edited.' ), 'assertive' );\n\t\t} else {\n\t\t\tspeak( __( 'Link inserted.' ), 'assertive' );\n\t\t}\n\t}\n\n\tconst popoverAnchor = useAnchor( {\n\t\teditableContentElement: contentRef.current,\n\t\tsettings,\n\t} );\n\n\t// As you change the link by interacting with the Link UI\n\t// the return value of document.getSelection jumps to the field you're editing,\n\t// not the highlighted text. Given that useAnchor uses document.getSelection,\n\t// it will return null, since it can't find the <mark> element within the Link UI.\n\t// This caches the last truthy value of the selection anchor reference.\n\t// This ensures the Popover is positioned correctly on initial submission of the link.\n\tconst cachedRect = useCachedTruthy( popoverAnchor.getBoundingClientRect() );\n\n\t// If the link is not active (i.e. it is a new link) then we need to\n\t// override the getBoundingClientRect method on the anchor element\n\t// to return the cached value of the selection represented by the text\n\t// that the user selected to be linked.\n\t// If the link is active (i.e. it is an existing link) then we allow\n\t// the default behaviour of the popover anchor to be used. This will get\n\t// the anchor based on the `<a>` element in the rich text.\n\tif ( ! isActive ) {\n\t\tpopoverAnchor.getBoundingClientRect = () => cachedRect;\n\t}\n\n\tasync function handleCreate( pageTitle ) {\n\t\tconst page = await createPageEntity( {\n\t\t\ttitle: pageTitle,\n\t\t\tstatus: 'draft',\n\t\t} );\n\n\t\treturn {\n\t\t\tid: page.id,\n\t\t\ttype: page.type,\n\t\t\ttitle: page.title.rendered,\n\t\t\turl: page.link,\n\t\t\tkind: 'post-type',\n\t\t};\n\t}\n\n\tfunction createButtonText( searchTerm ) {\n\t\treturn createInterpolateElement(\n\t\t\tsprintf(\n\t\t\t\t/* translators: %s: search term. */\n\t\t\t\t__( 'Create page: <mark>%s</mark>' ),\n\t\t\t\tsearchTerm\n\t\t\t),\n\t\t\t{ mark: <mark /> }\n\t\t);\n\t}\n\n\treturn (\n\t\t<Popover\n\t\t\tanchor={ popoverAnchor }\n\t\t\tonClose={ stopAddingLink }\n\t\t\tonFocusOutside={ onFocusOutside }\n\t\t\tplacement=\"bottom\"\n\t\t\toffset={ 10 }\n\t\t\tshift\n\t\t>\n\t\t\t<LinkControl\n\t\t\t\tvalue={ linkValue }\n\t\t\t\tonChange={ onChangeLink }\n\t\t\t\tonRemove={ removeLink }\n\t\t\t\thasRichPreviews\n\t\t\t\tcreateSuggestion={ createPageEntity && handleCreate }\n\t\t\t\twithCreateSuggestion={ userCanCreatePages }\n\t\t\t\tcreateSuggestionButtonText={ createButtonText }\n\t\t\t\thasTextControl\n\t\t\t\tsettings={ LINK_SETTINGS }\n\t\t\t\tshowInitialSuggestions={ true }\n\t\t\t\tsuggestionsQuery={ {\n\t\t\t\t\t// always show Pages as initial suggestions\n\t\t\t\t\tinitialSuggestionsSearchOptions: {\n\t\t\t\t\t\ttype: 'post',\n\t\t\t\t\t\tsubtype: 'page',\n\t\t\t\t\t\tperPage: 20,\n\t\t\t\t\t},\n\t\t\t\t} }\n\t\t\t/>\n\t\t</Popover>\n\t);\n}\n\nfunction getRichTextValueFromSelection( value, isActive ) {\n\t// Default to the selection ranges on the RichTextValue object.\n\tlet textStart = value.start;\n\tlet textEnd = value.end;\n\n\t// If the format is currently active then the rich text value\n\t// should always be taken from the bounds of the active format\n\t// and not the selected text.\n\tif ( isActive ) {\n\t\tconst boundary = getFormatBoundary( value, {\n\t\t\ttype: 'core/link',\n\t\t} );\n\n\t\ttextStart = boundary.start;\n\n\t\t// Text *selection* always extends +1 beyond the edge of the format.\n\t\t// We account for that here.\n\t\ttextEnd = boundary.end + 1;\n\t}\n\n\t// Get a RichTextValue containing the selected text content.\n\treturn slice( value, textStart, textEnd );\n}\n\nexport default InlineLinkUI;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/format-library",
3
- "version": "4.27.1",
3
+ "version": "4.28.0",
4
4
  "description": "Format library for the WordPress editor.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -26,18 +26,18 @@
26
26
  "react-native": "src/index",
27
27
  "dependencies": {
28
28
  "@babel/runtime": "^7.16.0",
29
- "@wordpress/a11y": "^3.50.0",
30
- "@wordpress/block-editor": "^12.18.1",
31
- "@wordpress/components": "^25.16.0",
32
- "@wordpress/compose": "^6.27.0",
33
- "@wordpress/data": "^9.20.0",
34
- "@wordpress/element": "^5.27.0",
35
- "@wordpress/html-entities": "^3.50.0",
36
- "@wordpress/i18n": "^4.50.0",
37
- "@wordpress/icons": "^9.41.0",
38
- "@wordpress/private-apis": "^0.32.0",
39
- "@wordpress/rich-text": "^6.27.0",
40
- "@wordpress/url": "^3.51.0"
29
+ "@wordpress/a11y": "^3.51.0",
30
+ "@wordpress/block-editor": "^12.19.0",
31
+ "@wordpress/components": "^26.0.0",
32
+ "@wordpress/compose": "^6.28.0",
33
+ "@wordpress/data": "^9.21.0",
34
+ "@wordpress/element": "^5.28.0",
35
+ "@wordpress/html-entities": "^3.51.0",
36
+ "@wordpress/i18n": "^4.51.0",
37
+ "@wordpress/icons": "^9.42.0",
38
+ "@wordpress/private-apis": "^0.33.0",
39
+ "@wordpress/rich-text": "^6.28.0",
40
+ "@wordpress/url": "^3.52.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "react": "^18.0.0",
@@ -46,5 +46,5 @@
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  },
49
- "gitHead": "c2f6d07ffab7d68b9ac3c423ce7e11af91f6e45e"
49
+ "gitHead": "eb796371e9630636a4a8837033807b0c4a06ed67"
50
50
  }
package/src/link/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { __ } from '@wordpress/i18n';
5
- import { useState } from '@wordpress/element';
5
+ import { useState, useLayoutEffect } from '@wordpress/element';
6
6
  import {
7
7
  getTextContent,
8
8
  applyFormat,
@@ -18,7 +18,7 @@ import {
18
18
  RichTextShortcut,
19
19
  } from '@wordpress/block-editor';
20
20
  import { decodeEntities } from '@wordpress/html-entities';
21
- import { link as linkIcon, linkOff } from '@wordpress/icons';
21
+ import { link as linkIcon } from '@wordpress/icons';
22
22
  import { speak } from '@wordpress/a11y';
23
23
 
24
24
  /**
@@ -39,18 +39,58 @@ function Edit( {
39
39
  contentRef,
40
40
  } ) {
41
41
  const [ addingLink, setAddingLink ] = useState( false );
42
+ // We only need to store the button element that opened the popover. We can ignore the other states, as they will be handled by the onFocus prop to return to the rich text field.
43
+ const [ openedBy, setOpenedBy ] = useState( null );
42
44
 
43
- function addLink() {
45
+ useLayoutEffect( () => {
46
+ const editableContentElement = contentRef.current;
47
+ if ( ! editableContentElement ) {
48
+ return;
49
+ }
50
+
51
+ // Close the Link popover if there is no active selection
52
+ // after the link was added - this can happen if the user
53
+ // adds a link without any text selected.
54
+ // We assume that if there is no active selection after
55
+ // link insertion there are no active formats.
56
+ if ( ! value.activeFormats ) {
57
+ editableContentElement.focus();
58
+ setAddingLink( false );
59
+ return;
60
+ }
61
+
62
+ function handleClick( event ) {
63
+ // There is a situation whereby there is an existing link in the rich text
64
+ // and the user clicks on the leftmost edge of that link and fails to activate
65
+ // the link format, but the click event still fires on the `<a>` element.
66
+ // This causes the `addingLink` state to be set to `true` and the link UI
67
+ // to be rendered in "creating" mode. We need to check isActive to see if
68
+ // we have an active link format.
69
+ if ( event.target.tagName !== 'A' || ! isActive ) {
70
+ return;
71
+ }
72
+
73
+ setAddingLink( true );
74
+ }
75
+
76
+ editableContentElement.addEventListener( 'click', handleClick );
77
+
78
+ return () => {
79
+ editableContentElement.removeEventListener( 'click', handleClick );
80
+ };
81
+ }, [ contentRef, isActive, addingLink, value ] );
82
+
83
+ function addLink( target ) {
44
84
  const text = getTextContent( slice( value ) );
45
85
 
46
- if ( text && isURL( text ) && isValidHref( text ) ) {
86
+ if ( ! isActive && text && isURL( text ) && isValidHref( text ) ) {
47
87
  onChange(
48
88
  applyFormat( value, {
49
89
  type: name,
50
90
  attributes: { url: text },
51
91
  } )
52
92
  );
53
- } else if ( text && isEmail( text ) ) {
93
+ } else if ( ! isActive && text && isEmail( text ) ) {
54
94
  onChange(
55
95
  applyFormat( value, {
56
96
  type: name,
@@ -58,15 +98,48 @@ function Edit( {
58
98
  } )
59
99
  );
60
100
  } else {
101
+ if ( target ) {
102
+ setOpenedBy( target );
103
+ }
61
104
  setAddingLink( true );
62
105
  }
63
106
  }
64
107
 
65
- function stopAddingLink( returnFocus = true ) {
108
+ /**
109
+ * Runs when the popover is closed via escape keypress, unlinking the selected text,
110
+ * but _not_ on a click outside the popover. onFocusOutside handles that.
111
+ */
112
+ function stopAddingLink() {
113
+ // Don't let the click handler on the toolbar button trigger again.
114
+
115
+ // There are two places for us to return focus to on Escape keypress:
116
+ // 1. The rich text field.
117
+ // 2. The toolbar button.
118
+
119
+ // The toolbar button is the only one we need to handle returning focus to.
120
+ // Otherwise, we rely on the passed in onFocus to return focus to the rich text field.
121
+
122
+ // Close the popover
66
123
  setAddingLink( false );
67
- if ( returnFocus ) {
124
+ // Return focus to the toolbar button or the rich text field
125
+ if ( openedBy?.tagName === 'BUTTON' ) {
126
+ openedBy.focus();
127
+ } else {
68
128
  onFocus();
69
129
  }
130
+ // Remove the openedBy state
131
+ setOpenedBy( null );
132
+ }
133
+
134
+ // Test for this:
135
+ // 1. Click on the link button
136
+ // 2. Click the Options button in the top right of header
137
+ // 3. Focus should be in the dropdown of the Options button
138
+ // 4. Press Escape
139
+ // 5. Focus should be on the Options button
140
+ function onFocusOutside() {
141
+ setAddingLink( false );
142
+ setOpenedBy( null );
70
143
  }
71
144
 
72
145
  function onRemoveFormat() {
@@ -82,36 +155,23 @@ function Edit( {
82
155
  character="k"
83
156
  onUse={ onRemoveFormat }
84
157
  />
85
- { isActive && (
86
- <RichTextToolbarButton
87
- name="link"
88
- icon={ linkOff }
89
- title={ __( 'Unlink' ) }
90
- onClick={ onRemoveFormat }
91
- isActive={ isActive }
92
- shortcutType="primaryShift"
93
- shortcutCharacter="k"
94
- aria-haspopup="true"
95
- aria-expanded={ addingLink || isActive }
96
- />
97
- ) }
98
- { ! isActive && (
99
- <RichTextToolbarButton
100
- name="link"
101
- icon={ linkIcon }
102
- title={ title }
103
- onClick={ addLink }
104
- isActive={ isActive }
105
- shortcutType="primary"
106
- shortcutCharacter="k"
107
- aria-haspopup="true"
108
- aria-expanded={ addingLink || isActive }
109
- />
110
- ) }
111
- { ( addingLink || isActive ) && (
158
+ <RichTextToolbarButton
159
+ name="link"
160
+ icon={ linkIcon }
161
+ title={ isActive ? __( 'Link' ) : title }
162
+ onClick={ ( event ) => {
163
+ addLink( event.currentTarget );
164
+ } }
165
+ isActive={ isActive || addingLink }
166
+ shortcutType="primary"
167
+ shortcutCharacter="k"
168
+ aria-haspopup="true"
169
+ aria-expanded={ addingLink }
170
+ />
171
+ { addingLink && (
112
172
  <InlineLinkUI
113
- addingLink={ addingLink }
114
173
  stopAddingLink={ stopAddingLink }
174
+ onFocusOutside={ onFocusOutside }
115
175
  isActive={ isActive }
116
176
  activeAttributes={ activeAttributes }
117
177
  value={ value }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useMemo, useRef, createInterpolateElement } from '@wordpress/element';
4
+ import { useMemo, createInterpolateElement } from '@wordpress/element';
5
5
  import { __, sprintf } from '@wordpress/i18n';
6
6
  import { speak } from '@wordpress/a11y';
7
7
  import { Popover } from '@wordpress/components';
@@ -42,9 +42,9 @@ const LINK_SETTINGS = [
42
42
  function InlineLinkUI( {
43
43
  isActive,
44
44
  activeAttributes,
45
- addingLink,
46
45
  value,
47
46
  onChange,
47
+ onFocusOutside,
48
48
  stopAddingLink,
49
49
  contentRef,
50
50
  } ) {
@@ -113,78 +113,70 @@ function InlineLinkUI( {
113
113
 
114
114
  const newText = nextValue.title || newUrl;
115
115
 
116
+ // Scenario: we have any active text selection or an active format.
117
+ let newValue;
116
118
  if ( isCollapsed( value ) && ! isActive ) {
117
119
  // Scenario: we don't have any actively selected text or formats.
118
- const toInsert = applyFormat(
119
- create( { text: newText } ),
120
+ const inserted = insert( value, newText );
121
+
122
+ newValue = applyFormat(
123
+ inserted,
120
124
  linkFormat,
121
- 0,
122
- newText.length
125
+ value.start,
126
+ value.end + newText.length
123
127
  );
124
- onChange( insert( value, toInsert ) );
128
+ } else if ( newText === richTextText ) {
129
+ newValue = applyFormat( value, linkFormat );
125
130
  } else {
126
- // Scenario: we have any active text selection or an active format.
127
- let newValue;
128
-
129
- if ( newText === richTextText ) {
130
- // If we're not updating the text then ignore.
131
- newValue = applyFormat( value, linkFormat );
132
- } else {
133
- // Create new RichText value for the new text in order that we
134
- // can apply formats to it.
135
- newValue = create( { text: newText } );
136
-
137
- // Apply the new Link format to this new text value.
138
- newValue = applyFormat(
139
- newValue,
140
- linkFormat,
141
- 0,
142
- newText.length
143
- );
144
-
145
- // Get the boundaries of the active link format.
146
- const boundary = getFormatBoundary( value, {
147
- type: 'core/link',
148
- } );
149
-
150
- // Split the value at the start of the active link format.
151
- // Passing "start" as the 3rd parameter is required to ensure
152
- // the second half of the split value is split at the format's
153
- // start boundary and avoids relying on the value's "end" property
154
- // which may not correspond correctly.
155
- const [ valBefore, valAfter ] = split(
156
- value,
157
- boundary.start,
158
- boundary.start
159
- );
160
-
161
- // Update the original (full) RichTextValue replacing the
162
- // target text with the *new* RichTextValue containing:
163
- // 1. The new text content.
164
- // 2. The new link format.
165
- // As "replace" will operate on the first match only, it is
166
- // run only against the second half of the value which was
167
- // split at the active format's boundary. This avoids a bug
168
- // with incorrectly targetted replacements.
169
- // See: https://github.com/WordPress/gutenberg/issues/41771.
170
- // Note original formats will be lost when applying this change.
171
- // That is expected behaviour.
172
- // See: https://github.com/WordPress/gutenberg/pull/33849#issuecomment-936134179.
173
- const newValAfter = replace( valAfter, richTextText, newValue );
174
-
175
- newValue = concat( valBefore, newValAfter );
176
- }
177
-
178
- onChange( newValue );
131
+ // Scenario: Editing an existing link.
132
+
133
+ // Create new RichText value for the new text in order that we
134
+ // can apply formats to it.
135
+ newValue = create( { text: newText } );
136
+ // Apply the new Link format to this new text value.
137
+ newValue = applyFormat( newValue, linkFormat, 0, newText.length );
138
+
139
+ // Get the boundaries of the active link format.
140
+ const boundary = getFormatBoundary( value, {
141
+ type: 'core/link',
142
+ } );
143
+
144
+ // Split the value at the start of the active link format.
145
+ // Passing "start" as the 3rd parameter is required to ensure
146
+ // the second half of the split value is split at the format's
147
+ // start boundary and avoids relying on the value's "end" property
148
+ // which may not correspond correctly.
149
+ const [ valBefore, valAfter ] = split(
150
+ value,
151
+ boundary.start,
152
+ boundary.start
153
+ );
154
+
155
+ // Update the original (full) RichTextValue replacing the
156
+ // target text with the *new* RichTextValue containing:
157
+ // 1. The new text content.
158
+ // 2. The new link format.
159
+ // As "replace" will operate on the first match only, it is
160
+ // run only against the second half of the value which was
161
+ // split at the active format's boundary. This avoids a bug
162
+ // with incorrectly targetted replacements.
163
+ // See: https://github.com/WordPress/gutenberg/issues/41771.
164
+ // Note original formats will be lost when applying this change.
165
+ // That is expected behaviour.
166
+ // See: https://github.com/WordPress/gutenberg/pull/33849#issuecomment-936134179.
167
+ const newValAfter = replace( valAfter, richTextText, newValue );
168
+
169
+ newValue = concat( valBefore, newValAfter );
179
170
  }
180
171
 
172
+ onChange( newValue );
173
+
181
174
  // Focus should only be returned to the rich text on submit if this link is not
182
175
  // being created for the first time. If it is then focus should remain within the
183
176
  // Link UI because it should remain open for the user to modify the link they have
184
177
  // just created.
185
178
  if ( ! isNewLink ) {
186
- const returnFocusToRichText = true;
187
- stopAddingLink( returnFocusToRichText );
179
+ stopAddingLink();
188
180
  }
189
181
 
190
182
  if ( ! isValidHref( newUrl ) ) {
@@ -213,15 +205,17 @@ function InlineLinkUI( {
213
205
  // This caches the last truthy value of the selection anchor reference.
214
206
  // This ensures the Popover is positioned correctly on initial submission of the link.
215
207
  const cachedRect = useCachedTruthy( popoverAnchor.getBoundingClientRect() );
216
- popoverAnchor.getBoundingClientRect = () => cachedRect;
217
208
 
218
- // Focus should only be moved into the Popover when the Link is being created or edited.
219
- // When the Link is in "preview" mode focus should remain on the rich text because at
220
- // this point the Link dialog is informational only and thus the user should be able to
221
- // continue editing the rich text.
222
- // Ref used because the focusOnMount prop shouldn't evolve during render of a Popover
223
- // otherwise it causes a render of the content.
224
- const focusOnMount = useRef( addingLink ? 'firstElement' : false );
209
+ // If the link is not active (i.e. it is a new link) then we need to
210
+ // override the getBoundingClientRect method on the anchor element
211
+ // to return the cached value of the selection represented by the text
212
+ // that the user selected to be linked.
213
+ // If the link is active (i.e. it is an existing link) then we allow
214
+ // the default behaviour of the popover anchor to be used. This will get
215
+ // the anchor based on the `<a>` element in the rich text.
216
+ if ( ! isActive ) {
217
+ popoverAnchor.getBoundingClientRect = () => cachedRect;
218
+ }
225
219
 
226
220
  async function handleCreate( pageTitle ) {
227
221
  const page = await createPageEntity( {
@@ -252,9 +246,8 @@ function InlineLinkUI( {
252
246
  return (
253
247
  <Popover
254
248
  anchor={ popoverAnchor }
255
- focusOnMount={ focusOnMount.current }
256
249
  onClose={ stopAddingLink }
257
- onFocusOutside={ () => stopAddingLink( false ) }
250
+ onFocusOutside={ onFocusOutside }
258
251
  placement="bottom"
259
252
  offset={ 10 }
260
253
  shift
@@ -263,7 +256,6 @@ function InlineLinkUI( {
263
256
  value={ linkValue }
264
257
  onChange={ onChangeLink }
265
258
  onRemove={ removeLink }
266
- forceIsEditingLink={ addingLink }
267
259
  hasRichPreviews
268
260
  createSuggestion={ createPageEntity && handleCreate }
269
261
  withCreateSuggestion={ userCanCreatePages }