UNPKG

@gechiui/block-editor

Version:
563 lines (368 loc) 17.6 kB
# Link Control Renders a link control. A link control is a controlled input which maintains a value associated with a link (HTML anchor element) and relevant settings for how that link is expected to behave. It is designed to provide a standardized UI for the creation of a link throughout the Editor, see History section at bottom for further background. ## Relationship to `<URLInput>` As of Gutenberg 7.4, `<LinkControl>` became the default link creation interface within the Block Editor, replacing previous disparate uses of `<URLInput>` and standardizing the UI. Nonetheless, it should be remembered that `<LinkControl>` builds **on top of** `<URLInput>` and makes use of it under the hood. The distinction between the two components is perhaps best summarized by the following definitions: - `<URLInput>` - an input for presenting and managing selection behaviors associated with choosing a URL, optionally from a pool of available candidates. - `<LinkControl>` - includes the features of `<URLInput>`, plus additional UI and behaviors to control how this URL applies to the concept of a "link". This includes link "settings" (eg: "opens in new tab", etc) and dynamic, "on the fly" link creation capabilities. ## Search Suggestions When creating links the `LinkControl` component will handle two kinds of input from users: 1. Entity searches - the user may input free-text based search queries for entities retrieved from remote data sources (in the context of GeChiUI these are post-type entities). For example, a user might search for a `Page` they have just created by name (eg: About) and the UI will return a matching result if found. 2. Direct entry - the user may also enter any arbitrary URL-like text. This includes full URLs (https://), URL fragements (eg: `#myinternallink`), `tel` protocol links (eg: `tel: 0800 1234`) and `mailto` protocol links (eg: `mailto: hello@www.gechiui.com`). In addition, `<LinkControl>` also allows for on the fly creation of links based on the **current content of the `<input>` element**. When enabled, a default "Create new" search suggestion is appended to all non-URL-like search results. When this suggestion is selected it will call the `createSuggestion` prop affording the developer the ability to create new links on the fly (the [Navigation Block uses this to allow creation of Pages from within the Block](https://github.com/GeChiUI/gutenberg/pull/19775/files)). See below for more details. ### Data sources By default `LinkControl` utilizes the `__experimentalFetchLinkSuggestions` API from `core/block-editor` in order to retrieve search suggestions for matching post-type entities. By default this provides no functionality and so you must implement and provide this in your own Editor instance ([example](https://github.com/GeChiUI/gutenberg/blob/65c752816f46a9334b84f4801d80dea00ed76fba/packages/editor/src/components/provider/use-block-editor-settings.js#L114-L115)). ## Props ### value - Type: `Object` - Required: No Current link value. A link `value` is composed of a union between the values of default link properties and any custom link `settings`. The resulting default properties of `value` include: - `url` (`string`): Link URL. - `title` (`string`, optional): Link title. - `opensInNewTab` (`boolean`, optional): Whether link should open in a new browser tab. This value is only assigned when not providing a custom `settings` prop. ### settings - Type: `Array` - Required: No - Default: ```js [ { id: 'opensInNewTab', title: 'Open in new tab', }, ]; ``` An array of settings objects associated with a link (for example: a setting to determine whether or not the link opens in a new tab). Each object will be used to render a `ToggleControl` for that setting. To disable settings, pass in an empty array. for example: ```jsx <LinkControl settings={ [] } /> ``` ### onChange - Type: `Function` - Required: No Value change handler, called with the updated value if the user selects a new link or updates settings. ```jsx <LinkControl onChange={ ( nextValue ) => { console.log( `The selected item URL: ${ nextValue.url }.` ); } /> ``` ### showSuggestions - Type: `boolean` - Required: No - Default: `true` Whether to present suggestions when typing the URL. ### showInitialSuggestions - Type: `boolean` - Required: No - Default: `false` Whether to present initial suggestions immediately. ### suggestionsQuery - Type: `Object` - Required: No Controls the query parameters used to search for suggestions. For example, to limit a query to just `Page` types use: ```jsx <LinkControl suggestionQuery={ { type: 'post', subtype: 'page', } } /> ``` ### forceIsEditingLink - Type: `boolean` - Required: No Controls the internal editing state of the component. If passed as either `true` or `false` will respectively show or hide the URL input field. ### createSuggestion - Type: `function` - Required: No - Returns: When called may return either a new `suggestion` directly or a `Promise` which resolves to a new `suggestion`. Used to handle the dynamic creation of a new `suggestion` (and ultimately new link `value`) within the Link UI. When provided, an option is appended to all search results requests which when clicked will call the `createSuggestion` callback (passing the current value of the search `<input>`). This affords the parent component the opportunity to dynamically create a new link `suggestion` (see above). This `suggestion` will then be _automatically_ passed to the `onChange` handler to create **the next link value**. As a result of the above, this prop is often used to allow on the fly creation of new entities (eg: `Posts`, `Pages`) based on the text the user has entered into the link search UI. As an example, the Navigation Block uses `createSuggestion` to create Pages on the fly from within the Block itself. ### onRemove - Type: `Function` - Required: No - Default: null An optional handler, which when passed will trigger the display of an `Unlink` UI within the control. This handler is expected to remove the current `value` of the control thus resetting it back to a default state. The key use case for this is allowing users to remove a link from the control without relying on there being an "unlink" control in the block toolbar. #### Search `suggestion` values A `suggestion` should have the following shape: ```js { id: // uniquely identifies the suggestion. type: // the type of the suggestion (eg: `post`). title: // human-readable label for the suggestion. url: // any string representing a URL value } ``` #### Example ```jsx // Promise example <LinkControl createSuggestion={ async (inputText) => { // Hard coded values. These could be dynamically created by calling out to an API which creates an entity (eg: https://developer.gechiui.com/rest-api/reference/pages/#create-a-page). return { id: 1234, type: 'page', title: inputText, url: '/some-url-here' } }} /> // Non-Promise example <LinkControl createSuggestion={ (inputText) => ( { id: 1234, type: 'page', title: inputText, url: '/some-url-here' } )} /> ``` ### renderControlBottom - Type: `Function` - Required: No - Default: null A render prop that can be used to pass optional controls to be rendered at the bottom of the component. # LinkControlSearchInput The search input used by `LinkControl`. It is a wrapper over `<URLInput />` that caters it to `LinkControl`'s needs. ## Props ### allowDirectEntry - Type: `boolean` - Required: No - Default: `true` The opposite of `noDirectEntry` from LinkControl, refer to an earlier section of this README file for more details. ### children - Type: `Element` - Required: No If passed, children are rendered after the input. ```jsx <LinkControlSearchInput> <div className="block-editor-link-control__search-actions"> <Button type="submit" label={ __( 'Submit' ) } icon={ keyboardReturn } className="block-editor-link-control__search-submit" /> </div> </LinkControlSearchInput> ``` ### className - Type: `string` - Required: No - Default: `null` Passed verbatim to URLInput, refer to it's README.md for more details. ### createSuggestionButtonText - Type: `string` - Required: No The same as in LinkControl, refer to an earlier section of this README file for more details. ### currentLink - Type: `Object` - Required: No - Default: `{}` The same as `value` in LinkControl, refer to an earlier section of this README file for more details. ### fetchSuggestions - Type: `Function` - Required: No Custom search handler for suggestions. If specified, it's passed to `URLInput` as `__experimentalFetchLinkSuggestions`, if not, the default handler is used. Refer to URLInput's README.md for more details about `__experimentalFetchLinkSuggestions` and see the [createSuggestion](#createSuggestion) section of this file to learn more about suggestions. ### onChange - Type: `Function` - Required: No Value change handler passed to the underlying `<URLInput />`. Refer to URLInput's README.md for more details. ### onCreateSuggestion - Type: `Function` - Required: No By default, when there are no matching results, LinkControlSearchInput proposes creating a new page by rendering a suggestion with `{ type: __CREATE__, title: <<User input>> }` properties. This function is called when that suggestion is selected. See the [createSuggestion](#createSuggestion) section of this file to learn more about suggestions. ```jsx <LinkControlSearchInput onCreateSuggestion={( inputValue ) => { createNewPage( inputValue ); }) /> ``` ### onSelect - Type: `Function` - Required: No Suggestion selection handler, called when the user chooses one of the suggested items with `selectedValues` as the argument. ### placeholder - Type: `string` - Required: No Passed verbatim to URLInput, refer to it's README.md for more details. ### renderSuggestions - Type: `Function` - Required: No - Default: `(props) => <LinkControlSearchResults {...props} />` Function used to render search suggestions. It is decorated with extra properties and passed to `URLInput` as `__experimentalRenderSuggestions`. The following properties are provided by URLInput: - buildSuggestionItemProps - handleSuggestionClick - isInitialSuggestions - isLoading - suggestions - selectedSuggestion - suggestionsListProps The following extra properties are provided by LinkControlSearchInput: - currentInputValue - createSuggestionButtonText - handleSuggestionClick - instanceId - suggestionsQuery - withCreateSuggestion See the [createSuggestion](#createSuggestion) section of this file to learn more about suggestions. ```jsx <LinkControlSearchInput renderSuggestions={( { suggestions } ) => { return ( <Popover focusOnMount={ false } position="bottom"> <ul> { suggestions.map( () => ( <li key={ `${ suggestion.id }-${ suggestion.type }` }>{ suggestion.title }</li> ) ) } </ul> </Popover> ); }) /> ``` ```jsx <LinkControlSearchInput renderSuggestions={( suggestionsProps ) => { return ( <Popover focusOnMount={ false } position="bottom"> <LinkControlSearchResults { ...suggestionsProps } /> </Popover> ); }) /> ``` ### showInitialSuggestions - Type: `boolean` - Required: No - Default: `false` The same as in LinkControl, refer to an earlier section of this README file for more details. ### showSuggestions - Type: `boolean` - Required: No - Default: `true` The same as in LinkControl, refer to an earlier section of this README file for more details. ### suggestionsQuery - Type: `Object` - Required: No - Default: `{}` The same as in LinkControl, refer to an earlier section of this README file for more details. ### withCreateSuggestion - Type: `boolean` - Required: No - Default: `true` The same as in LinkControl, refer to an earlier section of this README file for more details. ### value - Type: `string` - Required: No Passed verbatim to URLInput, refer to it's README.md for more details. # LinkControlSearchResults The list of search results used by `LinkControlSearchInput`. ## Props ### buildSuggestionItemProps - Type: `Function` - Required: Yes Function that takes `suggestion` and `index` as arguments, and returns HTML props of the suggestion item. When this component is used with `LinkControlSearchInput`, this property is provided by `URLInput`. ### currentInputValue - Type: `string` - Required: Yes Current value of the related search input, used e.g. for highlighting matching part of the page title. When this component is used with `LinkControlSearchInput`, this property is provided by `LinkControlSearchInput`. ### handleSuggestionClick - Type: `Function` - Required: Yes Called with `suggestion` as the argument, when said suggestion is clicked by the user. When this component is used with `LinkControlSearchInput`, this property is provided by `LinkControlSearchInput`. See the [createSuggestion](#createSuggestion) section of this file to learn more about suggestions. ### instanceId - Type: `string` - Required: Yes Unique ID of parent component, used for the aria-label property. When this component is used with `LinkControlSearchInput`, this property is provided by `LinkControlSearchInput`. ### isLoading - Type: `boolean` - Required: Yes Whether the suggestions are being fetched at the moment. When this component is used with `LinkControlSearchInput`, this property is provided by `URLInput`. ### isInitialSuggestions - Type: `boolean` - Required: No Whether this component was rendered to show initial suggestions (the ones displayed right after mounting, before the user begins interacting with LinkControl). ### selectedSuggestion - Type: `Object` - Required: Yes The suggestions that is currently selected. When this component is used with `LinkControlSearchInput`, this property is provided by `LinkControlSearchInput`. ### suggestions - Type: `Array` - Required: Yes The list of suggestions to render. When this component is used with `LinkControlSearchInput`, this property is provided by `URLInput`. ### suggestionsListProps - Type: `Object` - Required: No List of additional HTML properties passed to the element wrapping the list of suggestions. When this component is used with `LinkControlSearchInput`, this property is provided by `URLInput`. ### createSuggestionButtonText - Type: `string` - Required: No The same as in LinkControl, refer to an earlier section of this README file for more details. ### suggestionsQuery - Type: `Object` - Required: No The same as in LinkControl, refer to an earlier section of this README file for more details. ### withCreateSuggestion - Type: `boolean` - Required: No The same as in LinkControl, refer to an earlier section of this README file for more details. # LinkControlSearchItem A single suggestion rendered by `LinkControlSearchResults`. ## Props ### itemProps - Type: `Object` - Required: No A list of extra HTML properties for the root element rendered by this component. ### isSelected - Type: `boolean` - Required: No - Default: `false` Whether this item represents a selected suggestion. ### isURL - Type: `boolean` - Required: No - Default: `false` Whether this item represents a suggestion referring to a URL (e.g. post, page). ### onClick - Type: `Function` - Required: Yes Click handler, called with click event as the only argument. ### searchTerm - Type: `string` - Required: Yes The search term as specified by the user. Used for highlighting the matching part of the suggestion title. ### shouldShowType - Type: `boolean` - Required: No - Default: `false` If true, type of the suggestion is rendered (e.g. post, tag) ### suggestion - Type: `Object` - Required: Yes The suggestion to render. See the [createSuggestion](#createSuggestion) section of this file to learn more about suggestions. ## History Much of the context for this component can be found in [the original Issue](https://github.com/GeChiUI/gutenberg/issues/17557). Previously iterations of a hyperlink UI existed within the Gutenberg interface but these tended to be highly tailored to their individual use cases and were not standardized, each having their own implementation. These older UIs tended to make use of two existing components: `URLInput` and `URLPopover`. When a requirement was raised to implement a new UI for hyperlink creation, an assessment of these existing components was undertaken and it was determined that they were too opinionated as to be easily refactored to accommodate the new use cases required by the new UI. Attempting to do so would also have meant unavoidable breaking changes to the interface of `URLInput` which would have (most probably) caused breaking changes to ripple across not only the Core codebase, but also that of 3rd party Plugins. As a result, it was agreed that a new component `LinkControl` would be created to realise the new hyperlink creation interface. This new UI would begin life as an experimental component which would consume `URLInput` internally. The API of `URLInput` would be enhanced as required with "experimental" features to facilitate the implementation of the new UI.