@nkootstra/block-kit 0.3.1 → 0.3.3
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.js +85 -26
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6285,6 +6285,39 @@ const TIME_ZONE_LABELS = {
|
|
|
6285
6285
|
function timeZoneLabel(timeZone) {
|
|
6286
6286
|
return TIME_ZONE_LABELS[timeZone] ?? timeZone;
|
|
6287
6287
|
}
|
|
6288
|
+
/** A timestamp's wall-clock date ("2026-01-01") and time ("11:00") in `timeZone`. */
|
|
6289
|
+
function wallClock(ts, timeZone) {
|
|
6290
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
6291
|
+
timeZone,
|
|
6292
|
+
year: "numeric",
|
|
6293
|
+
month: "2-digit",
|
|
6294
|
+
day: "2-digit",
|
|
6295
|
+
hour: "2-digit",
|
|
6296
|
+
minute: "2-digit",
|
|
6297
|
+
hourCycle: "h23"
|
|
6298
|
+
}).formatToParts(/* @__PURE__ */ new Date(ts * 1e3));
|
|
6299
|
+
const get = (type) => parts.find((p) => p.type === type)?.value ?? "00";
|
|
6300
|
+
return {
|
|
6301
|
+
date: `${get("year")}-${get("month")}-${get("day")}`,
|
|
6302
|
+
time: `${get("hour")}:${get("minute")}`
|
|
6303
|
+
};
|
|
6304
|
+
}
|
|
6305
|
+
/** The timestamp of a wall-clock date and time in `timeZone`. Around a daylight saving change it
|
|
6306
|
+
* resolves like JavaScript's `Date`: a time the clocks skip moves forward by the gap, and a time
|
|
6307
|
+
* they repeat takes its first occurrence. */
|
|
6308
|
+
function fromWallClock(date, time, timeZone) {
|
|
6309
|
+
const asUtc = Date.parse(`${date}T${time}:00Z`) / 1e3;
|
|
6310
|
+
const offsetAt = (ts) => {
|
|
6311
|
+
const wall = wallClock(ts, timeZone);
|
|
6312
|
+
return Date.parse(`${wall.date}T${wall.time}:00Z`) / 1e3 - ts;
|
|
6313
|
+
};
|
|
6314
|
+
const before = asUtc - offsetAt(asUtc - 86400);
|
|
6315
|
+
const matches = [before, asUtc - offsetAt(asUtc + 86400)].filter((ts) => {
|
|
6316
|
+
const wall = wallClock(ts, timeZone);
|
|
6317
|
+
return wall.date === date && wall.time === time;
|
|
6318
|
+
});
|
|
6319
|
+
return matches.length > 0 ? Math.min(...matches) : before;
|
|
6320
|
+
}
|
|
6288
6321
|
//#endregion
|
|
6289
6322
|
//#region src/elements/inputBlockContext.ts
|
|
6290
6323
|
/** True while rendering the element of an `input` block, where Slack lays some elements out differently. */
|
|
@@ -6292,6 +6325,19 @@ const InputBlockContext = createContext(false);
|
|
|
6292
6325
|
function useInInputBlock() {
|
|
6293
6326
|
return useContext(InputBlockContext);
|
|
6294
6327
|
}
|
|
6328
|
+
/** The id of the error shown under an `input` block's element, while it has one. */
|
|
6329
|
+
const InputErrorContext = createContext(void 0);
|
|
6330
|
+
/**
|
|
6331
|
+
* Props for the element's focusable control: an `input` block with an error marks it invalid,
|
|
6332
|
+
* which also gives it Slack's red focus ring, and describes it with the error text.
|
|
6333
|
+
*/
|
|
6334
|
+
function useInvalidProps() {
|
|
6335
|
+
const errorId = useContext(InputErrorContext);
|
|
6336
|
+
return errorId ? {
|
|
6337
|
+
"aria-invalid": true,
|
|
6338
|
+
"aria-describedby": errorId
|
|
6339
|
+
} : {};
|
|
6340
|
+
}
|
|
6295
6341
|
//#endregion
|
|
6296
6342
|
//#region src/elements/Popover.tsx
|
|
6297
6343
|
/** Space between the anchor and the popover, as in Slack's menus. */
|
|
@@ -6386,6 +6432,7 @@ function DatePicker({ element, blockId }) {
|
|
|
6386
6432
|
const [open, setOpen] = useState(false);
|
|
6387
6433
|
const rootRef = useRef(null);
|
|
6388
6434
|
const inputRef = useFocusOnLoad(element);
|
|
6435
|
+
const invalid = useInvalidProps();
|
|
6389
6436
|
const loadFocus = useRef(element.focus_on_load === true);
|
|
6390
6437
|
const actionId = element.action_id ?? "";
|
|
6391
6438
|
useEffect(() => {
|
|
@@ -6439,6 +6486,7 @@ function DatePicker({ element, blockId }) {
|
|
|
6439
6486
|
placeholder: element.placeholder?.text ?? "Select a date",
|
|
6440
6487
|
"aria-haspopup": "dialog",
|
|
6441
6488
|
"aria-expanded": open,
|
|
6489
|
+
...invalid,
|
|
6442
6490
|
ref: inputRef,
|
|
6443
6491
|
onFocus: () => {
|
|
6444
6492
|
if (loadFocus.current) loadFocus.current = false;
|
|
@@ -6470,16 +6518,6 @@ function DatePicker({ element, blockId }) {
|
|
|
6470
6518
|
}
|
|
6471
6519
|
//#endregion
|
|
6472
6520
|
//#region src/elements/DateTimePicker.tsx
|
|
6473
|
-
function toISODate(ts) {
|
|
6474
|
-
return (/* @__PURE__ */ new Date(ts * 1e3)).toISOString().slice(0, 10);
|
|
6475
|
-
}
|
|
6476
|
-
function toHM(ts) {
|
|
6477
|
-
const d = /* @__PURE__ */ new Date(ts * 1e3);
|
|
6478
|
-
return `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}`;
|
|
6479
|
-
}
|
|
6480
|
-
function combine(date, time) {
|
|
6481
|
-
return Math.floor((/* @__PURE__ */ new Date(`${date}T${time}:00Z`)).getTime() / 1e3);
|
|
6482
|
-
}
|
|
6483
6521
|
/** Slack's Builder splits a datetimepicker's closed control into a date box ("January 1st,
|
|
6484
6522
|
* 2026") and a separate time box ("11:00 AM"), rather than one combined string. */
|
|
6485
6523
|
function formatDate(ts, timeZone) {
|
|
@@ -6488,7 +6526,7 @@ function formatDate(ts, timeZone) {
|
|
|
6488
6526
|
month: "long",
|
|
6489
6527
|
day: "numeric",
|
|
6490
6528
|
year: "numeric",
|
|
6491
|
-
timeZone
|
|
6529
|
+
timeZone
|
|
6492
6530
|
}).formatToParts(d);
|
|
6493
6531
|
const month = parts.find((p) => p.type === "month")?.value ?? "";
|
|
6494
6532
|
const day = Number(parts.find((p) => p.type === "day")?.value ?? "1");
|
|
@@ -6499,7 +6537,7 @@ function formatTime$1(ts, timeZone) {
|
|
|
6499
6537
|
return (/* @__PURE__ */ new Date(ts * 1e3)).toLocaleString("en-US", {
|
|
6500
6538
|
hour: "numeric",
|
|
6501
6539
|
minute: "2-digit",
|
|
6502
|
-
timeZone
|
|
6540
|
+
timeZone
|
|
6503
6541
|
});
|
|
6504
6542
|
}
|
|
6505
6543
|
function DateTimePicker({ element, blockId }) {
|
|
@@ -6507,8 +6545,10 @@ function DateTimePicker({ element, blockId }) {
|
|
|
6507
6545
|
const { ask, dialog } = useConfirm(element.confirm);
|
|
6508
6546
|
const [value, setValueState] = useState(element.initial_date_time);
|
|
6509
6547
|
const [open, setOpen] = useState(false);
|
|
6510
|
-
const
|
|
6511
|
-
const
|
|
6548
|
+
const zone = timeZone ?? "UTC";
|
|
6549
|
+
const initial = element.initial_date_time !== void 0 ? wallClock(element.initial_date_time, zone) : void 0;
|
|
6550
|
+
const [draftDate, setDraftDate] = useState(initial?.date);
|
|
6551
|
+
const [draftTime, setDraftTime] = useState(initial?.time ?? "12:00");
|
|
6512
6552
|
const rootRef = useRef(null);
|
|
6513
6553
|
const focusRef = useFocusOnLoad(element);
|
|
6514
6554
|
const actionId = element.action_id ?? "";
|
|
@@ -6521,7 +6561,7 @@ function DateTimePicker({ element, blockId }) {
|
|
|
6521
6561
|
async function apply() {
|
|
6522
6562
|
if (!draftDate) return;
|
|
6523
6563
|
if (!await ask()) return;
|
|
6524
|
-
const ts =
|
|
6564
|
+
const ts = fromWallClock(draftDate, draftTime, zone);
|
|
6525
6565
|
setValueState(ts);
|
|
6526
6566
|
setOpen(false);
|
|
6527
6567
|
setValue(blockId, actionId, {
|
|
@@ -6552,7 +6592,7 @@ function DateTimePicker({ element, blockId }) {
|
|
|
6552
6592
|
/* @__PURE__ */ jsx(CalendarIcon, { className: "sbk-datetimepicker__icon" }),
|
|
6553
6593
|
/* @__PURE__ */ jsx("span", {
|
|
6554
6594
|
className: value !== void 0 ? "sbk-datetimepicker__value" : "sbk-datetimepicker__placeholder",
|
|
6555
|
-
children: value !== void 0 ? formatDate(value,
|
|
6595
|
+
children: value !== void 0 ? formatDate(value, zone) : "Select a date"
|
|
6556
6596
|
}),
|
|
6557
6597
|
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-datetimepicker__chevron" })
|
|
6558
6598
|
]
|
|
@@ -6567,13 +6607,13 @@ function DateTimePicker({ element, blockId }) {
|
|
|
6567
6607
|
/* @__PURE__ */ jsx(ClockIcon, { className: "sbk-datetimepicker__icon" }),
|
|
6568
6608
|
/* @__PURE__ */ jsx("span", {
|
|
6569
6609
|
className: value !== void 0 ? "sbk-datetimepicker__value" : "sbk-datetimepicker__placeholder",
|
|
6570
|
-
children: value !== void 0 ? formatTime$1(value,
|
|
6610
|
+
children: value !== void 0 ? formatTime$1(value, zone) : "Select a time"
|
|
6571
6611
|
}),
|
|
6572
6612
|
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-datetimepicker__chevron" })
|
|
6573
6613
|
]
|
|
6574
6614
|
}), /* @__PURE__ */ jsxs("p", {
|
|
6575
6615
|
className: "sbk-datetimepicker__timezone",
|
|
6576
|
-
children: ["Time zone: ", timeZoneLabel(
|
|
6616
|
+
children: ["Time zone: ", timeZoneLabel(zone)]
|
|
6577
6617
|
})]
|
|
6578
6618
|
})]
|
|
6579
6619
|
}),
|
|
@@ -7072,6 +7112,7 @@ function RichTextInput({ element, blockId }) {
|
|
|
7072
7112
|
const { setValue, dispatch } = useBlockKit();
|
|
7073
7113
|
const ref = useRef(null);
|
|
7074
7114
|
useFocusOnLoad(element, ref);
|
|
7115
|
+
const invalid = useInvalidProps();
|
|
7075
7116
|
const actionId = element.action_id ?? "";
|
|
7076
7117
|
const initialText = fromRichText(element.initial_value);
|
|
7077
7118
|
useEffect(() => {
|
|
@@ -7102,6 +7143,7 @@ function RichTextInput({ element, blockId }) {
|
|
|
7102
7143
|
suppressContentEditableWarning: true,
|
|
7103
7144
|
role: "textbox",
|
|
7104
7145
|
"aria-multiline": "true",
|
|
7146
|
+
...invalid,
|
|
7105
7147
|
"aria-label": element.placeholder?.text ?? actionId,
|
|
7106
7148
|
"data-placeholder": element.placeholder?.text,
|
|
7107
7149
|
onInput,
|
|
@@ -7184,6 +7226,7 @@ function Select({ element, blockId }) {
|
|
|
7184
7226
|
const rootRef = useRef(null);
|
|
7185
7227
|
const triggerRef = useRef(null);
|
|
7186
7228
|
useFocusOnLoad(element, triggerRef);
|
|
7229
|
+
const invalid = useInvalidProps();
|
|
7187
7230
|
const listRef = useRef(null);
|
|
7188
7231
|
const remote = source === "external" && loadOptions !== void 0;
|
|
7189
7232
|
const minQueryLength = element.min_query_length ?? DEFAULT_MIN_QUERY_LENGTH;
|
|
@@ -7342,6 +7385,7 @@ function Select({ element, blockId }) {
|
|
|
7342
7385
|
onClick: () => setOpen((o) => !o),
|
|
7343
7386
|
"aria-haspopup": "listbox",
|
|
7344
7387
|
"aria-expanded": open,
|
|
7388
|
+
...invalid,
|
|
7345
7389
|
children: [multi ? items.length > 0 ? /* @__PURE__ */ jsx("span", {
|
|
7346
7390
|
className: "sbk-select__chips",
|
|
7347
7391
|
children: items.map((item) => /* @__PURE__ */ jsxs("span", {
|
|
@@ -7361,6 +7405,12 @@ function Select({ element, blockId }) {
|
|
|
7361
7405
|
e.stopPropagation();
|
|
7362
7406
|
removeItem(item.id);
|
|
7363
7407
|
},
|
|
7408
|
+
onKeyDown: (e) => {
|
|
7409
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
7410
|
+
e.preventDefault();
|
|
7411
|
+
e.stopPropagation();
|
|
7412
|
+
removeItem(item.id);
|
|
7413
|
+
},
|
|
7364
7414
|
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
7365
7415
|
})]
|
|
7366
7416
|
}, item.id))
|
|
@@ -7471,6 +7521,7 @@ function TextInput({ element, blockId }) {
|
|
|
7471
7521
|
const { setValue, dispatch, surface } = useBlockKit();
|
|
7472
7522
|
const sizeClass = surface === "message" ? "" : " sbk-text-input--medium";
|
|
7473
7523
|
const [value, setLocalValue] = useState(element.initial_value ?? "");
|
|
7524
|
+
const invalid = useInvalidProps();
|
|
7474
7525
|
const focusRef = useFocusOnLoad(element);
|
|
7475
7526
|
const actionId = element.action_id ?? "";
|
|
7476
7527
|
const dispatchEnabled = element.__dispatchAction === true;
|
|
@@ -7514,7 +7565,8 @@ function TextInput({ element, blockId }) {
|
|
|
7514
7565
|
placeholder,
|
|
7515
7566
|
onKeyDown,
|
|
7516
7567
|
ref: focusRef,
|
|
7517
|
-
"aria-label": element.action_id
|
|
7568
|
+
"aria-label": element.action_id,
|
|
7569
|
+
...invalid
|
|
7518
7570
|
};
|
|
7519
7571
|
const leadingIcon = element.type === "email_text_input" ? /* @__PURE__ */ jsx(EmailIcon, { className: "sbk-text-input__icon" }) : element.type === "url_text_input" ? /* @__PURE__ */ jsx(LinkGlyphIcon, { className: "sbk-text-input__icon" }) : null;
|
|
7520
7572
|
if (element.multiline) return /* @__PURE__ */ jsx("textarea", {
|
|
@@ -7558,6 +7610,7 @@ function TimePicker({ element, blockId }) {
|
|
|
7558
7610
|
const rootRef = useRef(null);
|
|
7559
7611
|
const triggerRef = useRef(null);
|
|
7560
7612
|
useFocusOnLoad(element, triggerRef);
|
|
7613
|
+
const invalid = useInvalidProps();
|
|
7561
7614
|
const listRef = useRef(null);
|
|
7562
7615
|
const actionId = element.action_id ?? "";
|
|
7563
7616
|
useEffect(() => {
|
|
@@ -7609,6 +7662,7 @@ function TimePicker({ element, blockId }) {
|
|
|
7609
7662
|
onClick: () => setOpen((o) => !o),
|
|
7610
7663
|
"aria-haspopup": "listbox",
|
|
7611
7664
|
"aria-expanded": open,
|
|
7665
|
+
...invalid,
|
|
7612
7666
|
children: [
|
|
7613
7667
|
/* @__PURE__ */ jsx(ClockIcon, { className: "sbk-timepicker__icon" }),
|
|
7614
7668
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -9466,6 +9520,7 @@ function Input({ block, blockId }) {
|
|
|
9466
9520
|
__dispatchAction: block.dispatch_action === true
|
|
9467
9521
|
};
|
|
9468
9522
|
const error = errors[blockId];
|
|
9523
|
+
const errorId = useId();
|
|
9469
9524
|
const showDispatchHint = dispatchHint(block);
|
|
9470
9525
|
const showOptional = block.optional && surface !== "message";
|
|
9471
9526
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -9482,13 +9537,16 @@ function Input({ block, blockId }) {
|
|
|
9482
9537
|
className: "sbk-input__element",
|
|
9483
9538
|
children: /* @__PURE__ */ jsx(InputBlockContext.Provider, {
|
|
9484
9539
|
value: true,
|
|
9485
|
-
children:
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9540
|
+
children: /* @__PURE__ */ jsx(InputErrorContext.Provider, {
|
|
9541
|
+
value: error ? errorId : void 0,
|
|
9542
|
+
children: block.dispatch_action === true ? /* @__PURE__ */ jsx(Element, {
|
|
9543
|
+
element,
|
|
9544
|
+
blockId
|
|
9545
|
+
}) : /* @__PURE__ */ jsx(SuppressActions, { children: /* @__PURE__ */ jsx(Element, {
|
|
9546
|
+
element,
|
|
9547
|
+
blockId
|
|
9548
|
+
}) })
|
|
9549
|
+
})
|
|
9492
9550
|
})
|
|
9493
9551
|
}),
|
|
9494
9552
|
block.hint && /* @__PURE__ */ jsx("div", {
|
|
@@ -9514,6 +9572,7 @@ function Input({ block, blockId }) {
|
|
|
9514
9572
|
}),
|
|
9515
9573
|
error && /* @__PURE__ */ jsx("div", {
|
|
9516
9574
|
className: "sbk-input__error",
|
|
9575
|
+
id: errorId,
|
|
9517
9576
|
children: error
|
|
9518
9577
|
})
|
|
9519
9578
|
]
|