playbook_ui 18.2.0.pre.rc.2 → 18.2.0
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 +4 -4
- data/app/pb_kits/playbook/pb_background/_background.tsx +3 -1
- data/app/pb_kits/playbook/pb_background/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_caption/_caption.tsx +3 -1
- data/app/pb_kits/playbook/pb_caption/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_container/_container.tsx +3 -1
- data/app/pb_kits/playbook/pb_container/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_currency/_currency.tsx +3 -1
- data/app/pb_kits/playbook/pb_currency/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_dashboard_value/_dashboard_value.tsx +3 -1
- data/app/pb_kits/playbook/pb_dashboard_value/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_detail/_detail.tsx +3 -1
- data/app/pb_kits/playbook/pb_detail/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +6 -5
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_custom.md +7 -2
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_custom_rails.md +7 -2
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_enable_time.html.erb +33 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_enable_time.jsx +42 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_quickpick_enable_time.md +24 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_highlight.jsx +101 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_highlight.md +3 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +3 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_dropdown/index.js +19 -10
- data/app/pb_kits/playbook/pb_dropdown/quickpick/index.ts +60 -18
- data/app/pb_kits/playbook/pb_dropdown/quickpick_helper.rb +92 -3
- data/app/pb_kits/playbook/pb_dropdown/utilities/index.ts +8 -1
- data/app/pb_kits/playbook/pb_dropdown/utilities/quickpickDateSync.ts +137 -0
- data/app/pb_kits/playbook/pb_filter/Filter/InteractiveFilter.tsx +1 -1
- data/app/pb_kits/playbook/pb_highlight/_highlight.tsx +3 -1
- data/app/pb_kits/playbook/pb_highlight/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_icon_stat_value/_icon_stat_value.tsx +3 -1
- data/app/pb_kits/playbook/pb_icon_stat_value/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_icon_value/_icon_value.tsx +3 -1
- data/app/pb_kits/playbook/pb_icon_value/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_label_value/_label_value.tsx +3 -1
- data/app/pb_kits/playbook/pb_label_value/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_map/docs/_map_default.md +3 -3
- data/app/pb_kits/playbook/pb_message/_message.tsx +3 -1
- data/app/pb_kits/playbook/pb_message/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_multiple_users/_multiple_users.tsx +3 -1
- data/app/pb_kits/playbook/pb_multiple_users/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_multiple_users_stacked/_multiple_users_stacked.tsx +4 -2
- data/app/pb_kits/playbook/pb_multiple_users_stacked/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/MoreExtensionsDropdown.tsx +10 -3
- data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/Toolbar.tsx +16 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.json +2 -3
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.overrides.json +2 -3
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_attributes.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_default.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_default.md +2 -3
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_focus.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_height.jsx +3 -13
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_inline.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +2 -3
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_markdown_support.jsx +1 -2
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_min_height.jsx +3 -13
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_preview.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_simple.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_sticky.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_templates.jsx +1 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_more_extensions.jsx +3 -8
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_more_extensions.md +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_rails_default.md +2 -2
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.test.js +2 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_advanced.test.js +7 -5
- data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor_rails.js +15 -4
- data/app/pb_kits/playbook/pb_stat_value/_stat_value.tsx +3 -1
- data/app/pb_kits/playbook/pb_stat_value/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_table/_table.tsx +70 -197
- data/app/pb_kits/playbook/pb_table/index.ts +53 -122
- data/app/pb_kits/playbook/pb_table/styles/_sticky_columns.scss +26 -3
- data/app/pb_kits/playbook/pb_table/utilities/stickyColumns.test.ts +266 -0
- data/app/pb_kits/playbook/pb_table/utilities/stickyColumns.ts +145 -0
- data/app/pb_kits/playbook/pb_title_count/_title_count.tsx +3 -1
- data/app/pb_kits/playbook/pb_title_count/kit.schema.json +1 -0
- data/app/pb_kits/playbook/pb_title_detail/_title_detail.tsx +3 -1
- data/app/pb_kits/playbook/pb_title_detail/kit.schema.json +1 -0
- data/app/pb_kits/playbook/utilities/test/globalProps/onClick.test.js +237 -0
- data/dist/ai/all-schemas.json +16 -0
- data/dist/ai/forms.json +1 -1
- data/dist/ai/index.json +2 -2
- data/dist/ai/kits/background.schema.json +1 -0
- data/dist/ai/kits/caption.schema.json +1 -0
- data/dist/ai/kits/container.schema.json +1 -0
- data/dist/ai/kits/currency.schema.json +1 -0
- data/dist/ai/kits/dashboard_value.schema.json +1 -0
- data/dist/ai/kits/detail.schema.json +1 -0
- data/dist/ai/kits/highlight.schema.json +1 -0
- data/dist/ai/kits/icon_stat_value.schema.json +1 -0
- data/dist/ai/kits/icon_value.schema.json +1 -0
- data/dist/ai/kits/label_value.schema.json +1 -0
- data/dist/ai/kits/message.schema.json +1 -0
- data/dist/ai/kits/multiple_users.schema.json +1 -0
- data/dist/ai/kits/multiple_users_stacked.schema.json +1 -0
- data/dist/ai/kits/stat_value.schema.json +1 -0
- data/dist/ai/kits/title_count.schema.json +1 -0
- data/dist/ai/kits/title_detail.schema.json +1 -0
- data/dist/ai/playgrounds/rich_text_editor.json +1 -1
- data/dist/chunks/{_detail-BqqzPTNr.js → _detail-Bsvl694i.js} +2 -2
- data/dist/chunks/{_pb_line_graph-BGyCJjgG.js → _pb_line_graph-Du7xE9vl.js} +1 -1
- data/dist/chunks/_table-Bf6LGyL9.js +1 -0
- data/dist/chunks/_typeahead-w6S5Vx4b.js +1 -0
- data/dist/chunks/{globalProps-K1DCn3il.js → globalProps-B120NbSJ.js} +1 -1
- data/dist/chunks/{lib-7vm9BMqp.js → lib-Buo6zHEw.js} +1 -1
- data/dist/chunks/quickpickDateSync-DLDx4zce.js +1 -0
- data/dist/chunks/{time_picker_helper-BZutIzB3.js → time_picker_helper-CYGg0UQ0.js} +1 -1
- data/dist/chunks/vendor.js +2 -2
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +19 -13
- data/dist/chunks/_table-CEZU0qUK.js +0 -1
- data/dist/chunks/_typeahead-C7iPR5ZF.js +0 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { applyStickyColumns } from "./stickyColumns"
|
|
2
|
+
|
|
3
|
+
const stubWidth = (element: HTMLElement, width: number) => {
|
|
4
|
+
Object.defineProperty(element, "offsetWidth", {
|
|
5
|
+
configurable: true,
|
|
6
|
+
get: () => Math.floor(width),
|
|
7
|
+
})
|
|
8
|
+
element.getBoundingClientRect = () =>
|
|
9
|
+
({
|
|
10
|
+
width,
|
|
11
|
+
height: 40,
|
|
12
|
+
top: 0,
|
|
13
|
+
left: 0,
|
|
14
|
+
bottom: 40,
|
|
15
|
+
right: width,
|
|
16
|
+
x: 0,
|
|
17
|
+
y: 0,
|
|
18
|
+
toJSON: () => ({}),
|
|
19
|
+
} as DOMRect)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const buildStickyTable = () => {
|
|
23
|
+
document.body.innerHTML = `
|
|
24
|
+
<table class="pb_table">
|
|
25
|
+
<thead>
|
|
26
|
+
<tr>
|
|
27
|
+
<th data-sticky-id="1">Col 1</th>
|
|
28
|
+
<th data-sticky-id="2">Col 2</th>
|
|
29
|
+
<th data-sticky-id="3">Col 3</th>
|
|
30
|
+
<th>Col 4</th>
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
<tr>
|
|
35
|
+
<td data-sticky-id="1">A</td>
|
|
36
|
+
<td data-sticky-id="2">B</td>
|
|
37
|
+
<td data-sticky-id="3">C</td>
|
|
38
|
+
<td>D</td>
|
|
39
|
+
</tr>
|
|
40
|
+
</tbody>
|
|
41
|
+
</table>
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
return document.querySelector("table") as HTMLTableElement
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("applyStickyColumns", () => {
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
document.body.innerHTML = ""
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test("scopes sticky left offsets to the given table", () => {
|
|
53
|
+
document.body.innerHTML = `
|
|
54
|
+
<table id="other">
|
|
55
|
+
<thead><tr><th data-sticky-id="1">Other</th></tr></thead>
|
|
56
|
+
</table>
|
|
57
|
+
<table id="target" class="pb_table">
|
|
58
|
+
<thead>
|
|
59
|
+
<tr>
|
|
60
|
+
<th data-sticky-id="1">Col 1</th>
|
|
61
|
+
<th data-sticky-id="2">Col 2</th>
|
|
62
|
+
</tr>
|
|
63
|
+
</thead>
|
|
64
|
+
<tbody>
|
|
65
|
+
<tr>
|
|
66
|
+
<td data-sticky-id="1">A</td>
|
|
67
|
+
<td data-sticky-id="2">B</td>
|
|
68
|
+
</tr>
|
|
69
|
+
</tbody>
|
|
70
|
+
</table>
|
|
71
|
+
`
|
|
72
|
+
|
|
73
|
+
const target = document.getElementById("target") as HTMLTableElement
|
|
74
|
+
const otherHeader = document.querySelector(
|
|
75
|
+
"#other th[data-sticky-id='1']"
|
|
76
|
+
) as HTMLElement
|
|
77
|
+
const targetHeader1 = target.querySelector(
|
|
78
|
+
"th[data-sticky-id='1']"
|
|
79
|
+
) as HTMLElement
|
|
80
|
+
const targetHeader2 = target.querySelector(
|
|
81
|
+
"th[data-sticky-id='2']"
|
|
82
|
+
) as HTMLElement
|
|
83
|
+
|
|
84
|
+
stubWidth(targetHeader1, 100)
|
|
85
|
+
stubWidth(targetHeader2, 80)
|
|
86
|
+
stubWidth(otherHeader, 250)
|
|
87
|
+
|
|
88
|
+
applyStickyColumns(target, ["1", "2"], "left")
|
|
89
|
+
|
|
90
|
+
expect(otherHeader.classList.contains("sticky")).toBe(false)
|
|
91
|
+
expect(otherHeader.style.left).toBe("")
|
|
92
|
+
expect(targetHeader1.style.left).toBe("0px")
|
|
93
|
+
expect(targetHeader2.style.left).toBe("100px")
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test("uses fractional widths so sticky left columns stay flush", () => {
|
|
97
|
+
const table = buildStickyTable()
|
|
98
|
+
const header1 = table.querySelector(
|
|
99
|
+
"th[data-sticky-id='1']"
|
|
100
|
+
) as HTMLElement
|
|
101
|
+
const header2 = table.querySelector(
|
|
102
|
+
"th[data-sticky-id='2']"
|
|
103
|
+
) as HTMLElement
|
|
104
|
+
const header3 = table.querySelector(
|
|
105
|
+
"th[data-sticky-id='3']"
|
|
106
|
+
) as HTMLElement
|
|
107
|
+
const cell2 = table.querySelector(
|
|
108
|
+
"td[data-sticky-id='2']"
|
|
109
|
+
) as HTMLElement
|
|
110
|
+
|
|
111
|
+
stubWidth(header1, 100.4)
|
|
112
|
+
stubWidth(header2, 80.2)
|
|
113
|
+
stubWidth(header3, 60)
|
|
114
|
+
|
|
115
|
+
applyStickyColumns(table, ["1", "2", "3"], "left")
|
|
116
|
+
|
|
117
|
+
expect(header1.style.left).toBe("0px")
|
|
118
|
+
expect(header1.classList.contains("with-border-right")).toBe(true)
|
|
119
|
+
// Must not use integer offsetWidth (100) or a gap appears at some zooms
|
|
120
|
+
expect(header2.style.left).toBe("100.4px")
|
|
121
|
+
expect(header2.classList.contains("with-border-right")).toBe(true)
|
|
122
|
+
expect(cell2.style.left).toBe(header2.style.left)
|
|
123
|
+
expect(header3.style.left).toBe("180.6px")
|
|
124
|
+
expect(header3.classList.contains("sticky-left-shadow")).toBe(true)
|
|
125
|
+
expect(header3.classList.contains("with-border-right")).toBe(false)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test("applies sticky right offsets from the trailing edge", () => {
|
|
129
|
+
const table = buildStickyTable()
|
|
130
|
+
const header1 = table.querySelector(
|
|
131
|
+
"th[data-sticky-id='1']"
|
|
132
|
+
) as HTMLElement
|
|
133
|
+
const header2 = table.querySelector(
|
|
134
|
+
"th[data-sticky-id='2']"
|
|
135
|
+
) as HTMLElement
|
|
136
|
+
const header3 = table.querySelector(
|
|
137
|
+
"th[data-sticky-id='3']"
|
|
138
|
+
) as HTMLElement
|
|
139
|
+
|
|
140
|
+
stubWidth(header1, 60)
|
|
141
|
+
stubWidth(header2, 80)
|
|
142
|
+
stubWidth(header3, 100)
|
|
143
|
+
|
|
144
|
+
applyStickyColumns(table, ["1", "2", "3"], "right")
|
|
145
|
+
|
|
146
|
+
// Right sticky walks ids in reverse: 3 at right:0, then 2, then 1 with shadow
|
|
147
|
+
expect(header3.style.right).toBe("0px")
|
|
148
|
+
expect(header3.classList.contains("with-border-left")).toBe(true)
|
|
149
|
+
expect(header2.style.right).toBe("100px")
|
|
150
|
+
expect(header1.style.right).toBe("180px")
|
|
151
|
+
expect(header1.classList.contains("sticky-right-shadow")).toBe(true)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test("does not mutate the column id array when applying right sticky", () => {
|
|
155
|
+
const table = buildStickyTable()
|
|
156
|
+
const ids = ["1", "2", "3"]
|
|
157
|
+
const header1 = table.querySelector(
|
|
158
|
+
"th[data-sticky-id='1']"
|
|
159
|
+
) as HTMLElement
|
|
160
|
+
const header2 = table.querySelector(
|
|
161
|
+
"th[data-sticky-id='2']"
|
|
162
|
+
) as HTMLElement
|
|
163
|
+
const header3 = table.querySelector(
|
|
164
|
+
"th[data-sticky-id='3']"
|
|
165
|
+
) as HTMLElement
|
|
166
|
+
|
|
167
|
+
stubWidth(header1, 60)
|
|
168
|
+
stubWidth(header2, 80)
|
|
169
|
+
stubWidth(header3, 100)
|
|
170
|
+
|
|
171
|
+
applyStickyColumns(table, ids, "right")
|
|
172
|
+
|
|
173
|
+
expect(ids).toEqual(["1", "2", "3"])
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test("pins every header row cell that shares a sticky id", () => {
|
|
177
|
+
document.body.innerHTML = `
|
|
178
|
+
<table class="pb_table">
|
|
179
|
+
<thead>
|
|
180
|
+
<tr>
|
|
181
|
+
<th data-sticky-id="1"></th>
|
|
182
|
+
<th colspan="2">Group</th>
|
|
183
|
+
</tr>
|
|
184
|
+
<tr>
|
|
185
|
+
<th data-sticky-id="1">Territory</th>
|
|
186
|
+
<th>Shadow</th>
|
|
187
|
+
<th>Delta</th>
|
|
188
|
+
</tr>
|
|
189
|
+
</thead>
|
|
190
|
+
<tbody>
|
|
191
|
+
<tr>
|
|
192
|
+
<td data-sticky-id="1">East</td>
|
|
193
|
+
<td>1</td>
|
|
194
|
+
<td>2</td>
|
|
195
|
+
</tr>
|
|
196
|
+
</tbody>
|
|
197
|
+
</table>
|
|
198
|
+
`
|
|
199
|
+
|
|
200
|
+
const table = document.querySelector("table") as HTMLTableElement
|
|
201
|
+
const headers = table.querySelectorAll(
|
|
202
|
+
"th[data-sticky-id='1']"
|
|
203
|
+
) as NodeListOf<HTMLElement>
|
|
204
|
+
const cell = table.querySelector(
|
|
205
|
+
"td[data-sticky-id='1']"
|
|
206
|
+
) as HTMLElement
|
|
207
|
+
|
|
208
|
+
// First header row cell is empty/narrow; second row is wider — use max width
|
|
209
|
+
stubWidth(headers[0], 40)
|
|
210
|
+
stubWidth(headers[1], 120)
|
|
211
|
+
stubWidth(cell, 120)
|
|
212
|
+
|
|
213
|
+
applyStickyColumns(table, ["1"], "left")
|
|
214
|
+
|
|
215
|
+
expect(headers[0].classList.contains("sticky")).toBe(true)
|
|
216
|
+
expect(headers[0].style.left).toBe("0px")
|
|
217
|
+
expect(headers[0].classList.contains("sticky-left-shadow")).toBe(true)
|
|
218
|
+
expect(headers[1].classList.contains("sticky")).toBe(true)
|
|
219
|
+
expect(headers[1].style.left).toBe("0px")
|
|
220
|
+
expect(headers[1].classList.contains("sticky-left-shadow")).toBe(true)
|
|
221
|
+
expect(cell.style.left).toBe("0px")
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test("sets top on sticky header cells when sticky-header is enabled", () => {
|
|
225
|
+
document.body.innerHTML = `
|
|
226
|
+
<table class="pb_table sticky-header sticky-left-column">
|
|
227
|
+
<thead>
|
|
228
|
+
<tr>
|
|
229
|
+
<th data-sticky-id="1">Group</th>
|
|
230
|
+
<th>Other</th>
|
|
231
|
+
</tr>
|
|
232
|
+
<tr>
|
|
233
|
+
<th data-sticky-id="1">Sub</th>
|
|
234
|
+
<th>Col</th>
|
|
235
|
+
</tr>
|
|
236
|
+
</thead>
|
|
237
|
+
<tbody>
|
|
238
|
+
<tr>
|
|
239
|
+
<td data-sticky-id="1">A</td>
|
|
240
|
+
<td>B</td>
|
|
241
|
+
</tr>
|
|
242
|
+
</tbody>
|
|
243
|
+
</table>
|
|
244
|
+
`
|
|
245
|
+
|
|
246
|
+
const table = document.querySelector("table") as HTMLTableElement
|
|
247
|
+
const headers = table.querySelectorAll(
|
|
248
|
+
"th[data-sticky-id='1']"
|
|
249
|
+
) as NodeListOf<HTMLElement>
|
|
250
|
+
const firstRow = table.querySelector("thead tr") as HTMLElement
|
|
251
|
+
|
|
252
|
+
Object.defineProperty(firstRow, "offsetHeight", {
|
|
253
|
+
configurable: true,
|
|
254
|
+
get: () => 48,
|
|
255
|
+
})
|
|
256
|
+
stubWidth(headers[0], 100)
|
|
257
|
+
stubWidth(headers[1], 100)
|
|
258
|
+
|
|
259
|
+
applyStickyColumns(table, ["1"], "left")
|
|
260
|
+
|
|
261
|
+
expect(headers[0].style.top).toBe("0px")
|
|
262
|
+
expect(headers[1].style.top).toBe("48px")
|
|
263
|
+
expect(headers[0].style.left).toBe("0px")
|
|
264
|
+
expect(headers[1].style.left).toBe("0px")
|
|
265
|
+
})
|
|
266
|
+
})
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
type StickySide = "left" | "right"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Prefer the more precise layout width. Fractional values avoid integer
|
|
5
|
+
* offsetWidth rounding that leaves gaps between sticky columns at some
|
|
6
|
+
* zoom/viewport sizes.
|
|
7
|
+
*/
|
|
8
|
+
const measureStickyWidth = (element: HTMLElement): number => {
|
|
9
|
+
const rectWidth = element.getBoundingClientRect().width
|
|
10
|
+
return rectWidth > 0 ? rectWidth : element.offsetWidth
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const formatStickyOffset = (offset: number): string =>
|
|
14
|
+
`${parseFloat(offset.toFixed(3))}px`
|
|
15
|
+
|
|
16
|
+
const applyStickySideClasses = (
|
|
17
|
+
element: HTMLElement,
|
|
18
|
+
isLastColumn: boolean,
|
|
19
|
+
borderClass: string,
|
|
20
|
+
shadowClass: string
|
|
21
|
+
): void => {
|
|
22
|
+
if (!isLastColumn) {
|
|
23
|
+
element.classList.add(borderClass)
|
|
24
|
+
element.classList.remove(shadowClass)
|
|
25
|
+
} else {
|
|
26
|
+
element.classList.remove(borderClass)
|
|
27
|
+
element.classList.add(shadowClass)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const stickyHeaderSelector = (colId: string): string =>
|
|
32
|
+
`th[data-sticky-id="${colId}"], .pb_table_th[data-sticky-id="${colId}"]`
|
|
33
|
+
|
|
34
|
+
const stickyCellSelector = (colId: string): string =>
|
|
35
|
+
`td[data-sticky-id="${colId}"], .pb_table_td[data-sticky-id="${colId}"]`
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Vertical offset within thead so sticky column header cells keep sticking
|
|
39
|
+
* with sticky-header on multi-row theads (row 2+ need top: row1Height, etc.).
|
|
40
|
+
*/
|
|
41
|
+
const stickyHeaderRowTop = (header: HTMLElement): number => {
|
|
42
|
+
const row = header.closest("tr, .pb_table_tr") as HTMLElement | null
|
|
43
|
+
if (!row) return 0
|
|
44
|
+
|
|
45
|
+
let top = 0
|
|
46
|
+
let previous = row.previousElementSibling as HTMLElement | null
|
|
47
|
+
while (previous) {
|
|
48
|
+
if (previous instanceof HTMLElement) {
|
|
49
|
+
top += previous.offsetHeight
|
|
50
|
+
}
|
|
51
|
+
previous = previous.previousElementSibling as HTMLElement | null
|
|
52
|
+
}
|
|
53
|
+
return top
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const applyStickyHeaderTop = (
|
|
57
|
+
table: Element,
|
|
58
|
+
headerElement: HTMLElement
|
|
59
|
+
): void => {
|
|
60
|
+
if (!table.classList.contains("sticky-header")) return
|
|
61
|
+
headerElement.style.top = formatStickyOffset(stickyHeaderRowTop(headerElement))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const applyStickyColumns = (
|
|
65
|
+
table: Element,
|
|
66
|
+
columnIds: string[],
|
|
67
|
+
side: StickySide
|
|
68
|
+
): void => {
|
|
69
|
+
if (!columnIds.length) return
|
|
70
|
+
|
|
71
|
+
const orderedIds = side === "right" ? [...columnIds].reverse() : columnIds
|
|
72
|
+
const borderClass = side === "left" ? "with-border-right" : "with-border-left"
|
|
73
|
+
const shadowClass = side === "left" ? "sticky-left-shadow" : "sticky-right-shadow"
|
|
74
|
+
|
|
75
|
+
let accumulatedWidth = 0
|
|
76
|
+
|
|
77
|
+
orderedIds.forEach((colId, index) => {
|
|
78
|
+
const isLastColumn = index === orderedIds.length - 1
|
|
79
|
+
// Multi-row theads can repeat the same data-sticky-id on each header row —
|
|
80
|
+
// pin every matching th, not only the first.
|
|
81
|
+
const headers = table.querySelectorAll(stickyHeaderSelector(colId))
|
|
82
|
+
const cells = table.querySelectorAll(stickyCellSelector(colId))
|
|
83
|
+
|
|
84
|
+
if (!headers.length) return
|
|
85
|
+
|
|
86
|
+
const offset = accumulatedWidth
|
|
87
|
+
const offsetValue = formatStickyOffset(offset)
|
|
88
|
+
let columnWidth = 0
|
|
89
|
+
|
|
90
|
+
headers.forEach((header) => {
|
|
91
|
+
const headerElement = header as HTMLElement
|
|
92
|
+
headerElement.classList.add("sticky")
|
|
93
|
+
headerElement.style[side] = offsetValue
|
|
94
|
+
applyStickyHeaderTop(table, headerElement)
|
|
95
|
+
applyStickySideClasses(headerElement, isLastColumn, borderClass, shadowClass)
|
|
96
|
+
columnWidth = Math.max(columnWidth, measureStickyWidth(headerElement))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
accumulatedWidth = offset + columnWidth
|
|
100
|
+
|
|
101
|
+
cells.forEach((cell) => {
|
|
102
|
+
const cellElement = cell as HTMLElement
|
|
103
|
+
cellElement.classList.add("sticky")
|
|
104
|
+
cellElement.style[side] = offsetValue
|
|
105
|
+
applyStickySideClasses(cellElement, isLastColumn, borderClass, shadowClass)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Corner cells may use .sticky without data-sticky-id (e.g. colspan spanning
|
|
110
|
+
// multiple sticky columns). Still need top when sticky-header is on.
|
|
111
|
+
if (table.classList.contains("sticky-header")) {
|
|
112
|
+
table
|
|
113
|
+
.querySelectorAll("thead .sticky, .pb_table_thead .sticky")
|
|
114
|
+
.forEach((header) => {
|
|
115
|
+
applyStickyHeaderTop(table, header as HTMLElement)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Double rAF + fonts.ready so offsets are measured after layout settles. */
|
|
121
|
+
export const scheduleStickyColumns = (apply: () => void): (() => void) => {
|
|
122
|
+
let cancelled = false
|
|
123
|
+
let frameId = 0
|
|
124
|
+
|
|
125
|
+
const run = () => {
|
|
126
|
+
if (!cancelled) apply()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
frameId = requestAnimationFrame(() => {
|
|
130
|
+
run()
|
|
131
|
+
frameId = requestAnimationFrame(run)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const fonts = (
|
|
135
|
+
document as Document & { fonts?: { ready?: Promise<unknown> } }
|
|
136
|
+
).fonts
|
|
137
|
+
if (fonts?.ready) {
|
|
138
|
+
fonts.ready.then(run).catch((): undefined => undefined)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return () => {
|
|
142
|
+
cancelled = true
|
|
143
|
+
cancelAnimationFrame(frameId)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -4,6 +4,7 @@ import classnames from 'classnames'
|
|
|
4
4
|
|
|
5
5
|
import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
|
|
6
6
|
import { globalProps } from '../utilities/globalProps'
|
|
7
|
+
import { GlobalEventProps, globalEventProps } from '../utilities/globalEventProps'
|
|
7
8
|
|
|
8
9
|
import Body from '../pb_body/_body'
|
|
9
10
|
import Title from '../pb_title/_title'
|
|
@@ -19,7 +20,7 @@ type TitleCountProps = {
|
|
|
19
20
|
id?: string,
|
|
20
21
|
title?: string,
|
|
21
22
|
size?: "lg" | "sm",
|
|
22
|
-
};
|
|
23
|
+
} & GlobalEventProps;
|
|
23
24
|
|
|
24
25
|
const TitleCount = (props: TitleCountProps): React.ReactElement => {
|
|
25
26
|
const {
|
|
@@ -50,6 +51,7 @@ const TitleCount = (props: TitleCountProps): React.ReactElement => {
|
|
|
50
51
|
{...ariaProps}
|
|
51
52
|
{...dataProps}
|
|
52
53
|
{...htmlProps}
|
|
54
|
+
{...globalEventProps(props)}
|
|
53
55
|
className={css}
|
|
54
56
|
id={id}
|
|
55
57
|
>
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import classnames from 'classnames'
|
|
3
3
|
|
|
4
4
|
import { GlobalProps, globalProps } from '../utilities/globalProps'
|
|
5
|
+
import { GlobalEventProps, globalEventProps } from '../utilities/globalEventProps'
|
|
5
6
|
import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
|
|
6
7
|
|
|
7
8
|
import Body from '../pb_body/_body'
|
|
@@ -16,7 +17,7 @@ type TitleDetailProps = {
|
|
|
16
17
|
htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
|
|
17
18
|
id?: string,
|
|
18
19
|
title: string,
|
|
19
|
-
} & GlobalProps
|
|
20
|
+
} & GlobalProps & GlobalEventProps
|
|
20
21
|
|
|
21
22
|
const TitleDetail = (props: TitleDetailProps): React.ReactElement => {
|
|
22
23
|
const {
|
|
@@ -40,6 +41,7 @@ const TitleDetail = (props: TitleDetailProps): React.ReactElement => {
|
|
|
40
41
|
{...ariaProps}
|
|
41
42
|
{...dataProps}
|
|
42
43
|
{...htmlProps}
|
|
44
|
+
{...globalEventProps(props)}
|
|
43
45
|
className={classnames(pbCss, globalProps(props), className)}
|
|
44
46
|
id={id}
|
|
45
47
|
>
|