Skip to content

Commit

Permalink
Components: Move kebabCase() function from block-editor package and m…
Browse files Browse the repository at this point in the history
…ark it as private API (#56758)

* Components: Move kebabCase() function from block-editor package and mark it as private API

* Update changelog

* Fix native app test

* Try to fix native app test

* Try to fix mobile app test

* Try importing kebabCase directly from components

* Fix another import

* Move changelog entry to Internal section

* Change the argument type of kebabCase function to unknown

* Fix lint error

* Merge duplicate Internal sections

* Remove type info in the JSDoc comment

---------

Co-authored-by: Marin Atanasov <[email protected]>
  • Loading branch information
2 people authored and artemiomorales committed Jan 4, 2024
1 parent d370173 commit 3acb484
Show file tree
Hide file tree
Showing 21 changed files with 214 additions and 161 deletions.
9 changes: 8 additions & 1 deletion packages/block-editor/src/components/colors/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { colord, extend } from 'colord';
import namesPlugin from 'colord/plugins/names';
import a11yPlugin from 'colord/plugins/a11y';

/**
* WordPress dependencies
*/
import { privateApis as componentsPrivateApis } from '@wordpress/components';

/**
* Internal dependencies
*/
import { kebabCase } from '../../utils/object';
import { unlock } from '../../lock-unlock';

extend( [ namesPlugin, a11yPlugin ] );

Expand Down Expand Up @@ -70,6 +75,8 @@ export function getColorClassName( colorContextName, colorSlug ) {
return undefined;
}

const { kebabCase } = unlock( componentsPrivateApis );

return `has-${ kebabCase( colorSlug ) }-${ colorContextName }`;
}

Expand Down
4 changes: 3 additions & 1 deletion packages/block-editor/src/components/colors/with-colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { useMemo, Component } from '@wordpress/element';
import { compose, createHigherOrderComponent } from '@wordpress/compose';
import { privateApis as componentsPrivateApis } from '@wordpress/components';

