playbook_ui 18.0.0.pre.alpha.PLAY323219731 → 18.0.0.pre.alpha.PLAY323219732

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 134e64bef59f08a763ab0b45a1bb404dae1d6b435b243a07551455983a92696e
4
- data.tar.gz: a5d30ad8a5065712dae2137286b1410f3f025a5034c0a0e974a39e4747c614bc
3
+ metadata.gz: 702601751c0fd5c028b371a8daee1fa45544f6a1b212e924e91a68436f663b3e
4
+ data.tar.gz: 42d98672288c51cf43e762b28d094eec228145dddbb62a56869baab16149897c
5
5
  SHA512:
6
- metadata.gz: 85b4699843c3b6160e7171400ace59258f9d385b4823a1c175c353c4823ea32742a0dc47e6ba9ef4234e63038994a313f2d3bd71272d1cf75152f9ce8c8fade8
7
- data.tar.gz: a08acad0f987fb16655ec999ac88b108dc8362e4af3744107f28fb0e06d5a149ee471553e08e60c1956d6a044cf7bd4545180bfffc0131c9d59441cc11652b25
6
+ metadata.gz: e8582dd5511729d758e3110a3224b9a0d55c1f37f10d5c28fd3e16475d72694b4116cff6783631fc2f92d63a85eebbd3bd0c576f5674f9d03ef8ed92ea1cef4d
7
+ data.tar.gz: 856c48ef6c7bf02b8d69ea3c8e418bf151936c8c3fe5a28c4227e8e337ce91cdf820e7c26d50150ad532e991cb086c057f2108e0b5053b189502724a84842615
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useLayoutEffect, useRef, useState } from "react";
2
2
  import Background from "../../pb_background/_background";
3
3
  import Flex from "../../pb_flex/_flex";
4
4
  import SectionSeparator from "../../pb_section_separator/_section_separator";
@@ -11,13 +11,71 @@ import { ToolbarTypes } from "./EditorTypes";
11
11
  import ToolbarHistoryItems from "./ToolbarHistory";
12
12
  import MoreExtensionsDropdown from "./MoreExtensionsDropdown";
13
13
 
14
+ const STACK_HYSTERESIS_PX = 32;
15
+
14
16
  const ToolbarSeparator = (): React.ReactElement => (
15
17
  <div className="toolbar_separator">
16
18
  <SectionSeparator orientation="vertical" />
17
19
  </div>
18
20
  );
19
21
 
