@nkootstra/block-kit 0.3.5 → 0.4.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.
- package/dist/{index-DXUlQCUq.d.ts → index--GXgMjw3.d.ts} +25 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +778 -189
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/dist/transport.d.ts +1 -1
- package/dist/web-api.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -50,12 +50,12 @@ function toViewOutput(view, state, identity) {
|
|
|
50
50
|
bot_id: "B00000000",
|
|
51
51
|
callback_id: view.callback_id ?? "",
|
|
52
52
|
type: view.type,
|
|
53
|
-
title: view.title ?? null,
|
|
54
|
-
close: view.close ?? null,
|
|
55
|
-
submit: view.submit ?? null,
|
|
56
|
-
blocks: view.blocks,
|
|
53
|
+
title: normalizeText(view.title ?? null),
|
|
54
|
+
close: normalizeText(view.close ?? null),
|
|
55
|
+
submit: normalizeText(view.submit ?? null),
|
|
56
|
+
blocks: normalizeBlocks(view.blocks),
|
|
57
57
|
private_metadata: view.private_metadata ?? "",
|
|
58
|
-
state: { values: state },
|
|
58
|
+
state: { values: normalizeState(state) },
|
|
59
59
|
hash: view.hash ?? "0.0000000000",
|
|
60
60
|
clear_on_close: view.clear_on_close ?? false,
|
|
61
61
|
notify_on_close: view.notify_on_close ?? false,
|
|
@@ -112,6 +112,35 @@ function normalizeEchoes(entry) {
|
|
|
112
112
|
function normalizeState(state) {
|
|
113
113
|
return Object.fromEntries(Object.entries(state).map(([blockId, elements]) => [blockId, Object.fromEntries(Object.entries(elements).map(([actionId, value]) => [actionId, normalizeEchoes(value)]))]));
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Blocks as Slack echoes a stored view: every text object normalized like {@link normalizeText},
|
|
117
|
+
* and an input block's `optional` and `dispatch_action` filled in as `false`.
|
|
118
|
+
*/
|
|
119
|
+
function normalizeBlocks(blocks) {
|
|
120
|
+
return normalizeBlockNode(blocks);
|
|
121
|
+
}
|
|
122
|
+
function normalizeBlockNode(node) {
|
|
123
|
+
if (Array.isArray(node)) return node.map(normalizeBlockNode);
|
|
124
|
+
if (typeof node !== "object" || node === null) return node;
|
|
125
|
+
const object = node;
|
|
126
|
+
if ((object.type === "plain_text" || object.type === "mrkdwn") && typeof object.text === "string") return normalizeText(object);
|
|
127
|
+
const out = Object.fromEntries(Object.entries(object).map(([key, value]) => [key, normalizeBlockNode(value)]));
|
|
128
|
+
if (object.type === "input") {
|
|
129
|
+
out.optional ??= false;
|
|
130
|
+
out.dispatch_action ??= false;
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
/** `enterprise` and `is_enterprise_install`, as every interaction payload carries them. */
|
|
135
|
+
function enterpriseFields(id) {
|
|
136
|
+
return {
|
|
137
|
+
enterprise: id.team?.enterprise_id ? {
|
|
138
|
+
id: id.team.enterprise_id,
|
|
139
|
+
name: id.team.enterprise_name ?? ""
|
|
140
|
+
} : null,
|
|
141
|
+
is_enterprise_install: id.isEnterpriseInstall
|
|
142
|
+
};
|
|
143
|
+
}
|
|
115
144
|
function buildBlockActionsPayload({ action, state, container, identity }) {
|
|
116
145
|
const id = resolveIdentity(identity);
|
|
117
146
|
state = normalizeState(state);
|
|
@@ -119,11 +148,7 @@ function buildBlockActionsPayload({ action, state, container, identity }) {
|
|
|
119
148
|
type: "block_actions",
|
|
120
149
|
actions: [normalizeEchoes(action)],
|
|
121
150
|
team: id.team,
|
|
122
|
-
|
|
123
|
-
id: id.team.enterprise_id,
|
|
124
|
-
name: id.team.enterprise_name ?? ""
|
|
125
|
-
} : null,
|
|
126
|
-
is_enterprise_install: id.isEnterpriseInstall,
|
|
151
|
+
...enterpriseFields(id),
|
|
127
152
|
user: id.user,
|
|
128
153
|
token: id.token,
|
|
129
154
|
response_url: id.responseUrl,
|
|
@@ -163,6 +188,7 @@ function buildViewSubmissionPayload({ view, state, identity, responseUrls }) {
|
|
|
163
188
|
return {
|
|
164
189
|
type: "view_submission",
|
|
165
190
|
team: id.team,
|
|
191
|
+
...enterpriseFields(id),
|
|
166
192
|
user: {
|
|
167
193
|
id: id.user.id,
|
|
168
194
|
name: id.user.name ?? id.user.username ?? "",
|
|
@@ -181,6 +207,7 @@ function buildViewClosedPayload({ view, state, identity, isCleared }) {
|
|
|
181
207
|
return {
|
|
182
208
|
type: "view_closed",
|
|
183
209
|
team: id.team,
|
|
210
|
+
...enterpriseFields(id),
|
|
184
211
|
user: {
|
|
185
212
|
id: id.user.id,
|
|
186
213
|
name: id.user.name ?? id.user.username ?? "",
|
|
@@ -203,7 +230,8 @@ function buildBlockSuggestionPayload({ actionId, blockId, value, container, stat
|
|
|
203
230
|
action_id: actionId,
|
|
204
231
|
block_id: blockId,
|
|
205
232
|
value,
|
|
206
|
-
team: id.team
|
|
233
|
+
team: id.team,
|
|
234
|
+
...enterpriseFields(id)
|
|
207
235
|
};
|
|
208
236
|
if (container.type === "message") return {
|
|
209
237
|
...base,
|
|
@@ -1040,10 +1068,10 @@ function BlockKitProvider(props) {
|
|
|
1040
1068
|
containerRef.current = container;
|
|
1041
1069
|
}, []);
|
|
1042
1070
|
const dispatch = useCallback((action, scope) => {
|
|
1043
|
-
const full = {
|
|
1071
|
+
const full = normalizeEchoes({
|
|
1044
1072
|
...action,
|
|
1045
1073
|
action_ts: action.action_ts ?? actionTs()
|
|
1046
|
-
};
|
|
1074
|
+
});
|
|
1047
1075
|
const message = scope?.message;
|
|
1048
1076
|
onAction?.(full, {
|
|
1049
1077
|
state: stateRef.current,
|
|
@@ -5982,33 +6010,89 @@ function Text({ text, emojiSize = 22 }) {
|
|
|
5982
6010
|
}
|
|
5983
6011
|
//#endregion
|
|
5984
6012
|
//#region src/confirm/ConfirmDialog.tsx
|
|
5985
|
-
/**
|
|
6013
|
+
/**
|
|
6014
|
+
* Slack's confirmation dialog (`c-dialog`): blocks the triggering action until the user confirms or
|
|
6015
|
+
* denies. Escape, the close button and a click on the overlay all deny.
|
|
6016
|
+
*
|
|
6017
|
+
* Slack marks it `role="dialog"`; this keeps `alertdialog`, the role WAI-ARIA gives a dialog that
|
|
6018
|
+
* interrupts to ask for a confirmation, so screen readers announce its text on open. Focus starts
|
|
6019
|
+
* on the deny button, the least destructive choice, stays inside while it's open, and goes back to
|
|
6020
|
+
* whatever opened it when it closes.
|
|
6021
|
+
*/
|
|
5986
6022
|
function ConfirmDialog({ confirm, onConfirm, onDeny }) {
|
|
5987
6023
|
const { theme } = useBlockKit();
|
|
5988
6024
|
const confirmStyle = confirm.style ?? "primary";
|
|
6025
|
+
const titleId = useId();
|
|
6026
|
+
const textId = useId();
|
|
6027
|
+
const dialogRef = useRef(null);
|
|
6028
|
+
const denyRef = useRef(null);
|
|
6029
|
+
useEffect(() => {
|
|
6030
|
+
const opener = document.activeElement;
|
|
6031
|
+
denyRef.current?.focus();
|
|
6032
|
+
return () => {
|
|
6033
|
+
if (opener?.isConnected) opener.focus();
|
|
6034
|
+
};
|
|
6035
|
+
}, []);
|
|
6036
|
+
function onKeyDown(event) {
|
|
6037
|
+
if (event.key === "Escape") {
|
|
6038
|
+
event.stopPropagation();
|
|
6039
|
+
onDeny();
|
|
6040
|
+
return;
|
|
6041
|
+
}
|
|
6042
|
+
if (event.key !== "Tab") return;
|
|
6043
|
+
const buttons = [...dialogRef.current?.querySelectorAll("button") ?? []];
|
|
6044
|
+
const first = buttons[0];
|
|
6045
|
+
const last = buttons.at(-1);
|
|
6046
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
6047
|
+
event.preventDefault();
|
|
6048
|
+
last?.focus();
|
|
6049
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
6050
|
+
event.preventDefault();
|
|
6051
|
+
first?.focus();
|
|
6052
|
+
}
|
|
6053
|
+
}
|
|
5989
6054
|
const node = /* @__PURE__ */ jsx("div", {
|
|
5990
6055
|
className: "sbk-confirm__overlay",
|
|
5991
6056
|
role: "presentation",
|
|
5992
6057
|
"data-theme": theme,
|
|
5993
6058
|
onClick: onDeny,
|
|
5994
6059
|
children: /* @__PURE__ */ jsxs("div", {
|
|
6060
|
+
ref: dialogRef,
|
|
5995
6061
|
className: "sbk-confirm",
|
|
5996
6062
|
role: "alertdialog",
|
|
5997
6063
|
"aria-modal": "true",
|
|
5998
|
-
"aria-
|
|
6064
|
+
"aria-labelledby": confirm.title ? titleId : void 0,
|
|
6065
|
+
"aria-label": confirm.title ? void 0 : confirm.confirm?.text ?? "Confirm",
|
|
6066
|
+
"aria-describedby": textId,
|
|
6067
|
+
onKeyDown,
|
|
5999
6068
|
onClick: (event) => event.stopPropagation(),
|
|
6000
6069
|
children: [
|
|
6001
|
-
|
|
6002
|
-
className: "sbk-
|
|
6003
|
-
children: confirm.title
|
|
6070
|
+
/* @__PURE__ */ jsxs("div", {
|
|
6071
|
+
className: "sbk-confirm__header",
|
|
6072
|
+
children: [confirm.title && /* @__PURE__ */ jsx("h2", {
|
|
6073
|
+
id: titleId,
|
|
6074
|
+
className: "sbk-confirm__title",
|
|
6075
|
+
children: confirm.title.text
|
|
6076
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
6077
|
+
type: "button",
|
|
6078
|
+
className: "sbk-confirm__close",
|
|
6079
|
+
"aria-label": "Close",
|
|
6080
|
+
onClick: onDeny,
|
|
6081
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {
|
|
6082
|
+
width: "20",
|
|
6083
|
+
height: "20"
|
|
6084
|
+
})
|
|
6085
|
+
})]
|
|
6004
6086
|
}),
|
|
6005
6087
|
/* @__PURE__ */ jsx("div", {
|
|
6088
|
+
id: textId,
|
|
6006
6089
|
className: "sbk-confirm__text",
|
|
6007
6090
|
children: /* @__PURE__ */ jsx(Text, { text: confirm.text })
|
|
6008
6091
|
}),
|
|
6009
6092
|
/* @__PURE__ */ jsxs("div", {
|
|
6010
6093
|
className: "sbk-confirm__actions",
|
|
6011
6094
|
children: [/* @__PURE__ */ jsx("button", {
|
|
6095
|
+
ref: denyRef,
|
|
6012
6096
|
type: "button",
|
|
6013
6097
|
className: "sbk-confirm__button sbk-confirm__button--deny",
|
|
6014
6098
|
onClick: onDeny,
|
|
@@ -6181,31 +6265,107 @@ function todayISO() {
|
|
|
6181
6265
|
const now = /* @__PURE__ */ new Date();
|
|
6182
6266
|
return toISO(now.getFullYear(), now.getMonth(), now.getDate());
|
|
6183
6267
|
}
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6268
|
+
/** Slack's calendar header icons, 20×20. */
|
|
6269
|
+
const NAV_PATHS = {
|
|
6270
|
+
"previous-year": "M11.03 2.78a.75.75 0 1 0-1.06-1.06L2.22 9.47a.75.75 0 0 0 0 1.06l7.75 7.75a.75.75 0 1 0 1.06-1.06L3.81 10zm6.25 0a.75.75 0 0 0-1.06-1.06L8.47 9.47a.75.75 0 0 0 0 1.06l7.75 7.75a.75.75 0 1 0 1.06-1.06L10.06 10z",
|
|
6271
|
+
"previous-month": "M14.53 1.72a.75.75 0 0 1 0 1.06L7.31 10l7.22 7.22a.75.75 0 1 1-1.06 1.06l-7.75-7.75a.75.75 0 0 1 0-1.06l7.75-7.75a.75.75 0 0 1 1.06 0",
|
|
6272
|
+
"next-month": "M5.72 1.72a.75.75 0 0 1 1.06 0l7.75 7.75a.75.75 0 0 1 0 1.06l-7.75 7.75a.75.75 0 0 1-1.06-1.06L12.94 10 5.72 2.78a.75.75 0 0 1 0-1.06",
|
|
6273
|
+
"next-year": "M3.78 1.72a.75.75 0 0 0-1.06 1.06L9.94 10l-7.22 7.22a.75.75 0 1 0 1.06 1.06l7.75-7.75a.75.75 0 0 0 0-1.06zm6.25 0a.75.75 0 1 0-1.06 1.06L16.19 10l-7.22 7.22a.75.75 0 1 0 1.06 1.06l7.75-7.75a.75.75 0 0 0 0-1.06z"
|
|
6274
|
+
};
|
|
6275
|
+
/** A header button that moves the calendar by `months`. */
|
|
6276
|
+
function NavButton({ icon, label, onClick }) {
|
|
6277
|
+
return /* @__PURE__ */ jsx("button", {
|
|
6278
|
+
type: "button",
|
|
6279
|
+
className: "sbk-calendar__nav",
|
|
6280
|
+
onClick,
|
|
6281
|
+
"aria-label": label,
|
|
6282
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
6283
|
+
viewBox: "0 0 20 20",
|
|
6284
|
+
width: "20",
|
|
6285
|
+
height: "20",
|
|
6286
|
+
"aria-hidden": "true",
|
|
6287
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
6288
|
+
fill: "currentColor",
|
|
6289
|
+
fillRule: "evenodd",
|
|
6290
|
+
clipRule: "evenodd",
|
|
6291
|
+
d: NAV_PATHS[icon]
|
|
6292
|
+
})
|
|
6195
6293
|
})
|
|
6196
6294
|
});
|
|
6197
6295
|
}
|
|
6296
|
+
/** The day `delta` days after the `YYYY-MM-DD` date, as `YYYY-MM-DD`. */
|
|
6297
|
+
function addDays(iso, delta) {
|
|
6298
|
+
const d = /* @__PURE__ */ new Date(`${iso}T00:00:00Z`);
|
|
6299
|
+
d.setUTCDate(d.getUTCDate() + delta);
|
|
6300
|
+
return toISO(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
|
|
6301
|
+
}
|
|
6302
|
+
/** Days the arrow keys move the focused day: a day sideways, a week up or down. */
|
|
6303
|
+
const ARROW_DAYS = {
|
|
6304
|
+
ArrowLeft: -1,
|
|
6305
|
+
ArrowRight: 1,
|
|
6306
|
+
ArrowUp: -7,
|
|
6307
|
+
ArrowDown: 7
|
|
6308
|
+
};
|
|
6198
6309
|
/** A month grid matching Slack's `c-date_picker_calendar`: a bordered 7-column grid of 44×42
|
|
6199
|
-
* day cells, a ring around today, and solid blue for the selected day.
|
|
6310
|
+
* day cells, a ring around today, and solid blue for the selected day. The grid is one tab stop:
|
|
6311
|
+
* the focused day (the selected one, else today, else the 1st) takes Tab, and the arrow keys move
|
|
6312
|
+
* it a day or a week, turning the month when they cross its edge. */
|
|
6200
6313
|
function Calendar({ value, onSelect, onClear }) {
|
|
6201
6314
|
const initial = value ? /* @__PURE__ */ new Date(`${value}T00:00:00Z`) : /* @__PURE__ */ new Date();
|
|
6202
6315
|
const [year, setYear] = useState(initial.getUTCFullYear());
|
|
6203
6316
|
const [month, setMonth] = useState(initial.getUTCMonth());
|
|
6317
|
+
const [focused, setFocused] = useState(() => {
|
|
6318
|
+
if (value) return value;
|
|
6319
|
+
const today = todayISO();
|
|
6320
|
+
return today.startsWith(toISO(year, month, 1).slice(0, 7)) ? today : toISO(year, month, 1);
|
|
6321
|
+
});
|
|
6322
|
+
const gridRef = useRef(null);
|
|
6323
|
+
const [keyboard, setKeyboard] = useState(false);
|
|
6324
|
+
const moveFocus = useRef(false);
|
|
6325
|
+
useEffect(() => {
|
|
6326
|
+
if (!moveFocus.current) return;
|
|
6327
|
+
moveFocus.current = false;
|
|
6328
|
+
gridRef.current?.querySelector(`[data-date="${focused}"]`)?.focus();
|
|
6329
|
+
}, [
|
|
6330
|
+
focused,
|
|
6331
|
+
year,
|
|
6332
|
+
month
|
|
6333
|
+
]);
|
|
6334
|
+
function onGridKeyDown(e) {
|
|
6335
|
+
const delta = ARROW_DAYS[e.key];
|
|
6336
|
+
if (delta === void 0) return;
|
|
6337
|
+
e.preventDefault();
|
|
6338
|
+
setKeyboard(true);
|
|
6339
|
+
const next = addDays(focused, delta);
|
|
6340
|
+
const [y = year, m = month + 1] = next.split("-").map(Number);
|
|
6341
|
+
moveFocus.current = true;
|
|
6342
|
+
setFocused(next);
|
|
6343
|
+
if (y !== year || m - 1 !== month) {
|
|
6344
|
+
setYear(y);
|
|
6345
|
+
setMonth(m - 1);
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
const shown = toISO(year, month, 1).slice(0, 7);
|
|
6349
|
+
const tabStop = focused.startsWith(shown) ? focused : toISO(year, month, 1);
|
|
6204
6350
|
const first = new Date(Date.UTC(year, month, 1));
|
|
6205
6351
|
const startWeekday = first.getUTCDay();
|
|
6206
6352
|
const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
6207
6353
|
const cells = [...Array(startWeekday).fill(null), ...Array.from({ length: daysInMonth }, (_, i) => i + 1)];
|
|
6208
6354
|
const today = todayISO();
|
|
6355
|
+
const lastRow = Math.floor((startWeekday + daysInMonth - 1) / 7);
|
|
6356
|
+
const lastRowFull = (startWeekday + daysInMonth) % 7 === 0;
|
|
6357
|
+
/** The grid's outer corners a day sits on, which Slack rounds. */
|
|
6358
|
+
function corners(day) {
|
|
6359
|
+
const at = startWeekday + day - 1;
|
|
6360
|
+
const row = Math.floor(at / 7);
|
|
6361
|
+
const col = at % 7;
|
|
6362
|
+
const out = [];
|
|
6363
|
+
if (day === 1 || row === 1 && col === 0) out.push("top-left");
|
|
6364
|
+
if (row === 0 && col === 6) out.push("top-right");
|
|
6365
|
+
if (row === lastRow && col === 0) out.push("bottom-left");
|
|
6366
|
+
if (day === daysInMonth || !lastRowFull && row === lastRow - 1 && col === 6) out.push("bottom-right");
|
|
6367
|
+
return out;
|
|
6368
|
+
}
|
|
6209
6369
|
function go(delta) {
|
|
6210
6370
|
const d = new Date(Date.UTC(year, month + delta, 1));
|
|
6211
6371
|
setYear(d.getUTCFullYear());
|
|
@@ -6216,50 +6376,59 @@ function Calendar({ value, onSelect, onClear }) {
|
|
|
6216
6376
|
year: "numeric",
|
|
6217
6377
|
timeZone: "UTC"
|
|
6218
6378
|
});
|
|
6219
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
6379
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
6220
6380
|
className: "sbk-calendar",
|
|
6221
6381
|
children: [
|
|
6222
6382
|
/* @__PURE__ */ jsxs("div", {
|
|
6223
6383
|
className: "sbk-calendar__header",
|
|
6224
6384
|
children: [
|
|
6225
|
-
/* @__PURE__ */ jsx(
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
onClick: () => go(-
|
|
6229
|
-
|
|
6230
|
-
|
|
6385
|
+
/* @__PURE__ */ jsx(NavButton, {
|
|
6386
|
+
icon: "previous-year",
|
|
6387
|
+
label: "Previous year",
|
|
6388
|
+
onClick: () => go(-12)
|
|
6389
|
+
}),
|
|
6390
|
+
/* @__PURE__ */ jsx(NavButton, {
|
|
6391
|
+
icon: "previous-month",
|
|
6392
|
+
label: "Previous month",
|
|
6393
|
+
onClick: () => go(-1)
|
|
6231
6394
|
}),
|
|
6232
6395
|
/* @__PURE__ */ jsx("span", {
|
|
6233
6396
|
className: "sbk-calendar__label",
|
|
6234
6397
|
children: monthLabel
|
|
6235
6398
|
}),
|
|
6236
|
-
/* @__PURE__ */ jsx(
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
onClick: () => go(1)
|
|
6240
|
-
|
|
6241
|
-
|
|
6399
|
+
/* @__PURE__ */ jsx(NavButton, {
|
|
6400
|
+
icon: "next-month",
|
|
6401
|
+
label: "Next month",
|
|
6402
|
+
onClick: () => go(1)
|
|
6403
|
+
}),
|
|
6404
|
+
/* @__PURE__ */ jsx(NavButton, {
|
|
6405
|
+
icon: "next-year",
|
|
6406
|
+
label: "Next year",
|
|
6407
|
+
onClick: () => go(12)
|
|
6242
6408
|
})
|
|
6243
6409
|
]
|
|
6244
6410
|
}),
|
|
6245
6411
|
/* @__PURE__ */ jsx("div", {
|
|
6246
6412
|
className: "sbk-calendar__weekdays",
|
|
6247
6413
|
children: [
|
|
6248
|
-
"
|
|
6249
|
-
"
|
|
6250
|
-
"
|
|
6251
|
-
"
|
|
6252
|
-
"
|
|
6253
|
-
"
|
|
6254
|
-
"
|
|
6414
|
+
"Su",
|
|
6415
|
+
"Mo",
|
|
6416
|
+
"Tu",
|
|
6417
|
+
"We",
|
|
6418
|
+
"Th",
|
|
6419
|
+
"Fr",
|
|
6420
|
+
"Sa"
|
|
6255
6421
|
].map((w) => /* @__PURE__ */ jsx("span", { children: w }, w))
|
|
6256
6422
|
}),
|
|
6257
6423
|
/* @__PURE__ */ jsx("div", {
|
|
6258
|
-
className: "sbk-calendar__grid"
|
|
6424
|
+
className: `sbk-calendar__grid${keyboard ? " sbk-calendar__grid--keyboard" : ""}`,
|
|
6425
|
+
ref: gridRef,
|
|
6426
|
+
onKeyDown: onGridKeyDown,
|
|
6427
|
+
onPointerDown: () => setKeyboard(false),
|
|
6259
6428
|
children: cells.map((day, i) => {
|
|
6260
6429
|
if (day === null) return /* @__PURE__ */ jsx("span", { className: "sbk-calendar__cell sbk-calendar__cell--empty" }, i);
|
|
6261
6430
|
const iso = toISO(year, month, day);
|
|
6262
|
-
const classes = ["sbk-calendar__cell"];
|
|
6431
|
+
const classes = ["sbk-calendar__cell", ...corners(day).map((c) => `sbk-calendar__cell--${c}`)];
|
|
6263
6432
|
if (iso === value) classes.push("sbk-calendar__cell--selected");
|
|
6264
6433
|
if (iso === today) classes.push("sbk-calendar__cell--today");
|
|
6265
6434
|
return /* @__PURE__ */ jsx("button", {
|
|
@@ -6267,22 +6436,24 @@ function Calendar({ value, onSelect, onClear }) {
|
|
|
6267
6436
|
className: classes.join(" "),
|
|
6268
6437
|
"aria-pressed": iso === value,
|
|
6269
6438
|
"aria-current": iso === today ? "date" : void 0,
|
|
6439
|
+
"data-date": iso,
|
|
6440
|
+
tabIndex: iso === tabStop ? 0 : -1,
|
|
6441
|
+
onFocus: () => setFocused(iso),
|
|
6270
6442
|
onClick: () => onSelect(iso),
|
|
6271
6443
|
children: day
|
|
6272
6444
|
}, iso);
|
|
6273
6445
|
})
|
|
6274
|
-
}),
|
|
6275
|
-
onClear && value && /* @__PURE__ */ jsx("div", {
|
|
6276
|
-
className: "sbk-calendar__footer",
|
|
6277
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
6278
|
-
type: "button",
|
|
6279
|
-
className: "sbk-calendar__clear",
|
|
6280
|
-
onClick: onClear,
|
|
6281
|
-
children: "Clear selection"
|
|
6282
|
-
})
|
|
6283
6446
|
})
|
|
6284
6447
|
]
|
|
6285
|
-
})
|
|
6448
|
+
}), onClear && value && /* @__PURE__ */ jsx("div", {
|
|
6449
|
+
className: "sbk-calendar__footer",
|
|
6450
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
6451
|
+
type: "button",
|
|
6452
|
+
className: "sbk-calendar__clear",
|
|
6453
|
+
onClick: onClear,
|
|
6454
|
+
children: "Clear selection"
|
|
6455
|
+
})
|
|
6456
|
+
})] });
|
|
6286
6457
|
}
|
|
6287
6458
|
//#endregion
|
|
6288
6459
|
//#region src/elements/dateFormat.ts
|
|
@@ -6381,6 +6552,11 @@ const InputBlockContext = createContext(false);
|
|
|
6381
6552
|
function useInInputBlock() {
|
|
6382
6553
|
return useContext(InputBlockContext);
|
|
6383
6554
|
}
|
|
6555
|
+
/** True while rendering the element of an `input` block marked `optional`, which Slack lets the user empty. */
|
|
6556
|
+
const InputOptionalContext = createContext(false);
|
|
6557
|
+
function useInOptionalInput() {
|
|
6558
|
+
return useContext(InputOptionalContext);
|
|
6559
|
+
}
|
|
6384
6560
|
/** The id of the error shown under an `input` block's element, while it has one. */
|
|
6385
6561
|
const InputErrorContext = createContext(void 0);
|
|
6386
6562
|
/**
|
|
@@ -6398,14 +6574,18 @@ function useInvalidProps() {
|
|
|
6398
6574
|
//#region src/elements/Popover.tsx
|
|
6399
6575
|
/** Space between the anchor and the popover, as in Slack's menus. */
|
|
6400
6576
|
const DEFAULT_GAP = 4;
|
|
6577
|
+
/** The least space kept between a popover and the window's left and right edges. */
|
|
6578
|
+
const EDGE = 8;
|
|
6401
6579
|
/**
|
|
6402
6580
|
* The layer a menu or calendar opens in. Slack mounts its popovers at the top of the page, so a
|
|
6403
6581
|
* message list, a modal body or a carousel never clips them; this portals to `<body>` the same way
|
|
6404
6582
|
* and keeps the popover pinned to its anchor as the page scrolls or resizes, or its content does.
|
|
6583
|
+
* Where keeping its alignment would run it past the window's left or right edge (a narrow phone
|
|
6584
|
+
* screen), it shifts just enough to stay inside.
|
|
6405
6585
|
* The layer is as wide as the anchor, so a child sized `width: 100%` matches the control it opened
|
|
6406
6586
|
* from.
|
|
6407
6587
|
*/
|
|
6408
|
-
function Popover({ anchorRef, onDismiss, gap = DEFAULT_GAP, children }) {
|
|
6588
|
+
function Popover({ anchorRef, onDismiss, gap = DEFAULT_GAP, offsetX = 0, onPlaced, children }) {
|
|
6409
6589
|
const { theme } = useBlockKit();
|
|
6410
6590
|
const layerRef = useRef(null);
|
|
6411
6591
|
const [position, setPosition] = useState(null);
|
|
@@ -6426,9 +6606,19 @@ function Popover({ anchorRef, onDismiss, gap = DEFAULT_GAP, children }) {
|
|
|
6426
6606
|
const fitsAbove = above >= 0;
|
|
6427
6607
|
let top = !fitsBelow && fitsAbove ? above : below;
|
|
6428
6608
|
if (!fitsBelow && !fitsAbove) top = Math.max(gap, Math.min(below, window.innerHeight - height - gap));
|
|
6609
|
+
layer.style.width = `${a.width}px`;
|
|
6610
|
+
const content = layer.firstElementChild?.getBoundingClientRect();
|
|
6611
|
+
const layerLeft = layer.getBoundingClientRect().left;
|
|
6612
|
+
let left = a.left + offsetX;
|
|
6613
|
+
if (content) {
|
|
6614
|
+
const contentLeft = left + content.left - layerLeft;
|
|
6615
|
+
const room = document.documentElement.clientWidth - EDGE;
|
|
6616
|
+
const shift = Math.max(EDGE - contentLeft, Math.min(0, room - (contentLeft + content.width)));
|
|
6617
|
+
left += shift;
|
|
6618
|
+
}
|
|
6429
6619
|
setPosition({
|
|
6430
6620
|
top,
|
|
6431
|
-
left
|
|
6621
|
+
left,
|
|
6432
6622
|
width: a.width
|
|
6433
6623
|
});
|
|
6434
6624
|
}
|
|
@@ -6443,7 +6633,18 @@ function Popover({ anchorRef, onDismiss, gap = DEFAULT_GAP, children }) {
|
|
|
6443
6633
|
window.removeEventListener("resize", place);
|
|
6444
6634
|
resize?.disconnect();
|
|
6445
6635
|
};
|
|
6446
|
-
}, [
|
|
6636
|
+
}, [
|
|
6637
|
+
anchorRef,
|
|
6638
|
+
gap,
|
|
6639
|
+
offsetX
|
|
6640
|
+
]);
|
|
6641
|
+
const placed = position !== null;
|
|
6642
|
+
const notified = useRef(false);
|
|
6643
|
+
useEffect(() => {
|
|
6644
|
+
if (!placed || notified.current) return;
|
|
6645
|
+
notified.current = true;
|
|
6646
|
+
onPlaced?.();
|
|
6647
|
+
}, [placed]);
|
|
6447
6648
|
useEffect(() => {
|
|
6448
6649
|
function onDocDown(e) {
|
|
6449
6650
|
const target = e.target;
|
|
@@ -6483,14 +6684,27 @@ function formatDate$1(date, long) {
|
|
|
6483
6684
|
function DatePicker({ element, blockId }) {
|
|
6484
6685
|
const { setValue, dispatch } = useBlockKit();
|
|
6485
6686
|
const inInputBlock = useInInputBlock();
|
|
6687
|
+
const clearable = useInOptionalInput();
|
|
6486
6688
|
const { ask, dialog } = useConfirm(element.confirm);
|
|
6487
6689
|
const [date, setDate] = useState(element.initial_date);
|
|
6488
6690
|
const [open, setOpen] = useState(false);
|
|
6489
6691
|
const rootRef = useRef(null);
|
|
6490
6692
|
const inputRef = useFocusOnLoad(element);
|
|
6491
6693
|
const invalid = useInvalidProps();
|
|
6492
|
-
const
|
|
6694
|
+
const quietFocus = useRef(element.focus_on_load === true);
|
|
6695
|
+
const focusCalendar = useRef(false);
|
|
6696
|
+
const popupRef = useRef(null);
|
|
6493
6697
|
const actionId = element.action_id ?? "";
|
|
6698
|
+
function focusDay() {
|
|
6699
|
+
popupRef.current?.querySelector(".sbk-calendar__grid [tabindex='0']")?.focus();
|
|
6700
|
+
}
|
|
6701
|
+
function close() {
|
|
6702
|
+
setOpen(false);
|
|
6703
|
+
const input = inputRef.current;
|
|
6704
|
+
if (!input || document.activeElement === input) return;
|
|
6705
|
+
quietFocus.current = true;
|
|
6706
|
+
input.focus();
|
|
6707
|
+
}
|
|
6494
6708
|
useEffect(() => {
|
|
6495
6709
|
if (date) setValue(blockId, actionId, {
|
|
6496
6710
|
type: "datepicker",
|
|
@@ -6500,7 +6714,7 @@ function DatePicker({ element, blockId }) {
|
|
|
6500
6714
|
async function pick(next) {
|
|
6501
6715
|
if (!await ask()) return;
|
|
6502
6716
|
setDate(next);
|
|
6503
|
-
|
|
6717
|
+
close();
|
|
6504
6718
|
setValue(blockId, actionId, {
|
|
6505
6719
|
type: "datepicker",
|
|
6506
6720
|
selected_date: next
|
|
@@ -6516,7 +6730,7 @@ function DatePicker({ element, blockId }) {
|
|
|
6516
6730
|
async function clear() {
|
|
6517
6731
|
if (!await ask()) return;
|
|
6518
6732
|
setDate(void 0);
|
|
6519
|
-
|
|
6733
|
+
close();
|
|
6520
6734
|
setValue(blockId, actionId, {
|
|
6521
6735
|
type: "datepicker",
|
|
6522
6736
|
selected_date: null
|
|
@@ -6547,12 +6761,27 @@ function DatePicker({ element, blockId }) {
|
|
|
6547
6761
|
...invalid,
|
|
6548
6762
|
ref: inputRef,
|
|
6549
6763
|
onFocus: () => {
|
|
6550
|
-
if (
|
|
6764
|
+
if (quietFocus.current) quietFocus.current = false;
|
|
6551
6765
|
else setOpen(true);
|
|
6552
6766
|
},
|
|
6553
|
-
|
|
6767
|
+
onMouseDown: () => {
|
|
6768
|
+
focusCalendar.current = true;
|
|
6769
|
+
},
|
|
6770
|
+
onClick: () => {
|
|
6771
|
+
if (open) focusDay();
|
|
6772
|
+
else focusCalendar.current = true;
|
|
6773
|
+
setOpen(true);
|
|
6774
|
+
},
|
|
6554
6775
|
onKeyDown: (e) => {
|
|
6555
6776
|
if (e.key === "Escape") setOpen(false);
|
|
6777
|
+
if (e.key === "ArrowDown") {
|
|
6778
|
+
e.preventDefault();
|
|
6779
|
+
if (open) focusDay();
|
|
6780
|
+
else {
|
|
6781
|
+
focusCalendar.current = true;
|
|
6782
|
+
setOpen(true);
|
|
6783
|
+
}
|
|
6784
|
+
}
|
|
6556
6785
|
}
|
|
6557
6786
|
}),
|
|
6558
6787
|
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-datepicker__chevron" })
|
|
@@ -6560,13 +6789,26 @@ function DatePicker({ element, blockId }) {
|
|
|
6560
6789
|
}),
|
|
6561
6790
|
open && /* @__PURE__ */ jsx(Popover, {
|
|
6562
6791
|
anchorRef: rootRef,
|
|
6792
|
+
gap: -4,
|
|
6563
6793
|
onDismiss: () => setOpen(false),
|
|
6794
|
+
onPlaced: () => {
|
|
6795
|
+
if (focusCalendar.current) focusDay();
|
|
6796
|
+
focusCalendar.current = false;
|
|
6797
|
+
},
|
|
6564
6798
|
children: /* @__PURE__ */ jsx("div", {
|
|
6565
6799
|
className: "sbk-datepicker__popup",
|
|
6800
|
+
ref: popupRef,
|
|
6801
|
+
role: "dialog",
|
|
6802
|
+
"aria-label": "Choose a date",
|
|
6803
|
+
onKeyDown: (e) => {
|
|
6804
|
+
if (e.key !== "Escape") return;
|
|
6805
|
+
e.preventDefault();
|
|
6806
|
+
close();
|
|
6807
|
+
},
|
|
6566
6808
|
children: /* @__PURE__ */ jsx(Calendar, {
|
|
6567
6809
|
value: date,
|
|
6568
6810
|
onSelect: pick,
|
|
6569
|
-
onClear: clear
|
|
6811
|
+
onClear: clearable ? clear : void 0
|
|
6570
6812
|
})
|
|
6571
6813
|
})
|
|
6572
6814
|
}),
|
|
@@ -6942,9 +7184,11 @@ function Overflow({ element, blockId }) {
|
|
|
6942
7184
|
const triggerRef = useRef(null);
|
|
6943
7185
|
const listRef = useRef(null);
|
|
6944
7186
|
const actionId = element.action_id ?? "";
|
|
7187
|
+
const idPrefix = useId();
|
|
6945
7188
|
async function choose(option) {
|
|
6946
7189
|
if (!await ask()) return;
|
|
6947
7190
|
setOpen(false);
|
|
7191
|
+
triggerRef.current?.focus();
|
|
6948
7192
|
dispatch({
|
|
6949
7193
|
type: "overflow",
|
|
6950
7194
|
action_id: actionId,
|
|
@@ -6985,11 +7229,15 @@ function Overflow({ element, blockId }) {
|
|
|
6985
7229
|
open && /* @__PURE__ */ jsx(Popover, {
|
|
6986
7230
|
anchorRef: rootRef,
|
|
6987
7231
|
onDismiss: () => setOpen(false),
|
|
7232
|
+
onPlaced: () => listRef.current?.focus(),
|
|
6988
7233
|
children: /* @__PURE__ */ jsx("div", {
|
|
6989
7234
|
className: "sbk-overflow__menu",
|
|
6990
7235
|
role: "menu",
|
|
6991
7236
|
ref: listRef,
|
|
7237
|
+
tabIndex: -1,
|
|
7238
|
+
"aria-activedescendant": nav.active >= 0 ? `${idPrefix}-${nav.active}` : void 0,
|
|
6992
7239
|
children: element.options.map((option, i) => /* @__PURE__ */ jsx("div", {
|
|
7240
|
+
id: `${idPrefix}-${i}`,
|
|
6993
7241
|
role: "menuitem",
|
|
6994
7242
|
className: "sbk-overflow__option",
|
|
6995
7243
|
onClick: () => choose(option),
|
|
@@ -7280,6 +7528,236 @@ function RichTextInput({ element, blockId }) {
|
|
|
7280
7528
|
});
|
|
7281
7529
|
}
|
|
7282
7530
|
//#endregion
|
|
7531
|
+
//#region src/elements/useCombobox.ts
|
|
7532
|
+
/**
|
|
7533
|
+
* The typeable trigger Slack's select and time pickers share (`c-select_input`): a text input with
|
|
7534
|
+
* role="combobox" that shows the chosen value while closed, and the query while its list is open.
|
|
7535
|
+
* Typing filters the list and highlights its first row; the arrow keys move the highlight, Enter
|
|
7536
|
+
* picks it, Escape and Tab close the list and put the chosen value back.
|
|
7537
|
+
*/
|
|
7538
|
+
function useCombobox({ query, onQueryChange, count, initialIndex, onChoose, onSubmitQuery, listRef, returnFocusRef, open: controlledOpen, onOpenChange }) {
|
|
7539
|
+
const [ownOpen, setOwnOpen] = useState(false);
|
|
7540
|
+
const open = controlledOpen ?? ownOpen;
|
|
7541
|
+
const setOpenState = onOpenChange ?? setOwnOpen;
|
|
7542
|
+
const inputRef = useRef(null);
|
|
7543
|
+
const id = useId();
|
|
7544
|
+
const listId = `${id}-list`;
|
|
7545
|
+
const optionId = (index) => `${id}-option-${index}`;
|
|
7546
|
+
function setOpen(next) {
|
|
7547
|
+
setOpenState(next);
|
|
7548
|
+
if (!next) onQueryChange("");
|
|
7549
|
+
}
|
|
7550
|
+
const nav = useMenuNavigation({
|
|
7551
|
+
open,
|
|
7552
|
+
count,
|
|
7553
|
+
initialIndex,
|
|
7554
|
+
onChoose,
|
|
7555
|
+
onClose: () => {
|
|
7556
|
+
setOpen(false);
|
|
7557
|
+
(returnFocusRef ?? inputRef).current?.focus();
|
|
7558
|
+
},
|
|
7559
|
+
onOpen: () => setOpenState(true),
|
|
7560
|
+
listRef
|
|
7561
|
+
});
|
|
7562
|
+
/** Keys for the input, or for a container around a button trigger and its list. */
|
|
7563
|
+
function onKeyDown(e) {
|
|
7564
|
+
if (e.key === "Tab") {
|
|
7565
|
+
if (open) setOpen(false);
|
|
7566
|
+
return;
|
|
7567
|
+
}
|
|
7568
|
+
if (open && e.key === "Enter" && nav.active < 0 && query.trim()) {
|
|
7569
|
+
e.preventDefault();
|
|
7570
|
+
onSubmitQuery?.(query.trim());
|
|
7571
|
+
return;
|
|
7572
|
+
}
|
|
7573
|
+
nav.onKeyDown(e);
|
|
7574
|
+
}
|
|
7575
|
+
/** Props for the input. `display` is the chosen value, shown while the list is closed. */
|
|
7576
|
+
function inputProps(display, placeholder) {
|
|
7577
|
+
return {
|
|
7578
|
+
ref: inputRef,
|
|
7579
|
+
type: "text",
|
|
7580
|
+
role: "combobox",
|
|
7581
|
+
autoComplete: "off",
|
|
7582
|
+
size: 1,
|
|
7583
|
+
spellCheck: false,
|
|
7584
|
+
"aria-autocomplete": "list",
|
|
7585
|
+
"aria-expanded": open,
|
|
7586
|
+
"aria-controls": open ? listId : void 0,
|
|
7587
|
+
"aria-activedescendant": open && nav.active >= 0 ? optionId(nav.active) : void 0,
|
|
7588
|
+
"aria-label": placeholder,
|
|
7589
|
+
placeholder: open ? display ?? placeholder : placeholder,
|
|
7590
|
+
value: open ? query : display ?? "",
|
|
7591
|
+
onClick: () => setOpenState(true),
|
|
7592
|
+
onChange: (e) => {
|
|
7593
|
+
onQueryChange(e.target.value);
|
|
7594
|
+
setOpenState(true);
|
|
7595
|
+
nav.setActive(0);
|
|
7596
|
+
},
|
|
7597
|
+
onKeyDown
|
|
7598
|
+
};
|
|
7599
|
+
}
|
|
7600
|
+
/** Props for the row at `index`, tying it to the input's aria-activedescendant. */
|
|
7601
|
+
function optionProps(index) {
|
|
7602
|
+
return {
|
|
7603
|
+
id: optionId(index),
|
|
7604
|
+
...nav.itemProps(index)
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
return {
|
|
7608
|
+
open,
|
|
7609
|
+
setOpen,
|
|
7610
|
+
onKeyDown,
|
|
7611
|
+
listId,
|
|
7612
|
+
inputRef,
|
|
7613
|
+
inputProps,
|
|
7614
|
+
optionProps,
|
|
7615
|
+
active: nav.active
|
|
7616
|
+
};
|
|
7617
|
+
}
|
|
7618
|
+
//#endregion
|
|
7619
|
+
//#region src/elements/accessoryContext.ts
|
|
7620
|
+
/**
|
|
7621
|
+
* True for an element rendered as a section block's accessory. Slack draws some elements
|
|
7622
|
+
* differently there: a multi-select becomes a small button that opens a "Select options" dialog.
|
|
7623
|
+
*/
|
|
7624
|
+
const SectionAccessoryContext = createContext(false);
|
|
7625
|
+
//#endregion
|
|
7626
|
+
//#region src/elements/SelectDialog.tsx
|
|
7627
|
+
const optionId = (option) => option.value ?? option.text.text;
|
|
7628
|
+
/**
|
|
7629
|
+
* Slack's "Select options" dialog, which a `multi_static_select` in a section accessory opens
|
|
7630
|
+
* instead of an inline menu. Picks are a draft: the option list stays open while choosing, and
|
|
7631
|
+
* nothing is sent until Confirm. Cancel, the close button and Escape discard the draft.
|
|
7632
|
+
*/
|
|
7633
|
+
function SelectDialog({ options, initial, placeholder, onConfirm, onCancel }) {
|
|
7634
|
+
const { theme } = useBlockKit();
|
|
7635
|
+
const titleId = useId();
|
|
7636
|
+
const [draft, setDraft] = useState(initial);
|
|
7637
|
+
const [query, setQuery] = useState("");
|
|
7638
|
+
const fieldRef = useRef(null);
|
|
7639
|
+
const listRef = useRef(null);
|
|
7640
|
+
const rows = options.filter((o) => o.text.text.toLowerCase().includes(query.toLowerCase()));
|
|
7641
|
+
const chosen = (option) => draft.some((d) => optionId(d) === optionId(option));
|
|
7642
|
+
const toggle = (option) => setDraft((current) => chosen(option) ? current.filter((d) => optionId(d) !== optionId(option)) : [...current, option]);
|
|
7643
|
+
const combo = useCombobox({
|
|
7644
|
+
query,
|
|
7645
|
+
onQueryChange: setQuery,
|
|
7646
|
+
count: rows.length,
|
|
7647
|
+
initialIndex: 0,
|
|
7648
|
+
onChoose: (i) => {
|
|
7649
|
+
const option = rows[i];
|
|
7650
|
+
if (option) toggle(option);
|
|
7651
|
+
},
|
|
7652
|
+
listRef
|
|
7653
|
+
});
|
|
7654
|
+
useEffect(() => {
|
|
7655
|
+
combo.inputRef.current?.focus();
|
|
7656
|
+
}, []);
|
|
7657
|
+
if (typeof document === "undefined") return null;
|
|
7658
|
+
return createPortal(/* @__PURE__ */ jsx("div", {
|
|
7659
|
+
className: "sbk-root sbk-select-dialog__overlay",
|
|
7660
|
+
"data-theme": theme,
|
|
7661
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
7662
|
+
className: "sbk-select-dialog",
|
|
7663
|
+
role: "dialog",
|
|
7664
|
+
"aria-modal": "true",
|
|
7665
|
+
"aria-labelledby": titleId,
|
|
7666
|
+
onKeyDown: (e) => {
|
|
7667
|
+
if (e.key !== "Escape") return;
|
|
7668
|
+
e.preventDefault();
|
|
7669
|
+
e.stopPropagation();
|
|
7670
|
+
onCancel();
|
|
7671
|
+
},
|
|
7672
|
+
children: [
|
|
7673
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7674
|
+
className: "sbk-select-dialog__header",
|
|
7675
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
7676
|
+
className: "sbk-select-dialog__title",
|
|
7677
|
+
id: titleId,
|
|
7678
|
+
children: "Select options"
|
|
7679
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7680
|
+
type: "button",
|
|
7681
|
+
className: "sbk-select-dialog__close",
|
|
7682
|
+
"aria-label": "Close",
|
|
7683
|
+
onClick: onCancel,
|
|
7684
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
7685
|
+
})]
|
|
7686
|
+
}),
|
|
7687
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7688
|
+
className: "sbk-select-dialog__body",
|
|
7689
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
7690
|
+
className: "sbk-select-dialog__field",
|
|
7691
|
+
ref: fieldRef,
|
|
7692
|
+
children: [draft.map((option) => /* @__PURE__ */ jsxs("span", {
|
|
7693
|
+
className: "sbk-select__chip",
|
|
7694
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
7695
|
+
className: "sbk-select__chip-label",
|
|
7696
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
7697
|
+
className: "sbk-select__chip-text",
|
|
7698
|
+
children: /* @__PURE__ */ jsx("span", { children: option.text.text })
|
|
7699
|
+
})
|
|
7700
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7701
|
+
type: "button",
|
|
7702
|
+
className: "sbk-select__chip-remove",
|
|
7703
|
+
"aria-label": `Remove ${option.text.text}`,
|
|
7704
|
+
onClick: () => toggle(option),
|
|
7705
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
7706
|
+
})]
|
|
7707
|
+
}, optionId(option))), /* @__PURE__ */ jsx("input", {
|
|
7708
|
+
...combo.inputProps(void 0, placeholder),
|
|
7709
|
+
placeholder: draft.length > 0 ? "" : placeholder,
|
|
7710
|
+
className: "sbk-select-dialog__input"
|
|
7711
|
+
})]
|
|
7712
|
+
}), combo.open && /* @__PURE__ */ jsx(Popover, {
|
|
7713
|
+
anchorRef: fieldRef,
|
|
7714
|
+
onDismiss: () => combo.setOpen(false),
|
|
7715
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
7716
|
+
className: "sbk-select__menu sbk-select-dialog__menu",
|
|
7717
|
+
role: "listbox",
|
|
7718
|
+
"aria-multiselectable": "true",
|
|
7719
|
+
id: combo.listId,
|
|
7720
|
+
ref: listRef,
|
|
7721
|
+
children: rows.map((option, i) => /* @__PURE__ */ jsx("div", {
|
|
7722
|
+
role: "option",
|
|
7723
|
+
"aria-selected": chosen(option),
|
|
7724
|
+
className: `sbk-select__option${chosen(option) ? " sbk-select__option--selected" : ""}`,
|
|
7725
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
7726
|
+
onClick: () => toggle(option),
|
|
7727
|
+
...combo.optionProps(i),
|
|
7728
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
7729
|
+
className: "sbk-select__option-text",
|
|
7730
|
+
children: /* @__PURE__ */ jsx(Text, { text: option.text })
|
|
7731
|
+
})
|
|
7732
|
+
}, optionId(option)))
|
|
7733
|
+
})
|
|
7734
|
+
})]
|
|
7735
|
+
}),
|
|
7736
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7737
|
+
className: "sbk-select-dialog__footer",
|
|
7738
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
7739
|
+
type: "button",
|
|
7740
|
+
className: "sbk-button",
|
|
7741
|
+
onClick: onCancel,
|
|
7742
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
7743
|
+
className: "sbk-button__label",
|
|
7744
|
+
children: "Cancel"
|
|
7745
|
+
})
|
|
7746
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7747
|
+
type: "button",
|
|
7748
|
+
className: "sbk-button sbk-button--primary",
|
|
7749
|
+
onClick: () => onConfirm(draft),
|
|
7750
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
7751
|
+
className: "sbk-button__label",
|
|
7752
|
+
children: "Confirm"
|
|
7753
|
+
})
|
|
7754
|
+
})]
|
|
7755
|
+
})
|
|
7756
|
+
]
|
|
7757
|
+
})
|
|
7758
|
+
}), document.body);
|
|
7759
|
+
}
|
|
7760
|
+
//#endregion
|
|
7283
7761
|
//#region src/elements/Select.tsx
|
|
7284
7762
|
function parseType(type) {
|
|
7285
7763
|
const multi = type.startsWith("multi_");
|
|
@@ -7349,7 +7827,6 @@ function Select({ element, blockId }) {
|
|
|
7349
7827
|
const [query, setQuery] = useState("");
|
|
7350
7828
|
const rootRef = useRef(null);
|
|
7351
7829
|
const triggerRef = useRef(null);
|
|
7352
|
-
useFocusOnLoad(element, triggerRef);
|
|
7353
7830
|
const invalid = useInvalidProps();
|
|
7354
7831
|
const listRef = useRef(null);
|
|
7355
7832
|
const remote = source === "external" && loadOptions !== void 0;
|
|
@@ -7470,28 +7947,166 @@ function Select({ element, blockId }) {
|
|
|
7470
7947
|
})) : [{ options: filteredOptions }];
|
|
7471
7948
|
const flatOptions = visibleGroups.flatMap((g) => g.options);
|
|
7472
7949
|
const isSelected = (option) => items.some((i) => i.id === (option.value ?? option.text.text));
|
|
7473
|
-
const
|
|
7950
|
+
const showSearchOnly = source !== "static";
|
|
7951
|
+
const typeable = source === "static" && !multi;
|
|
7952
|
+
const dialogMode = useContext(SectionAccessoryContext) && multi && source === "static";
|
|
7953
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
7954
|
+
const combo = useCombobox({
|
|
7474
7955
|
open,
|
|
7956
|
+
onOpenChange: setOpen,
|
|
7957
|
+
query,
|
|
7958
|
+
onQueryChange: setQuery,
|
|
7475
7959
|
count: flatOptions.length,
|
|
7476
7960
|
initialIndex: Math.max(0, flatOptions.findIndex(isSelected)),
|
|
7477
7961
|
onChoose: (i) => {
|
|
7478
7962
|
const option = flatOptions[i];
|
|
7479
7963
|
if (option) selectItem(optionToItem(option));
|
|
7480
7964
|
},
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7965
|
+
onSubmitQuery: (typed) => {
|
|
7966
|
+
if (showSearchOnly && !remote) selectItem({
|
|
7967
|
+
id: typed,
|
|
7968
|
+
label: resolveLabel(typed)
|
|
7969
|
+
});
|
|
7484
7970
|
},
|
|
7485
|
-
|
|
7486
|
-
|
|
7971
|
+
listRef,
|
|
7972
|
+
returnFocusRef: typeable ? void 0 : triggerRef
|
|
7487
7973
|
});
|
|
7974
|
+
useFocusOnLoad(element, typeable ? combo.inputRef : triggerRef);
|
|
7488
7975
|
const closedLabel = multi ? void 0 : items[0] ? items[0].label || resolveLabel(items[0].id) : void 0;
|
|
7489
7976
|
const placeholder = element.placeholder?.text ?? "Select an item";
|
|
7490
|
-
const
|
|
7977
|
+
const menu = open && /* @__PURE__ */ jsx(Popover, {
|
|
7978
|
+
anchorRef: rootRef,
|
|
7979
|
+
onDismiss: () => combo.setOpen(false),
|
|
7980
|
+
offsetX: typeable ? -12 : 0,
|
|
7981
|
+
gap: -4,
|
|
7982
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
7983
|
+
className: `sbk-select__menu${typeable ? " sbk-select__menu--typeable" : ""}`,
|
|
7984
|
+
role: "listbox",
|
|
7985
|
+
id: combo.listId,
|
|
7986
|
+
ref: listRef,
|
|
7987
|
+
children: [
|
|
7988
|
+
!typeable && (showSearchOnly || allOptions(element).length > 8) && /* @__PURE__ */ jsxs("div", {
|
|
7989
|
+
className: "sbk-select__search",
|
|
7990
|
+
children: [/* @__PURE__ */ jsx(SearchIcon$1, {}), /* @__PURE__ */ jsx("input", {
|
|
7991
|
+
autoFocus: true,
|
|
7992
|
+
className: "sbk-select__search-input",
|
|
7993
|
+
placeholder: source === "static" || source === "external" ? "Search options" : `Search ${source}`,
|
|
7994
|
+
value: query,
|
|
7995
|
+
onChange: (e) => setQuery(e.target.value)
|
|
7996
|
+
})]
|
|
7997
|
+
}),
|
|
7998
|
+
(() => {
|
|
7999
|
+
let index = 0;
|
|
8000
|
+
return visibleGroups.map((group, gi) => {
|
|
8001
|
+
const rows = group.options.map((option) => {
|
|
8002
|
+
const i = index++;
|
|
8003
|
+
return /* @__PURE__ */ jsx(SelectOption, {
|
|
8004
|
+
option,
|
|
8005
|
+
selected: isSelected(option),
|
|
8006
|
+
onSelect: () => selectItem(optionToItem(option)),
|
|
8007
|
+
navProps: combo.optionProps(i)
|
|
8008
|
+
}, option.value ?? i);
|
|
8009
|
+
});
|
|
8010
|
+
return group.label === void 0 ? rows : /* @__PURE__ */ jsxs("div", {
|
|
8011
|
+
className: "sbk-select__group",
|
|
8012
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
8013
|
+
className: "sbk-select__group-label",
|
|
8014
|
+
children: group.label
|
|
8015
|
+
}), rows]
|
|
8016
|
+
}, gi);
|
|
8017
|
+
});
|
|
8018
|
+
})(),
|
|
8019
|
+
remote && loading && /* @__PURE__ */ jsx("div", {
|
|
8020
|
+
className: "sbk-select__status",
|
|
8021
|
+
children: "Loading…"
|
|
8022
|
+
}),
|
|
8023
|
+
remote && !loading && remoteGroups !== void 0 && flatOptions.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
8024
|
+
className: "sbk-select__status",
|
|
8025
|
+
children: "No results"
|
|
8026
|
+
}),
|
|
8027
|
+
showSearchOnly && items.length > 0 && multi && /* @__PURE__ */ jsx("div", {
|
|
8028
|
+
className: "sbk-select__group-label",
|
|
8029
|
+
children: "Selected"
|
|
8030
|
+
}),
|
|
8031
|
+
showSearchOnly && multi && items.map((item) => /* @__PURE__ */ jsx("div", {
|
|
8032
|
+
className: "sbk-select__option sbk-select__option--selected",
|
|
8033
|
+
children: item.label || resolveLabel(item.id)
|
|
8034
|
+
}, item.id))
|
|
8035
|
+
]
|
|
8036
|
+
})
|
|
8037
|
+
});
|
|
8038
|
+
if (dialogMode) {
|
|
8039
|
+
const closeDialog = () => {
|
|
8040
|
+
setDialogOpen(false);
|
|
8041
|
+
triggerRef.current?.focus();
|
|
8042
|
+
};
|
|
8043
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
8044
|
+
className: `sbk-select sbk-select--multi${sizeClass}`,
|
|
8045
|
+
ref: rootRef,
|
|
8046
|
+
children: [
|
|
8047
|
+
/* @__PURE__ */ jsx("button", {
|
|
8048
|
+
ref: triggerRef,
|
|
8049
|
+
type: "button",
|
|
8050
|
+
className: "sbk-select__control",
|
|
8051
|
+
onClick: () => setDialogOpen(true),
|
|
8052
|
+
"aria-haspopup": "dialog",
|
|
8053
|
+
"aria-expanded": dialogOpen,
|
|
8054
|
+
...invalid,
|
|
8055
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
8056
|
+
className: "sbk-select__placeholder",
|
|
8057
|
+
children: items.length > 0 ? `${items.length} selected` : placeholder
|
|
8058
|
+
})
|
|
8059
|
+
}),
|
|
8060
|
+
dialogOpen && /* @__PURE__ */ jsx(SelectDialog, {
|
|
8061
|
+
options: allOptions(element),
|
|
8062
|
+
initial: items.flatMap((i) => i.option ? [i.option] : []),
|
|
8063
|
+
placeholder,
|
|
8064
|
+
onCancel: closeDialog,
|
|
8065
|
+
onConfirm: async (selected) => {
|
|
8066
|
+
closeDialog();
|
|
8067
|
+
if (!await ask()) return;
|
|
8068
|
+
await commit(selected.map(optionToItem));
|
|
8069
|
+
}
|
|
8070
|
+
}),
|
|
8071
|
+
dialog
|
|
8072
|
+
]
|
|
8073
|
+
});
|
|
8074
|
+
}
|
|
8075
|
+
if (typeable) {
|
|
8076
|
+
const display = items[0] !== void 0 && isUnresolved(items[0]) ? void 0 : closedLabel;
|
|
8077
|
+
const overlay = display !== void 0 && !open;
|
|
8078
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
8079
|
+
className: `sbk-select sbk-select--typeable${sizeClass}`,
|
|
8080
|
+
ref: rootRef,
|
|
8081
|
+
children: [
|
|
8082
|
+
/* @__PURE__ */ jsxs("label", {
|
|
8083
|
+
className: "sbk-select__control",
|
|
8084
|
+
children: [
|
|
8085
|
+
/* @__PURE__ */ jsx("input", {
|
|
8086
|
+
...combo.inputProps(display, placeholder),
|
|
8087
|
+
...invalid,
|
|
8088
|
+
className: `sbk-select__input${overlay ? " sbk-select__input--behind" : ""}`
|
|
8089
|
+
}),
|
|
8090
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-select__chevron" }),
|
|
8091
|
+
overlay && /* @__PURE__ */ jsx("span", {
|
|
8092
|
+
className: "sbk-select__content",
|
|
8093
|
+
"aria-hidden": "true",
|
|
8094
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
8095
|
+
className: "sbk-select__content-text",
|
|
8096
|
+
children: display
|
|
8097
|
+
})
|
|
8098
|
+
})
|
|
8099
|
+
]
|
|
8100
|
+
}),
|
|
8101
|
+
menu,
|
|
8102
|
+
dialog
|
|
8103
|
+
]
|
|
8104
|
+
});
|
|
8105
|
+
}
|
|
7491
8106
|
return /* @__PURE__ */ jsxs("div", {
|
|
7492
8107
|
className: `sbk-select${multi ? " sbk-select--multi" : ""}${multi && items.length > 0 ? " sbk-select--chips" : ""}${sizeClass}`,
|
|
7493
8108
|
ref: rootRef,
|
|
7494
|
-
onKeyDown:
|
|
8109
|
+
onKeyDown: combo.onKeyDown,
|
|
7495
8110
|
children: [
|
|
7496
8111
|
multi && element.max_selected_items && /* @__PURE__ */ jsxs("p", {
|
|
7497
8112
|
className: "sbk-select__max-info",
|
|
@@ -7550,70 +8165,7 @@ function Select({ element, blockId }) {
|
|
|
7550
8165
|
children: placeholder
|
|
7551
8166
|
}), /* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-select__chevron" })]
|
|
7552
8167
|
}),
|
|
7553
|
-
|
|
7554
|
-
anchorRef: rootRef,
|
|
7555
|
-
onDismiss: () => setOpen(false),
|
|
7556
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
7557
|
-
className: "sbk-select__menu",
|
|
7558
|
-
role: "listbox",
|
|
7559
|
-
ref: listRef,
|
|
7560
|
-
children: [
|
|
7561
|
-
(showSearchOnly || allOptions(element).length > 8) && /* @__PURE__ */ jsxs("div", {
|
|
7562
|
-
className: "sbk-select__search",
|
|
7563
|
-
children: [/* @__PURE__ */ jsx(SearchIcon$1, {}), /* @__PURE__ */ jsx("input", {
|
|
7564
|
-
autoFocus: true,
|
|
7565
|
-
className: "sbk-select__search-input",
|
|
7566
|
-
placeholder: source === "static" || source === "external" ? "Search options" : `Search ${source}`,
|
|
7567
|
-
value: query,
|
|
7568
|
-
onChange: (e) => setQuery(e.target.value),
|
|
7569
|
-
onKeyDown: (e) => {
|
|
7570
|
-
if (e.key === "Enter" && query.trim() && showSearchOnly && !remote) selectItem({
|
|
7571
|
-
id: query.trim(),
|
|
7572
|
-
label: resolveLabel(query.trim())
|
|
7573
|
-
});
|
|
7574
|
-
}
|
|
7575
|
-
})]
|
|
7576
|
-
}),
|
|
7577
|
-
(() => {
|
|
7578
|
-
let index = 0;
|
|
7579
|
-
return visibleGroups.map((group, gi) => {
|
|
7580
|
-
const rows = group.options.map((option) => {
|
|
7581
|
-
const i = index++;
|
|
7582
|
-
return /* @__PURE__ */ jsx(SelectOption, {
|
|
7583
|
-
option,
|
|
7584
|
-
selected: isSelected(option),
|
|
7585
|
-
onSelect: () => selectItem(optionToItem(option)),
|
|
7586
|
-
navProps: nav.itemProps(i)
|
|
7587
|
-
}, option.value ?? i);
|
|
7588
|
-
});
|
|
7589
|
-
return group.label === void 0 ? rows : /* @__PURE__ */ jsxs("div", {
|
|
7590
|
-
className: "sbk-select__group",
|
|
7591
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
7592
|
-
className: "sbk-select__group-label",
|
|
7593
|
-
children: group.label
|
|
7594
|
-
}), rows]
|
|
7595
|
-
}, gi);
|
|
7596
|
-
});
|
|
7597
|
-
})(),
|
|
7598
|
-
remote && loading && /* @__PURE__ */ jsx("div", {
|
|
7599
|
-
className: "sbk-select__status",
|
|
7600
|
-
children: "Loading…"
|
|
7601
|
-
}),
|
|
7602
|
-
remote && !loading && remoteGroups !== void 0 && flatOptions.length === 0 && /* @__PURE__ */ jsx("div", {
|
|
7603
|
-
className: "sbk-select__status",
|
|
7604
|
-
children: "No results"
|
|
7605
|
-
}),
|
|
7606
|
-
showSearchOnly && items.length > 0 && multi && /* @__PURE__ */ jsx("div", {
|
|
7607
|
-
className: "sbk-select__group-label",
|
|
7608
|
-
children: "Selected"
|
|
7609
|
-
}),
|
|
7610
|
-
showSearchOnly && multi && items.map((item) => /* @__PURE__ */ jsx("div", {
|
|
7611
|
-
className: "sbk-select__option sbk-select__option--selected",
|
|
7612
|
-
children: item.label || resolveLabel(item.id)
|
|
7613
|
-
}, item.id))
|
|
7614
|
-
]
|
|
7615
|
-
})
|
|
7616
|
-
}),
|
|
8168
|
+
menu,
|
|
7617
8169
|
dialog
|
|
7618
8170
|
]
|
|
7619
8171
|
});
|
|
@@ -7715,28 +8267,47 @@ function TextInput({ element, blockId }) {
|
|
|
7715
8267
|
}
|
|
7716
8268
|
//#endregion
|
|
7717
8269
|
//#region src/elements/TimePicker.tsx
|
|
7718
|
-
/** Formats `HH:mm` the way Slack's
|
|
8270
|
+
/** Formats `HH:mm` the way Slack's timepicker shows it, e.g. "1:37 PM". */
|
|
7719
8271
|
function formatTime(time) {
|
|
7720
8272
|
const [h = 0, m = 0] = time.split(":").map(Number);
|
|
7721
8273
|
const period = h >= 12 ? "PM" : "AM";
|
|
7722
8274
|
return `${h % 12 === 0 ? 12 : h % 12}:${String(m).padStart(2, "0")} ${period}`;
|
|
7723
8275
|
}
|
|
7724
|
-
/** Every
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
8276
|
+
/** Every hour of the day, as Slack's timepicker list offers. */
|
|
8277
|
+
const HOURS = Array.from({ length: 24 }, (_, h) => `${String(h).padStart(2, "0")}:00`);
|
|
8278
|
+
/**
|
|
8279
|
+
* Reads a typed time as `HH:mm`: "2:15 PM", "2:15pm", "2pm", "14:15" or "9". Without AM or PM the
|
|
8280
|
+
* hour is read on a 24-hour clock. Slack accepts typed times too; whether it keeps one off the hour
|
|
8281
|
+
* (2:15 PM) or rounds it hasn't been verified, so the minutes are kept as typed.
|
|
8282
|
+
*/
|
|
8283
|
+
function parseTime(input) {
|
|
8284
|
+
const match = /^(\d{1,2})(?::?(\d{2}))?\s*([ap])\.?m?\.?$|^(\d{1,2})(?::(\d{2}))?$/i.exec(input.trim());
|
|
8285
|
+
if (!match) return void 0;
|
|
8286
|
+
const meridiem = match[3]?.toLowerCase();
|
|
8287
|
+
let hour = Number(match[1] ?? match[4]);
|
|
8288
|
+
const minute = Number(match[2] ?? match[5] ?? 0);
|
|
8289
|
+
if (minute > 59) return void 0;
|
|
8290
|
+
if (meridiem) {
|
|
8291
|
+
if (hour < 1 || hour > 12) return void 0;
|
|
8292
|
+
hour = hour % 12 + (meridiem === "p" ? 12 : 0);
|
|
8293
|
+
} else if (hour > 23) return;
|
|
8294
|
+
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
8295
|
+
}
|
|
8296
|
+
const squash = (text) => text.toLowerCase().replace(/\s+/g, "");
|
|
8297
|
+
/** The rows for a query: the hours whose label starts with it, after a typed time the list lacks. */
|
|
8298
|
+
function timesFor(query) {
|
|
8299
|
+
if (!query.trim()) return HOURS;
|
|
8300
|
+
const typed = parseTime(query);
|
|
8301
|
+
const hours = HOURS.filter((t) => squash(formatTime(t)).startsWith(squash(query)));
|
|
8302
|
+
return typed && !hours.includes(typed) && !HOURS.includes(typed) ? [typed, ...hours] : hours;
|
|
7729
8303
|
}
|
|
7730
8304
|
function TimePicker({ element, blockId }) {
|
|
7731
8305
|
const { setValue, dispatch } = useBlockKit();
|
|
7732
8306
|
const { ask, dialog } = useConfirm(element.confirm);
|
|
7733
8307
|
const [time, setTime] = useState(element.initial_time);
|
|
7734
|
-
const [open, setOpen] = useState(false);
|
|
7735
8308
|
const rootRef = useRef(null);
|
|
7736
|
-
const triggerRef = useRef(null);
|
|
7737
|
-
useFocusOnLoad(element, triggerRef);
|
|
7738
|
-
const invalid = useInvalidProps();
|
|
7739
8309
|
const listRef = useRef(null);
|
|
8310
|
+
const invalid = useInvalidProps();
|
|
7740
8311
|
const actionId = element.action_id ?? "";
|
|
7741
8312
|
useEffect(() => {
|
|
7742
8313
|
if (time) setValue(blockId, actionId, {
|
|
@@ -7745,9 +8316,9 @@ function TimePicker({ element, blockId }) {
|
|
|
7745
8316
|
});
|
|
7746
8317
|
}, []);
|
|
7747
8318
|
async function pick(next) {
|
|
8319
|
+
combo.setOpen(false);
|
|
7748
8320
|
if (!await ask()) return;
|
|
7749
8321
|
setTime(next);
|
|
7750
|
-
setOpen(false);
|
|
7751
8322
|
setValue(blockId, actionId, {
|
|
7752
8323
|
type: "timepicker",
|
|
7753
8324
|
selected_time: next
|
|
@@ -7760,57 +8331,68 @@ function TimePicker({ element, blockId }) {
|
|
|
7760
8331
|
...element.initial_time !== void 0 ? { initial_time: element.initial_time } : {}
|
|
7761
8332
|
});
|
|
7762
8333
|
}
|
|
7763
|
-
const
|
|
7764
|
-
const
|
|
7765
|
-
|
|
8334
|
+
const [query, setQuery] = useState("");
|
|
8335
|
+
const times = timesFor(query);
|
|
8336
|
+
const combo = useCombobox({
|
|
8337
|
+
query,
|
|
8338
|
+
onQueryChange: setQuery,
|
|
7766
8339
|
count: times.length,
|
|
7767
8340
|
initialIndex: time ? times.indexOf(time) : -1,
|
|
7768
8341
|
onChoose: (i) => {
|
|
7769
8342
|
const t = times[i];
|
|
7770
8343
|
if (t) pick(t);
|
|
7771
8344
|
},
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
8345
|
+
onSubmitQuery: (typed) => {
|
|
8346
|
+
const t = parseTime(typed);
|
|
8347
|
+
if (t) pick(t);
|
|
7775
8348
|
},
|
|
7776
|
-
onOpen: () => setOpen(true),
|
|
7777
8349
|
listRef
|
|
7778
8350
|
});
|
|
8351
|
+
useFocusOnLoad(element, combo.inputRef);
|
|
8352
|
+
const display = time ? formatTime(time) : void 0;
|
|
8353
|
+
const input = combo.inputProps(display, element.placeholder?.text ?? "Select time");
|
|
8354
|
+
const overlay = display !== void 0 && !combo.open;
|
|
7779
8355
|
return /* @__PURE__ */ jsxs("div", {
|
|
7780
8356
|
className: "sbk-timepicker",
|
|
7781
8357
|
ref: rootRef,
|
|
7782
|
-
onKeyDown: nav.onKeyDown,
|
|
7783
8358
|
children: [
|
|
7784
|
-
/* @__PURE__ */ jsxs("
|
|
7785
|
-
ref: triggerRef,
|
|
7786
|
-
type: "button",
|
|
8359
|
+
/* @__PURE__ */ jsxs("label", {
|
|
7787
8360
|
className: "sbk-timepicker__control",
|
|
7788
|
-
onClick: () => setOpen((o) => !o),
|
|
7789
|
-
"aria-haspopup": "listbox",
|
|
7790
|
-
"aria-expanded": open,
|
|
7791
|
-
...invalid,
|
|
7792
8361
|
children: [
|
|
7793
8362
|
/* @__PURE__ */ jsx(ClockIcon, { className: "sbk-timepicker__icon" }),
|
|
7794
|
-
/* @__PURE__ */ jsx("
|
|
7795
|
-
|
|
7796
|
-
|
|
8363
|
+
/* @__PURE__ */ jsx("input", {
|
|
8364
|
+
...input,
|
|
8365
|
+
...invalid,
|
|
8366
|
+
className: `sbk-timepicker__input${overlay ? " sbk-timepicker__input--behind" : ""}`
|
|
7797
8367
|
}),
|
|
7798
|
-
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-timepicker__chevron" })
|
|
8368
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-timepicker__chevron" }),
|
|
8369
|
+
overlay && /* @__PURE__ */ jsx("span", {
|
|
8370
|
+
className: "sbk-timepicker__content",
|
|
8371
|
+
"aria-hidden": "true",
|
|
8372
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
8373
|
+
className: "sbk-timepicker__content-text",
|
|
8374
|
+
children: display
|
|
8375
|
+
})
|
|
8376
|
+
})
|
|
7799
8377
|
]
|
|
7800
8378
|
}),
|
|
7801
|
-
open && /* @__PURE__ */ jsx(Popover, {
|
|
8379
|
+
combo.open && /* @__PURE__ */ jsx(Popover, {
|
|
7802
8380
|
anchorRef: rootRef,
|
|
7803
|
-
onDismiss: () => setOpen(false),
|
|
8381
|
+
onDismiss: () => combo.setOpen(false),
|
|
8382
|
+
offsetX: -12,
|
|
8383
|
+
gap: -4,
|
|
7804
8384
|
children: /* @__PURE__ */ jsx("div", {
|
|
7805
8385
|
className: "sbk-timepicker__menu",
|
|
7806
8386
|
role: "listbox",
|
|
8387
|
+
id: combo.listId,
|
|
7807
8388
|
ref: listRef,
|
|
7808
8389
|
children: times.map((t, i) => /* @__PURE__ */ jsx("div", {
|
|
7809
8390
|
role: "option",
|
|
7810
8391
|
"aria-selected": t === time,
|
|
7811
8392
|
className: `sbk-timepicker__option${t === time ? " sbk-timepicker__option--selected" : ""}`,
|
|
8393
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
7812
8394
|
onClick: () => pick(t),
|
|
7813
|
-
...
|
|
8395
|
+
...combo.optionProps(i),
|
|
7814
8396
|
children: formatTime(t)
|
|
7815
8397
|
}, t))
|
|
7816
8398
|
})
|
|
@@ -9663,15 +10245,18 @@ function Input({ block, blockId }) {
|
|
|
9663
10245
|
className: "sbk-input__element",
|
|
9664
10246
|
children: /* @__PURE__ */ jsx(InputBlockContext.Provider, {
|
|
9665
10247
|
value: true,
|
|
9666
|
-
children: /* @__PURE__ */ jsx(
|
|
9667
|
-
value:
|
|
9668
|
-
children:
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
10248
|
+
children: /* @__PURE__ */ jsx(InputOptionalContext.Provider, {
|
|
10249
|
+
value: block.optional === true,
|
|
10250
|
+
children: /* @__PURE__ */ jsx(InputErrorContext.Provider, {
|
|
10251
|
+
value: error ? errorId : void 0,
|
|
10252
|
+
children: block.dispatch_action === true ? /* @__PURE__ */ jsx(Element, {
|
|
10253
|
+
element,
|
|
10254
|
+
blockId
|
|
10255
|
+
}) : /* @__PURE__ */ jsx(SuppressActions, { children: /* @__PURE__ */ jsx(Element, {
|
|
10256
|
+
element,
|
|
10257
|
+
blockId
|
|
10258
|
+
}) })
|
|
10259
|
+
})
|
|
9675
10260
|
})
|
|
9676
10261
|
})
|
|
9677
10262
|
}),
|
|
@@ -10110,6 +10695,7 @@ function TaskRow({ task }) {
|
|
|
10110
10695
|
const expandable = Boolean(task.details || task.output || sources.length > 0);
|
|
10111
10696
|
return /* @__PURE__ */ jsxs("li", {
|
|
10112
10697
|
className: "sbk-plan__task",
|
|
10698
|
+
tabIndex: 0,
|
|
10113
10699
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
10114
10700
|
className: "sbk-plan__step",
|
|
10115
10701
|
"aria-hidden": "true",
|
|
@@ -10560,9 +11146,12 @@ function Section({ block, blockId }) {
|
|
|
10560
11146
|
})]
|
|
10561
11147
|
}), accessory && /* @__PURE__ */ jsx("div", {
|
|
10562
11148
|
className: `sbk-section__accessory sbk-section__accessory--${accessory.type}`,
|
|
10563
|
-
children: /* @__PURE__ */ jsx(
|
|
10564
|
-
|
|
10565
|
-
|
|
11149
|
+
children: /* @__PURE__ */ jsx(SectionAccessoryContext.Provider, {
|
|
11150
|
+
value: true,
|
|
11151
|
+
children: /* @__PURE__ */ jsx(Element, {
|
|
11152
|
+
element: accessory,
|
|
11153
|
+
blockId
|
|
11154
|
+
})
|
|
10566
11155
|
})
|
|
10567
11156
|
})]
|
|
10568
11157
|
});
|