Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 15 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading