@wordpress/block-editor
Version:
389 lines (334 loc) • 12.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PositionPanel = PositionPanel;
exports.getPositionCSS = getPositionCSS;
exports.hasFixedPositionSupport = hasFixedPositionSupport;
exports.hasPositionSupport = hasPositionSupport;
exports.hasPositionValue = hasPositionValue;
exports.hasStickyOrFixedPositionValue = hasStickyOrFixedPositionValue;
exports.hasStickyPositionSupport = hasStickyPositionSupport;
exports.resetPosition = resetPosition;
exports.useIsPositionDisabled = useIsPositionDisabled;
exports.withPositionStyles = exports.withInspectorControls = void 0;
var _element = require("@wordpress/element");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _classnames = _interopRequireDefault(require("classnames"));
var _i18n = require("@wordpress/i18n");
var _blocks = require("@wordpress/blocks");
var _components = require("@wordpress/components");
var _compose = require("@wordpress/compose");
var _data = require("@wordpress/data");
var _hooks = require("@wordpress/hooks");
var _blockList = _interopRequireDefault(require("../components/block-list"));
var _useSetting = _interopRequireDefault(require("../components/use-setting"));
var _inspectorControls = _interopRequireDefault(require("../components/inspector-controls"));
var _utils = require("./utils");
var _lockUnlock = require("../lock-unlock");
var _store = require("../store");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const {
CustomSelectControl
} = (0, _lockUnlock.unlock)(_components.privateApis);
const POSITION_SUPPORT_KEY = 'position';
const OPTION_CLASSNAME = 'block-editor-hooks__position-selection__select-control__option';
const DEFAULT_OPTION = {
key: 'default',
value: '',
name: (0, _i18n.__)('Default'),
className: OPTION_CLASSNAME
};
const STICKY_OPTION = {
key: 'sticky',
value: 'sticky',
name: (0, _i18n._x)('Sticky', 'Name for the value of the CSS position property'),
className: OPTION_CLASSNAME,
__experimentalHint: (0, _i18n.__)('The block will stick to the top of the window instead of scrolling.')
};
const FIXED_OPTION = {
key: 'fixed',
value: 'fixed',
name: (0, _i18n._x)('Fixed', 'Name for the value of the CSS position property'),
className: OPTION_CLASSNAME,
__experimentalHint: (0, _i18n.__)('The block will not move when the page is scrolled.')
};
const POSITION_SIDES = ['top', 'right', 'bottom', 'left'];
const VALID_POSITION_TYPES = ['sticky', 'fixed'];
/**
* Get calculated position CSS.
*
* @param {Object} props Component props.
* @param {string} props.selector Selector to use.
* @param {Object} props.style Style object.
* @return {string} The generated CSS rules.
*/
function getPositionCSS({
selector,
style
}) {
let output = '';
const {
type: positionType
} = style?.position || {};
if (!VALID_POSITION_TYPES.includes(positionType)) {
return output;
}
output += `${selector} {`;
output += `position: ${positionType};`;
POSITION_SIDES.forEach(side => {
if (style?.position?.[side] !== undefined) {
output += `${side}: ${style.position[side]};`;
}
});
if (positionType === 'sticky' || positionType === 'fixed') {
// TODO: Replace hard-coded z-index value with a z-index preset approach in theme.json.
output += `z-index: 10`;
}
output += `}`;
return output;
}
/**
* Determines if there is sticky position support.
*
* @param {string|Object} blockType Block name or Block Type object.
*
* @return {boolean} Whether there is support.
*/
function hasStickyPositionSupport(blockType) {
const support = (0, _blocks.getBlockSupport)(blockType, POSITION_SUPPORT_KEY);
return !!(true === support || support?.sticky);
}
/**
* Determines if there is fixed position support.
*
* @param {string|Object} blockType Block name or Block Type object.
*
* @return {boolean} Whether there is support.
*/
function hasFixedPositionSupport(blockType) {
const support = (0, _blocks.getBlockSupport)(blockType, POSITION_SUPPORT_KEY);
return !!(true === support || support?.fixed);
}
/**
* Determines if there is position support.
*
* @param {string|Object} blockType Block name or Block Type object.
*
* @return {boolean} Whether there is support.
*/
function hasPositionSupport(blockType) {
const support = (0, _blocks.getBlockSupport)(blockType, POSITION_SUPPORT_KEY);
return !!support;
}
/**
* Checks if there is a current value in the position block support attributes.
*
* @param {Object} props Block props.
* @return {boolean} Whether or not the block has a position value set.
*/
function hasPositionValue(props) {
return props.attributes.style?.position?.type !== undefined;
}
/**
* Checks if the block is currently set to a sticky or fixed position.
* This check is helpful for determining how to position block toolbars or other elements.
*
* @param {Object} attributes Block attributes.
* @return {boolean} Whether or not the block is set to a sticky or fixed position.
*/
function hasStickyOrFixedPositionValue(attributes) {
const positionType = attributes.style?.position?.type;
return positionType === 'sticky' || positionType === 'fixed';
}
/**
* Resets the position block support attributes. This can be used when disabling
* the position support controls for a block via a `ToolsPanel`.
*
* @param {Object} props Block props.
* @param {Object} props.attributes Block's attributes.
* @param {Object} props.setAttributes Function to set block's attributes.
*/
function resetPosition({
attributes = {},
setAttributes
}) {
const {
style = {}
} = attributes;
setAttributes({
style: (0, _utils.cleanEmptyObject)({ ...style,
position: { ...style?.position,
type: undefined,
top: undefined,
right: undefined,
bottom: undefined,
left: undefined
}
})
});
}
/**
* Custom hook that checks if position settings have been disabled.
*
* @param {string} name The name of the block.
*
* @return {boolean} Whether padding setting is disabled.
*/
function useIsPositionDisabled({
name: blockName
} = {}) {
const allowFixed = (0, _useSetting.default)('position.fixed');
const allowSticky = (0, _useSetting.default)('position.sticky');
const isDisabled = !allowFixed && !allowSticky;
return !hasPositionSupport(blockName) || isDisabled;
}
/*
* Position controls rendered in an inspector control panel.
*
* @param {Object} props
*
* @return {WPElement} Position panel.
*/
function PositionPanel(props) {
const {
attributes: {
style = {}
},
clientId,
name: blockName,
setAttributes
} = props;
const allowFixed = hasFixedPositionSupport(blockName);
const allowSticky = hasStickyPositionSupport(blockName);
const value = style?.position?.type;
const {
hasParents
} = (0, _data.useSelect)(select => {
const {
getBlockParents
} = select(_store.store);
const parents = getBlockParents(clientId);
return {
hasParents: parents.length
};
}, [clientId]);
const options = (0, _element.useMemo)(() => {
const availableOptions = [DEFAULT_OPTION]; // Only display sticky option if the block has no parents (is at the root of the document),
// or if the block already has a sticky position value set.
if (allowSticky && !hasParents || value === STICKY_OPTION.value) {
availableOptions.push(STICKY_OPTION);
}
if (allowFixed || value === FIXED_OPTION.value) {
availableOptions.push(FIXED_OPTION);
}
return availableOptions;
}, [allowFixed, allowSticky, hasParents, value]);
const onChangeType = next => {
// For now, use a hard-coded `0px` value for the position.
// `0px` is preferred over `0` as it can be used in `calc()` functions.
// In the future, it could be useful to allow for an offset value.
const placementValue = '0px';
const newStyle = { ...style,
position: { ...style?.position,
type: next,
top: next === 'sticky' || next === 'fixed' ? placementValue : undefined
}
};
setAttributes({
style: (0, _utils.cleanEmptyObject)(newStyle)
});
};
const selectedOption = value ? options.find(option => option.value === value) || DEFAULT_OPTION : DEFAULT_OPTION; // Only display position controls if there is at least one option to choose from.
return _element.Platform.select({
web: options.length > 1 ? (0, _element.createElement)(_inspectorControls.default, {
group: "position"
}, (0, _element.createElement)(_components.BaseControl, {
className: "block-editor-hooks__position-selection"
}, (0, _element.createElement)(CustomSelectControl, {
__nextUnconstrainedWidth: true,
__next36pxDefaultSize: true,
className: "block-editor-hooks__position-selection__select-control",
label: (0, _i18n.__)('Position'),
hideLabelFromVision: true,
describedBy: (0, _i18n.sprintf)( // translators: %s: Currently selected position.
(0, _i18n.__)('Currently selected position: %s'), selectedOption.name),
options: options,
value: selectedOption,
__experimentalShowSelectedHint: true,
onChange: ({
selectedItem
}) => {
onChangeType(selectedItem.value);
},
size: '__unstable-large'
}))) : null,
native: null
});
}
/**
* Override the default edit UI to include position controls.
*
* @param {Function} BlockEdit Original component.
*
* @return {Function} Wrapped component.
*/
const withInspectorControls = (0, _compose.createHigherOrderComponent)(BlockEdit => props => {
const {
name: blockName
} = props;
const positionSupport = (0, _blocks.hasBlockSupport)(blockName, POSITION_SUPPORT_KEY);
const showPositionControls = positionSupport && !useIsPositionDisabled(props);
return [showPositionControls && (0, _element.createElement)(PositionPanel, (0, _extends2.default)({
key: "position"
}, props)), (0, _element.createElement)(BlockEdit, (0, _extends2.default)({
key: "edit"
}, props))];
}, 'withInspectorControls');
/**
* Override the default block element to add the position styles.
*
* @param {Function} BlockListBlock Original component.
*
* @return {Function} Wrapped component.
*/
exports.withInspectorControls = withInspectorControls;
const withPositionStyles = (0, _compose.createHigherOrderComponent)(BlockListBlock => props => {
const {
name,
attributes
} = props;
const hasPositionBlockSupport = (0, _blocks.hasBlockSupport)(name, POSITION_SUPPORT_KEY);
const allowPositionStyles = hasPositionBlockSupport && !useIsPositionDisabled(props);
const id = (0, _compose.useInstanceId)(BlockListBlock);
const element = (0, _element.useContext)(_blockList.default.__unstableElementContext); // Higher specificity to override defaults in editor UI.
const positionSelector = `.wp-container-${id}.wp-container-${id}`; // Get CSS string for the current position values.
let css;
if (allowPositionStyles) {
css = getPositionCSS({
selector: positionSelector,
style: attributes?.style
}) || '';
} // Attach a `wp-container-` id-based class name.
const className = (0, _classnames.default)(props?.className, {
[`wp-container-${id}`]: allowPositionStyles && !!css,
// Only attach a container class if there is generated CSS to be attached.
[`is-position-${attributes?.style?.position?.type}`]: allowPositionStyles && !!css && !!attributes?.style?.position?.type
});
return (0, _element.createElement)(_element.Fragment, null, allowPositionStyles && element && !!css && (0, _element.createPortal)((0, _element.createElement)("style", null, css), element), (0, _element.createElement)(BlockListBlock, (0, _extends2.default)({}, props, {
className: className
})));
}, 'withPositionStyles');
exports.withPositionStyles = withPositionStyles;
(0, _hooks.addFilter)('editor.BlockListBlock', 'core/editor/position/with-position-styles', withPositionStyles);
(0, _hooks.addFilter)('editor.BlockEdit', 'core/editor/position/with-inspector-controls', withInspectorControls);
//# sourceMappingURL=position.js.map