22
+ const toolsAreWrapped = (root: HTMLElement | null): boolean => {
23
+ if (!root) return false;
24
+
25
+ const format = root.querySelector<HTMLElement>(".toolbar_group_format");
26
+ const tools = root.querySelector<HTMLElement>(".toolbar_group_tools");
27
+ if (!format || !tools) return false;
28
+
29
+ return tools.offsetTop > format.offsetTop + 1;
30
+ };
31
+
20
32
  const EditorToolbar = ({ editor, extensions, simple, sticky }: any): React.ReactElement => {
33
+ const toolbarInnerRef = useRef<HTMLDivElement>(null);
34
+ const stackedAtWidthRef = useRef(0);
35
+ const [toolsStacked, setToolsStacked] = useState(false);
36
+
37
+ useLayoutEffect(() => {
38
+ if (simple) return;
39
+
40
+ const root = toolbarInnerRef.current;
41
+ if (!root) return;
42
+
43
+ const toolbar = root.closest(".toolbar");
44
+
45
+ const update = () => {
46
+ const wrapped = toolsAreWrapped(root);
47
+ const width = toolbar?.clientWidth ?? root.clientWidth;
48
+
49
+ setToolsStacked((stacked) => {
50
+ if (!stacked && wrapped) {
51
+ stackedAtWidthRef.current = width;
52
+ return true;
53
+ }
54
+
55
+ if (stacked && !wrapped && width > stackedAtWidthRef.current + STACK_HYSTERESIS_PX) {
56
+ return false;
57
+ }
58
+
59
+ if (stacked && wrapped) {
60
+ stackedAtWidthRef.current = Math.min(stackedAtWidthRef.current, width);
61
+ }
62
+
63
+ return stacked;
64
+ });
65
+ };
66
+
67
+ update();
68
+ const rafId = requestAnimationFrame(update);
69
+ const observer = new ResizeObserver(update);
70
+ observer.observe(root);
71
+ if (toolbar) observer.observe(toolbar);
72
+
73
+ return () => {
74
+ cancelAnimationFrame(rafId);
75
+ observer.disconnect();
76
+ };
77
+ }, [extensions, simple]);
78
+
21
79
  const simpleToolbaritems = [
22
80
  {
23
81
  icon: "bold",
@@ -33,10 +91,16 @@ const EditorToolbar = ({ editor, extensions, simple, sticky }: any): React.React
33
91
  },
34
92
  ]
35
93
 
94
+ const toolbarClassName = [
95
+ "toolbar",
96
+ toolsStacked && "toolbar_tools_stacked",
97
+ sticky && "pb_rich_text_editor_tiptap_toolbar_sticky",
98
+ ].filter(Boolean).join(" ");
99
+
36
100
  return (
37
- <Background backgroundColor="white"
38
- className={`toolbar ${sticky ? 'pb_rich_text_editor_tiptap_toolbar_sticky' : ''}`}
39
-
101
+ <Background
102
+ backgroundColor="white"
103
+ className={toolbarClassName}
40
104
  >
41
105
  {
42
106
  simple ? (
@@ -63,7 +127,10 @@ const EditorToolbar = ({ editor, extensions, simple, sticky }: any): React.React
63
127
  </div>
64
128
  </div>
65
129
  ) : (
66
- <div className="toolbar_inner">
130
+ <div
131
+ className="toolbar_inner"
132
+ ref={toolbarInnerRef}
133
+ >
67
134
  <Flex
68
135
  align="center"
69
136
  className="toolbar_controls"
@@ -131,7 +131,8 @@
131
131
  container-type: inline-size;
132
132
  position: relative;
133
133
 
134
- // Prefer one row; undo/redo stay top-right. Stack tools only when they cannot share a line.
134
+ // Prefer one row; undo/redo stay top-right.
135
+ // `.toolbar_tools_stacked` is set in JS when tools wrap to a second row.
135
136
  &_inner {
136
137
  box-sizing: border-box;
137
138
  min-height: $space_lg + $space_xxs + $space_xxs * 2; // 36px button + vertical padding
@@ -140,8 +141,7 @@
140
141
  position: relative;
141
142
  }
142
143
 
143
- // Tools on their own row: drop undo gutter so the tools scrollport can go edge-to-edge.
144
- &:has(.toolbar_group_more) .toolbar_inner {
144
+ &.toolbar_tools_stacked .toolbar_inner {
145
145
  padding-right: $space_sm;
146
146
  }
147
147
 
@@ -177,18 +177,12 @@
177
177
 
178
178
  &_tools {
179
179
  box-sizing: border-box;
180
+ flex-grow: 1;
180
181
  flex-wrap: nowrap;
181
182
  max-width: 100%;
182
183
  min-width: 0;
183
184
  order: 2;
184
185
  overflow-x: auto;
185
-
186
- // Hide leading pipe by default — tools often wrap before container queries
187
- // force a full-width stack, which left a lone "|" at the start of row 2.
188
- // Only show it when the bar is wide enough for a reliable single row.
189
- > .toolbar_separator:first-child {
190
- display: none;
191
- }
192
186
  }
193
187
 
194
188
  &_more {
@@ -196,30 +190,27 @@
196
190
  }
197
191
  }
198
192
 
199
- // With "more": stack tools under format|more until wide enough for one row
193
+ &.toolbar_tools_stacked .toolbar_group_tools > .toolbar_separator:first-child {
194
+ display: none;
195
+ }
196
+
197
+ // With "more": format|more on row 1, tools on row 2 until wide
200
198
  &_controls:has(.toolbar_group_more) {
201
199
  .toolbar_group_more {
202
200
  order: 2;
203
201
  }
204
202
 
205
203
  .toolbar_group_tools {
206
- flex: 1 1 100%;
204
+ flex-basis: 100%;
205
+ flex-grow: 1;
207
206
  order: 3;
208
207
  }
209
208
  }
210
209
 
211
210
  @container rte-toolbar (min-width: 42rem) {
212
- &:has(.toolbar_group_more) .toolbar_inner {
213
- padding-right: $history-gutter;
214
- }
215
-
216
- .toolbar_group_tools > .toolbar_separator:first-child {
217
- display: flex;
218
- }
219
-
220
211
  .toolbar_controls:has(.toolbar_group_more) {
221
212
  .toolbar_group_tools {
222
- flex: 0 1 auto;
213
+ flex-basis: auto;
223
214
  order: 2;
224
215
  overflow-x: visible;
225
216
  }
@@ -230,17 +221,6 @@
230
221
  }
231
222
  }
232
223
 
233
- // No "more": full-width tools row + no undo gutter when stacked
234
- @container rte-toolbar (max-width: 40rem) {
235
- &:not(:has(.toolbar_group_more)) .toolbar_inner {
236
- padding-right: $space_sm;
237
- }
238
-
239
- .toolbar_controls:not(:has(.toolbar_group_more)) .toolbar_group_tools {
240
- flex: 1 1 100%;
241
- }
242
- }
243
-
244
224
  // Separators: slightly inset from button height; xxs gap left/right
245
225
  &_separator {
246
226
  align-items: stretch;
@@ -103,6 +103,49 @@ function runToolbarAction(editor, action, { alignment, prompt = window.prompt, n
103
103
  if (chainMethod && typeof chain[chainMethod] === "function") chain[chainMethod]().run();
104
104
  }
105
105
 
106
+ const TOOLBAR_STACK_HYSTERESIS_PX = 32;
107
+
108
+ function syncToolbarToolsStacked(toolbar, state = { stacked: false, stackedAtWidth: 0 }) {
109
+ if (!toolbar) return state;
110
+
111
+ const format = toolbar.querySelector(".toolbar_group_format");
112
+ const tools = toolbar.querySelector(".toolbar_group_tools");
113
+ if (!format || !tools) return state;
114
+
115
+ const wrapped = tools.offsetTop > format.offsetTop + 1;
116
+ const width = toolbar.clientWidth;
117
+ let { stacked, stackedAtWidth } = state;
118
+
119
+ if (!stacked && wrapped) {
120
+ stacked = true;
121
+ stackedAtWidth = width;
122
+ } else if (stacked && !wrapped && width > stackedAtWidth + TOOLBAR_STACK_HYSTERESIS_PX) {
123
+ stacked = false;
124
+ } else if (stacked && wrapped) {
125
+ stackedAtWidth = Math.min(stackedAtWidth, width);
126
+ }
127
+
128
+ toolbar.classList.toggle("toolbar_tools_stacked", stacked);
129
+ return { stacked, stackedAtWidth };
130
+ }
131
+
132
+ function watchToolbarToolsStacked(toolbar) {
133
+ if (!toolbar) return;
134
+
135
+ let state = { stacked: false, stackedAtWidth: 0 };
136
+ const update = () => {
137
+ state = syncToolbarToolsStacked(toolbar, state);
138
+ };
139
+
140
+ update();
141
+ requestAnimationFrame(update);
142
+
143
+ if (typeof ResizeObserver === "undefined") return;
144
+
145
+ const observer = new ResizeObserver(update);
146
+ observer.observe(toolbar);
147
+ }
148
+
106
149
  async function initPlaybookRichTextEditorRails(container) {
107
150
  if (!container || container.dataset.pbRteInitialized || container.dataset.pbRtePending) return;
108
151
  container.dataset.pbRtePending = "true";
@@ -403,6 +446,7 @@ async function initPlaybookRichTextEditorRails(container) {
403
446
  editor.on("selectionUpdate", updateActiveStates);
404
447
  editor.on("transaction", updateActiveStates);
405
448
  updateActiveStates();
449
+ watchToolbarToolsStacked(toolbar);
406
450
 
407
451
  container.dataset.pbRteInitialized = "true";
408
452
  } finally {
data/dist/ai/forms.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "metadataVersion": 1,
3
- "version": "18.0.0-alpha.PLAY323219731",
3
+ "version": "18.0.0-alpha.PLAY323219732",
4
4
  "builder": {
5
5
  "class": "Playbook::Forms::Builder",
6
6
  "helper": "pb_form_with",
data/dist/ai/index.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "18.0.0-alpha.PLAY323219731",
2
+ "version": "18.0.0-alpha.PLAY323219732",
3
3
  "metadataVersion": 1,
4
4
  "forms": "forms.json",
5
- "generated": "2026-10-01T20:08:22.688Z",
5
+ "generated": "2026-10-01T20:51:10.526Z",
6
6
  "schemas": {
7
7
  "globalProps": "global-props.schema.json",
8
8
  "globalEventProps": "global-event-props.schema.json",