/**
* Internal dependencies
Expand All @@ -14,7 +15,7 @@ import {
getMostReadableColor,
} from './utils';
import { useSettings } from '../use-settings';
import { kebabCase } from '../../utils/object';
import { unlock } from '../../lock-unlock';

/**
* Capitalizes the first letter in a string.
Expand Down Expand Up @@ -79,6 +80,7 @@ const withEditorColorPalette = () =>
* @return {Component} The component that can be used as a HOC.
*/
function createColorHOC( colorTypes, withColorPalette ) {
const { kebabCase } = unlock( componentsPrivateApis );
const colorMap = colorTypes.reduce( ( colorObject, colorType ) => {
return {
...colorObject,
Expand Down
8 changes: 7 additions & 1 deletion packages/block-editor/src/components/font-sizes/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/**
* WordPress dependencies
*/
import { privateApis as componentsPrivateApis } from '@wordpress/components';

/**
* Internal dependencies
*/
import { kebabCase } from '../../utils/object';
import { unlock } from '../../lock-unlock';

/**
* Returns the font size object based on an array of named font sizes and the namedFontSize and customFontSize values.
Expand Down Expand Up @@ -64,5 +69,6 @@ export function getFontSizeClass( fontSizeSlug ) {
return;
}

const { kebabCase } = unlock( componentsPrivateApis );
return `has-${ kebabCase( fontSizeSlug ) }-font-size`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { useSelect } from '@wordpress/data';
import { useContext, useMemo } from '@wordpress/element';
import { getCSSRules } from '@wordpress/style-engine';
import { privateApis as componentsPrivateApis } from '@wordpress/components';

/**
* Internal dependencies
Expand All @@ -32,12 +33,9 @@ import { getDuotoneFilter } from '../duotone/utils';
import { getGapCSSValue } from '../../hooks/gap';
import { store as blockEditorStore } from '../../store';
import { LAYOUT_DEFINITIONS } from '../../layouts/definitions';
import {
getValueFromObjectPath,
kebabCase,
setImmutably,
} from '../../utils/object';
import { getValueFromObjectPath, setImmutably } from '../../utils/object';
import BlockContext from '../block-context';
import { unlock } from '../../lock-unlock';

// List of block support features that can have their related styles
// generated under their own feature level selector rather than the block's.
Expand Down Expand Up @@ -72,6 +70,8 @@ function compileStyleValue( uncompiledValue ) {
* @return {Array<Object>} An array of style declarations.
*/
function getPresetsDeclarations( blockPresets = {}, mergedSettings ) {
const { kebabCase } = unlock( componentsPrivateApis );

return PRESET_METADATA.reduce(
( declarations, { path, valueKey, valueFunc, cssVarInfix } ) => {
const presetByOrigin = getValueFromObjectPath(
Expand Down Expand Up @@ -116,6 +116,8 @@ function getPresetsDeclarations( blockPresets = {}, mergedSettings ) {
* @return {string} CSS declarations for the preset classes.
*/
function getPresetsClasses( blockSelector = '*', blockPresets = {} ) {
const { kebabCase } = unlock( componentsPrivateApis );

return PRESET_METADATA.reduce(
( declarations, { path, cssVarInfix, classes } ) => {
if ( ! classes ) {
Expand Down Expand Up @@ -180,6 +182,7 @@ function getPresetsSvgFilters( blockPresets = {} ) {
}

function flattenTree( input = {}, prefix, token ) {
const { kebabCase } = unlock( componentsPrivateApis );
let result = [];
Object.keys( input ).forEach( ( key ) => {
const newKey = prefix + kebabCase( key.replace( '/', '-' ) );
Expand Down Expand Up @@ -321,6 +324,7 @@ export function getStylesDeclarations(
tree = {},
isTemplate = true
) {
const { kebabCase } = unlock( componentsPrivateApis );
const isRoot = ROOT_BLOCK_SELECTOR === selector;
const output = Object.entries( STYLE_PROPERTY ).reduce(
(
Expand Down
4 changes: 3 additions & 1 deletion packages/block-editor/src/hooks/font-family.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import { addFilter } from '@wordpress/hooks';
import { hasBlockSupport } from '@wordpress/blocks';
import TokenList from '@wordpress/token-list';
import { privateApis as componentsPrivateApis } from '@wordpress/components';

/**
* Internal dependencies
*/
import { shouldSkipSerialization } from './utils';
import { TYPOGRAPHY_SUPPORT_KEY } from './typography';
import { kebabCase } from '../utils/object';
import { unlock } from '../lock-unlock';

export const FONT_FAMILY_SUPPORT_KEY = 'typography.__experimentalFontFamily';

Expand Down Expand Up @@ -67,6 +68,7 @@ function addSaveProps( props, blockType, attributes ) {

// Use TokenList to dedupe classes.
const classes = new TokenList( props.className );
const { kebabCase } = unlock( componentsPrivateApis );
classes.add( `has-${ kebabCase( attributes?.fontFamily ) }-font-family` );
const newClassName = classes.value;
props.className = newClassName ? newClassName : undefined;
Expand Down
5 changes: 4 additions & 1 deletion packages/block-editor/src/hooks/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ButtonGroup,
ToggleControl,
PanelBody,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';

Expand All @@ -27,8 +28,8 @@ import { useSettings } from '../components/use-settings';
import { getLayoutType, getLayoutTypes } from '../layouts';
import { useBlockEditingMode } from '../components/block-editing-mode';
import { LAYOUT_DEFINITIONS } from '../layouts/definitions';
import { kebabCase } from '../utils/object';
import { useBlockSettings, useStyleOverride } from './utils';
import { unlock } from '../lock-unlock';

const layoutBlockSupportKey = 'layout';

Expand All @@ -48,6 +49,7 @@ function hasLayoutBlockSupport( blockName ) {
* @return { Array } Array of CSS classname strings.
*/
export function useLayoutClasses( blockAttributes = {}, blockName = '' ) {
const { kebabCase } = unlock( componentsPrivateApis );
const rootPaddingAlignment = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
return getSettings().__experimentalFeatures
Expand Down Expand Up @@ -348,6 +350,7 @@ function BlockWithLayoutStyles( { block: BlockListBlock, props } ) {
: layout || defaultBlockLayout || {};
const layoutClasses = useLayoutClasses( attributes, name );

const { kebabCase } = unlock( componentsPrivateApis );
const selectorPrefix = `wp-container-${ kebabCase( name ) }-layout-`;
// Higher specificity to override defaults from theme.json.
const selector = `.${ selectorPrefix }${ id }.${ selectorPrefix }${ id }`;
Expand Down
8 changes: 7 additions & 1 deletion packages/block-editor/src/hooks/use-typography-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { privateApis as componentsPrivateApis } from '@wordpress/components';

/**
* Internal dependencies
*/
Expand All @@ -12,7 +17,7 @@ import {
getTypographyFontSizeValue,
getFluidTypographyOptionsFromSettings,
} from '../components/global-styles/typography-utils';
import { kebabCase } from '../utils/object';
import { unlock } from '../lock-unlock';

/*
* This utility is intended to assist where the serialization of the typography
Expand All @@ -29,6 +34,7 @@ import { kebabCase } from '../utils/object';
* @return {Object} Typography block support derived CSS classes & styles.
*/
export function getTypographyClassesAndStyles( attributes, settings ) {
const { kebabCase } = unlock( componentsPrivateApis );
let typographyStyles = attributes?.style?.typography || {};
const fluidTypographySettings =
getFluidTypographyOptionsFromSettings( settings );
Expand Down
2 changes: 0 additions & 2 deletions packages/block-editor/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as globalStyles from './components/global-styles';
import { ExperimentalBlockEditorProvider } from './components/provider';
import { lock } from './lock-unlock';
import { getRichTextValues } from './components/rich-text/get-rich-text-values';
import { kebabCase } from './utils/object';
import ResizableBoxPopover from './components/resizable-box-popover';
import { ComposedPrivateInserter as PrivateInserter } from './components/inserter';
import { PrivateListView } from './components/list-view';
Expand Down Expand Up @@ -36,7 +35,6 @@ lock( privateApis, {
ExperimentalBlockEditorProvider,
getDuotoneFilter,
getRichTextValues,
kebabCase,
PrivateInserter,
PrivateListView,
ResizableBoxPopover,
Expand Down
2 changes: 0 additions & 2 deletions packages/block-editor/src/private-apis.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/
import * as globalStyles from './components/global-styles';
import { ExperimentalBlockEditorProvider } from './components/provider';
import { kebabCase } from './utils/object';
import { lock } from './lock-unlock';

/**
Expand All @@ -12,6 +11,5 @@ import { lock } from './lock-unlock';
export const privateApis = {};
lock( privateApis, {
...globalStyles,
kebabCase,
ExperimentalBlockEditorProvider,
} );
35 changes: 0 additions & 35 deletions packages/block-editor/src/utils/object.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,3 @@
/**
* External dependencies
*/
import { paramCase } from 'change-case';

/**
* Converts any string to kebab case.
* Backwards compatible with Lodash's `_.kebabCase()`.
* Backwards compatible with `_wp_to_kebab_case()`.
*
* @see https://lodash.com/docs/4.17.15#kebabCase
* @see https://developer.wordpress.org/reference/functions/_wp_to_kebab_case/
*
* @param {string} str String to convert.
* @return {string} Kebab-cased string
*/
export function kebabCase( str ) {
let input = str;
if ( typeof str !== 'string' ) {
input = str?.toString?.() ?? '';
}

// See https://github.com/lodash/lodash/blob/b185fcee26b2133bd071f4aaca14b455c2ed1008/lodash.js#L4970
input = input.replace( /['\u2019]/, '' );

return paramCase( input, {
splitRegexp: [
/(?!(?:1ST|2ND|3RD|[4-9]TH)(?![a-z]))([a-z0-9])([A-Z])/g, // fooBar => foo-bar, 3Bar => 3-bar
/(?!(?:1st|2nd|3rd|[4-9]th)(?![a-z]))([0-9])([a-z])/g, // 3bar => 3-bar
/([A-Za-z])([0-9])/g, // Foo3 => foo-3, foo3 => foo-3
/([A-Z])([A-Z][a-z])/g, // FOOBar => foo-bar
],
} );
}

/**
* Immutably sets a value inside an object. Like `lodash#set`, but returning a
* new object. Treats nullish initial values as empty objects. Clones any
Expand Down
97 changes: 1 addition & 96 deletions packages/block-editor/src/utils/test/object.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,7 @@
/**
* Internal dependencies
*/
import { kebabCase, setImmutably } from '../object';

describe( 'kebabCase', () => {
it( 'separates lowercase letters, followed by uppercase letters', () => {
expect( kebabCase( 'fooBar' ) ).toEqual( 'foo-bar' );
} );

it( 'separates numbers, followed by uppercase letters', () => {
expect( kebabCase( '123FOO' ) ).toEqual( '123-foo' );
} );

it( 'separates numbers, followed by lowercase characters', () => {
expect( kebabCase( '123bar' ) ).toEqual( '123-bar' );
} );

it( 'separates uppercase letters, followed by numbers', () => {
expect( kebabCase( 'FOO123' ) ).toEqual( 'foo-123' );
} );

it( 'separates lowercase letters, followed by numbers', () => {
expect( kebabCase( 'foo123' ) ).toEqual( 'foo-123' );
} );

it( 'separates uppercase groups from capitalized groups', () => {
expect( kebabCase( 'FOOBar' ) ).toEqual( 'foo-bar' );
} );

it( 'removes any non-dash special characters', () => {
expect(
kebabCase( 'foo±§!@#$%^&*()-_=+/?.>,<\\|{}[]`~\'";:bar' )
).toEqual( 'foo-bar' );
} );

it( 'removes any spacing characters', () => {
expect( kebabCase( ' foo \t \n \r \f \v bar ' ) ).toEqual( 'foo-bar' );
} );

it( 'groups multiple dashes into a single one', () => {
expect( kebabCase( 'foo---bar' ) ).toEqual( 'foo-bar' );
} );

it( 'returns an empty string unchanged', () => {
expect( kebabCase( '' ) ).toEqual( '' );
} );

it( 'returns an existing kebab case string unchanged', () => {
expect( kebabCase( 'foo-123-bar' ) ).toEqual( 'foo-123-bar' );
} );

it( 'returns an empty string if any nullish type is passed', () => {
expect( kebabCase( undefined ) ).toEqual( '' );
expect( kebabCase( null ) ).toEqual( '' );
} );

it( 'converts any unexpected non-nullish type to a string', () => {
expect( kebabCase( 12345 ) ).toEqual( '12345' );
expect( kebabCase( [] ) ).toEqual( '' );
expect( kebabCase( {} ) ).toEqual( 'object-object' );
} );

/**
* Should cover all test cases of `_wp_to_kebab_case()`.
*
* @see https://developer.wordpress.org/reference/functions/_wp_to_kebab_case/
* @see https://github.com/WordPress/wordpress-develop/blob/76376fdbc3dc0b3261de377dffc350677345e7ba/tests/phpunit/tests/functions/wpToKebabCase.php#L35-L62
*/
it.each( [
[ 'white', 'white' ],
[ 'white+black', 'white-black' ],
[ 'white:black', 'white-black' ],
[ 'white*black', 'white-black' ],
[ 'white.black', 'white-black' ],
[ 'white black', 'white-black' ],
[ 'white black', 'white-black' ],
[ 'white-to-black', 'white-to-black' ],
[ 'white2white', 'white-2-white' ],
[ 'white2nd', 'white-2nd' ],
[ 'white2ndcolor', 'white-2-ndcolor' ],
[ 'white2ndColor', 'white-2nd-color' ],
[ 'white2nd_color', 'white-2nd-color' ],
[ 'white23color', 'white-23-color' ],
[ 'white23', 'white-23' ],
[ '23color', '23-color' ],
[ 'white4th', 'white-4th' ],
[ 'font2xl', 'font-2-xl' ],
[ 'whiteToWhite', 'white-to-white' ],
[ 'whiteTOwhite', 'white-t-owhite' ],
[ 'WHITEtoWHITE', 'whit-eto-white' ],
[ 42, '42' ],
[ "i've done", 'ive-done' ],
[ '#ffffff', 'ffffff' ],
[ '$ffffff', 'ffffff' ],
] )( 'converts %s properly to %s', ( input, expected ) => {
expect( kebabCase( input ) ).toEqual( expected );
} );
} );
import { setImmutably } from '../object';

describe( 'setImmutably', () => {
describe( 'handling falsy values properly', () => {
Expand Down
Loading

0 comments on commit 3acb484

Please sign in to comment.