| 1 |
import { addProperty } from "../../../helpers/helper"; |
| 2 |
import { |
| 3 |
WRAPPER_MARGIN, |
| 4 |
WRAPPER_BACKGROUND, |
| 5 |
WRAPPER_BORDER, |
| 6 |
WRAPPER_PADDING, |
| 7 |
READING_TIME_SIZE, |
| 8 |
AI_ACTIONS_PADDING, |
| 9 |
AI_ACTIONS_BACKGROUND, |
| 10 |
AI_ACTIONS_BORDER, |
| 11 |
AI_ACTIONS_ICON_SIZE, |
| 12 |
LISTEN_PADDING, |
| 13 |
LISTEN_BACKGROUND, |
| 14 |
LISTEN_BORDER, |
| 15 |
LISTEN_ICON_SIZE |
| 16 |
} from "./constants"; |
| 17 |
import { typoPrefix_reading_time, typoPrefix_ai_actions, typoPrefix_listen } from "./typographyPrefixConstants"; |
| 18 |
|
| 19 |
export default ( { blockId } ) => { |
| 20 |
const wrapperSelector = `.${blockId} .reading-time.betterdocs`; |
| 21 |
const readingTimeSelector = `.${blockId} .reading-time.betterdocs p`; |
| 22 |
const readingTimeIconSelector = `.${blockId} .reading-time p svg`; |
| 23 |
|
| 24 |
// Margin is the one Wrapper control that moved off the pill and onto the meta |
| 25 |
// row. Inside the row `.betterdocs-doc-meta-start > *` zeroes child block |
| 26 |
// margins with !important — it has to, or a pill with a bottom margin inflates |
| 27 |
// the flex line and knocks the button 12px off centre — so a margin left on |
| 28 |
// the pill would silently do nothing. On the row it still means what it always |
| 29 |
// did: the gap between this block and the article body. |
| 30 |
const metaRowSelector = `.${blockId} .betterdocs-doc-meta`; |
| 31 |
|
| 32 |
// Both halves of the split button, so background, padding and border stay |
| 33 |
// symmetrical across the divider between them. |
| 34 |
// |
| 35 |
// The `.betterdocs-ai-actions` in the middle is not decoration — it is the |
| 36 |
// third class that wins the cascade. The Customizer now ships real defaults |
| 37 |
// for this button (Admin/Customizer/Defaults.php) and writes them at two |
| 38 |
// classes, `.betterdocs-content-area .betterdocs-ai-actions-primary`. Its |
| 39 |
// stylesheet prints on `wp_head` at 10, after the file this block's CSS is |
| 40 |
// enqueued into, so at equal specificity the site-wide value would win and |
| 41 |
// every per-instance control here would look broken inside a classic |
| 42 |
// single-doc template. Three classes settles it without !important. |
| 43 |
const aiWrapperSelector = `.${blockId} .betterdocs-ai-actions`; |
| 44 |
// The wrapper element itself, at three classes. The block's root div and the |
| 45 |
// button wrapper both carry `blockId` (Blocks\ReadingTime appends it to the |
| 46 |
// button's class list), so `.id .id.betterdocs-ai-actions` matches the same |
| 47 |
// single element `.id .betterdocs-ai-actions` would — but outranks the |
| 48 |
// Customizer's two-class site-wide rules, which otherwise win on order. |
| 49 |
const aiWrapperSelectorOwn = `.${blockId} .${blockId}.betterdocs-ai-actions`; |
| 50 |
const aiButtonSelector = `${aiWrapperSelector} .betterdocs-ai-actions-primary, ${aiWrapperSelector} .betterdocs-ai-actions-toggle`; |
| 51 |
const aiLabelSelector = `${aiWrapperSelector} .betterdocs-ai-actions-label`; |
| 52 |
const aiIconSelector = `${aiWrapperSelector} .betterdocs-ai-actions-primary svg, ${aiWrapperSelector} .betterdocs-ai-actions-toggle svg`; |
| 53 |
|
| 54 |
// The Listen pill. One element rather than two halves, so background, border |
| 55 |
// and radius have one home — but the same three-class rule applies for the same |
| 56 |
// reason: the Customizer ships real defaults for this pill |
| 57 |
// (Admin/Customizer/Defaults.php) and writes them at two classes, in a |
| 58 |
// stylesheet that prints on `wp_head` at 10 — after the file this block's CSS |
| 59 |
// is enqueued into. At equal specificity the site-wide value would win and |
| 60 |
// every per-instance control here would look broken inside a classic |
| 61 |
// single-doc template. |
| 62 |
// |
| 63 |
// The block's root div and the pill both carry `blockId` |
| 64 |
// (Blocks\ReadingTime appends it to the pill's class list), so |
| 65 |
// `.id .id.betterdocs-listen` matches the same single element |
| 66 |
// `.id .betterdocs-listen` would — while outranking those two-class rules. |
| 67 |
const listenSelector = `.${blockId} .betterdocs-listen`; |
| 68 |
const listenSelectorOwn = `.${blockId} .${blockId}.betterdocs-listen`; |
| 69 |
// Padding, background and the corner all land on the trigger: it is the only |
| 70 |
// element in flow, so it is what gives the pill its size. The player is |
| 71 |
// absolutely positioned with `inset-block: 0` and inherits that height, which |
| 72 |
// is what keeps the design's promise that nothing below the pill moves. |
| 73 |
const listenTriggerSelector = `${listenSelector} .betterdocs-listen-trigger`; |
| 74 |
const listenLabelSelector = `${listenSelector} .betterdocs-listen-label`; |
| 75 |
const listenIconSelector = `${listenSelector} .betterdocs-listen-trigger svg`; |
| 76 |
// The accent is a custom property on the player, inherited by the play button, |
| 77 |
// the progress fill and the scrubber's ring. Its own selector, with nothing |
| 78 |
// else written to it: the helper that emits a custom property omits the |
| 79 |
// trailing semicolon, so a second declaration joined after it would run into |
| 80 |
// the value and both would be dropped. |
| 81 |
const listenAccentSelector = `${listenSelector} .betterdocs-listen-player`; |
| 82 |
|
| 83 |
return [ |
| 84 |
addProperty('dimension', WRAPPER_PADDING, { |
| 85 |
args: { styleFor: "padding" }, |
| 86 |
cssSelector: wrapperSelector |
| 87 |
}), |
| 88 |
addProperty('dimension', WRAPPER_MARGIN, { |
| 89 |
args: { styleFor: "margin" }, |
| 90 |
cssSelector: metaRowSelector |
| 91 |
}), |
| 92 |
addProperty('background', WRAPPER_BACKGROUND, { |
| 93 |
args: { noOverlay: true }, |
| 94 |
cssSelector: wrapperSelector |
| 95 |
}), |
| 96 |
addProperty('border', WRAPPER_BORDER, { |
| 97 |
args: { noShadow: true }, |
| 98 |
cssSelector:wrapperSelector |
| 99 |
}), |
| 100 |
addProperty('color', 'readingTimeTextColor', { |
| 101 |
cssSelector:readingTimeSelector |
| 102 |
}), |
| 103 |
addProperty('typography', typoPrefix_reading_time, { |
| 104 |
args: { |
| 105 |
defaultFontSize: 14, |
| 106 |
prefixConstant: typoPrefix_reading_time, |
| 107 |
}, |
| 108 |
cssSelector: readingTimeSelector |
| 109 |
}), |
| 110 |
addProperty('responsive', READING_TIME_SIZE, { |
| 111 |
args: { property: "height", controlName: READING_TIME_SIZE }, |
| 112 |
cssSelector: readingTimeIconSelector |
| 113 |
}), |
| 114 |
|
| 115 |
addProperty('dimension', AI_ACTIONS_PADDING, { |
| 116 |
args: { styleFor: "padding" }, |
| 117 |
cssSelector: aiButtonSelector |
| 118 |
}), |
| 119 |
addProperty('background', AI_ACTIONS_BACKGROUND, { |
| 120 |
args: { noOverlay: true }, |
| 121 |
cssSelector: aiButtonSelector |
| 122 |
}), |
| 123 |
// Border on the WRAPPER, not on the halves. A border drawn on each half |
| 124 |
// meets its neighbour at the divider and reads as a double line down the |
| 125 |
// middle of the pill — the stylesheet paints the surface on the wrapper |
| 126 |
// and leaves both halves `border: 0` for exactly that reason. |
| 127 |
addProperty('border', AI_ACTIONS_BORDER, { |
| 128 |
args: { noShadow: true }, |
| 129 |
cssSelector: aiWrapperSelectorOwn |
| 130 |
}), |
| 131 |
// …but the radius still has to reach the halves. The wrapper cannot clip |
| 132 |
// them (the dropdown is absolutely positioned, so `overflow: hidden` |
| 133 |
// would cut it off), so each half carries its own outer corners and a |
| 134 |
// radius set only on the wrapper leaves the pill's ends square. Same |
| 135 |
// attributes, second selector — the stylesheet pins the two inner |
| 136 |
// corners to 0, so only the outer pair actually moves. |
| 137 |
addProperty('dimension', `${AI_ACTIONS_BORDER}Rds_`, { |
| 138 |
id: `${AI_ACTIONS_BORDER}Rds_halves`, |
| 139 |
args: { styleFor: 'border-radius' }, |
| 140 |
cssSelector: aiButtonSelector |
| 141 |
}), |
| 142 |
addProperty('color', 'aiActionsTextColor', { |
| 143 |
cssSelector: aiLabelSelector |
| 144 |
}), |
| 145 |
// Same two selectors the icon-size control uses: every icon is stroked |
| 146 |
// with currentColor, so `color` on the svg is the whole job. |
| 147 |
addProperty('color', 'aiActionsIconColor', { |
| 148 |
cssSelector: aiIconSelector |
| 149 |
}), |
| 150 |
addProperty('typography', typoPrefix_ai_actions, { |
| 151 |
args: { |
| 152 |
defaultFontSize: 14, |
| 153 |
prefixConstant: typoPrefix_ai_actions, |
| 154 |
}, |
| 155 |
cssSelector: aiLabelSelector |
| 156 |
}), |
| 157 |
// One control, two declarations. The stylesheet sizes these icons on both |
| 158 |
// axes, so setting height alone would stretch them. useStyleSheets keys |
| 159 |
// the property list by `id`, so the second entry needs its own — while |
| 160 |
// `args.controlName` keeps both reading the same attribute. |
| 161 |
addProperty('responsive', AI_ACTIONS_ICON_SIZE, { |
| 162 |
args: { property: "height", controlName: AI_ACTIONS_ICON_SIZE }, |
| 163 |
cssSelector: aiIconSelector |
| 164 |
}), |
| 165 |
addProperty('responsive', AI_ACTIONS_ICON_SIZE, { |
| 166 |
id: `${AI_ACTIONS_ICON_SIZE}_width`, |
| 167 |
args: { property: "width", controlName: AI_ACTIONS_ICON_SIZE }, |
| 168 |
cssSelector: aiIconSelector |
| 169 |
}), |
| 170 |
|
| 171 |
addProperty('dimension', LISTEN_PADDING, { |
| 172 |
args: { styleFor: "padding" }, |
| 173 |
cssSelector: listenTriggerSelector |
| 174 |
}), |
| 175 |
// Background on the PILL, not on the trigger — unlike padding above, and |
| 176 |
// matching what Elementor and dynamic.css.php both do. The trigger goes to |
| 177 |
// `opacity: 0` the instant the player opens, so a fill painted there is |
| 178 |
// visible only while collapsed and the expanded player would ignore the |
| 179 |
// author's colour entirely. |
| 180 |
addProperty('background', LISTEN_BACKGROUND, { |
| 181 |
args: { noOverlay: true }, |
| 182 |
cssSelector: listenSelectorOwn |
| 183 |
}), |
| 184 |
// Border on the PILL, not on the trigger: the trigger fills the pill, and a |
| 185 |
// border on both would be drawn twice at the same place — once under the |
| 186 |
// `overflow: hidden` clip and once on top of it, which on a subpixel |
| 187 |
// boundary shows as a doubled edge. |
| 188 |
addProperty('border', LISTEN_BORDER, { |
| 189 |
args: { noShadow: true }, |
| 190 |
cssSelector: listenSelectorOwn |
| 191 |
}), |
| 192 |
// …and the radius also has to reach the trigger, which states its own |
| 193 |
// corner so a theme's `button { border-radius }` cannot square it off. Same |
| 194 |
// attributes, second selector; useStyleSheets keys the property list by |
| 195 |
// `id`, so this needs one of its own. |
| 196 |
addProperty('dimension', `${LISTEN_BORDER}Rds_`, { |
| 197 |
id: `${LISTEN_BORDER}Rds_trigger`, |
| 198 |
args: { styleFor: 'border-radius' }, |
| 199 |
cssSelector: listenTriggerSelector |
| 200 |
}), |
| 201 |
addProperty('color', 'listenTextColor', { |
| 202 |
cssSelector: listenLabelSelector |
| 203 |
}), |
| 204 |
addProperty('color', 'listenIconColor', { |
| 205 |
cssSelector: listenIconSelector |
| 206 |
}), |
| 207 |
addProperty('color', 'listenAccentColor', { |
| 208 |
args: { customProperty: '--bd-listen-accent' }, |
| 209 |
cssSelector: listenAccentSelector |
| 210 |
}), |
| 211 |
// On the pill, not on the label: the player's readout is sized in `em`, so |
| 212 |
// this is what scales the pill and the player together. A font-size on the |
| 213 |
// label alone would leave the clock behind at 12.6px. |
| 214 |
addProperty('typography', typoPrefix_listen, { |
| 215 |
args: { |
| 216 |
defaultFontSize: 14, |
| 217 |
prefixConstant: typoPrefix_listen, |
| 218 |
}, |
| 219 |
cssSelector: listenSelectorOwn |
| 220 |
}), |
| 221 |
// The headset glyph on the collapsed pill. Two declarations from one |
| 222 |
// control, for the reason the AI Actions pair above gives: the stylesheet |
| 223 |
// sizes these on both axes, so height alone would stretch them. |
| 224 |
addProperty('responsive', LISTEN_ICON_SIZE, { |
| 225 |
args: { property: "height", controlName: LISTEN_ICON_SIZE }, |
| 226 |
cssSelector: listenIconSelector |
| 227 |
}), |
| 228 |
addProperty('responsive', LISTEN_ICON_SIZE, { |
| 229 |
id: `${LISTEN_ICON_SIZE}_width`, |
| 230 |
args: { property: "width", controlName: LISTEN_ICON_SIZE }, |
| 231 |
cssSelector: listenIconSelector |
| 232 |
}), |
| 233 |
]; |
| 234 |
} |
| 235 |
|