@nkootstra/block-kit 0.3.0 → 0.3.2
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 +104 -50
- 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. */
|
|
@@ -6470,16 +6503,6 @@ function DatePicker({ element, blockId }) {
|
|
|
6470
6503
|
}
|
|
6471
6504
|
//#endregion
|
|
6472
6505
|
//#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
6506
|
/** Slack's Builder splits a datetimepicker's closed control into a date box ("January 1st,
|
|
6484
6507
|
* 2026") and a separate time box ("11:00 AM"), rather than one combined string. */
|
|
6485
6508
|
function formatDate(ts, timeZone) {
|
|
@@ -6488,7 +6511,7 @@ function formatDate(ts, timeZone) {
|
|
|
6488
6511
|
month: "long",
|
|
6489
6512
|
day: "numeric",
|
|
6490
6513
|
year: "numeric",
|
|
6491
|
-
timeZone
|
|
6514
|
+
timeZone
|
|
6492
6515
|
}).formatToParts(d);
|
|
6493
6516
|
const month = parts.find((p) => p.type === "month")?.value ?? "";
|
|
6494
6517
|
const day = Number(parts.find((p) => p.type === "day")?.value ?? "1");
|
|
@@ -6499,7 +6522,7 @@ function formatTime$1(ts, timeZone) {
|
|
|
6499
6522
|
return (/* @__PURE__ */ new Date(ts * 1e3)).toLocaleString("en-US", {
|
|
6500
6523
|
hour: "numeric",
|
|
6501
6524
|
minute: "2-digit",
|
|
6502
|
-
timeZone
|
|
6525
|
+
timeZone
|
|
6503
6526
|
});
|
|
6504
6527
|
}
|
|
6505
6528
|
function DateTimePicker({ element, blockId }) {
|
|
@@ -6507,8 +6530,10 @@ function DateTimePicker({ element, blockId }) {
|
|
|
6507
6530
|
const { ask, dialog } = useConfirm(element.confirm);
|
|
6508
6531
|
const [value, setValueState] = useState(element.initial_date_time);
|
|
6509
6532
|
const [open, setOpen] = useState(false);
|
|
6510
|
-
const
|
|
6511
|
-
const
|
|
6533
|
+
const zone = timeZone ?? "UTC";
|
|
6534
|
+
const initial = element.initial_date_time !== void 0 ? wallClock(element.initial_date_time, zone) : void 0;
|
|
6535
|
+
const [draftDate, setDraftDate] = useState(initial?.date);
|
|
6536
|
+
const [draftTime, setDraftTime] = useState(initial?.time ?? "12:00");
|
|
6512
6537
|
const rootRef = useRef(null);
|
|
6513
6538
|
const focusRef = useFocusOnLoad(element);
|
|
6514
6539
|
const actionId = element.action_id ?? "";
|
|
@@ -6521,7 +6546,7 @@ function DateTimePicker({ element, blockId }) {
|
|
|
6521
6546
|
async function apply() {
|
|
6522
6547
|
if (!draftDate) return;
|
|
6523
6548
|
if (!await ask()) return;
|
|
6524
|
-
const ts =
|
|
6549
|
+
const ts = fromWallClock(draftDate, draftTime, zone);
|
|
6525
6550
|
setValueState(ts);
|
|
6526
6551
|
setOpen(false);
|
|
6527
6552
|
setValue(blockId, actionId, {
|
|
@@ -6552,7 +6577,7 @@ function DateTimePicker({ element, blockId }) {
|
|
|
6552
6577
|
/* @__PURE__ */ jsx(CalendarIcon, { className: "sbk-datetimepicker__icon" }),
|
|
6553
6578
|
/* @__PURE__ */ jsx("span", {
|
|
6554
6579
|
className: value !== void 0 ? "sbk-datetimepicker__value" : "sbk-datetimepicker__placeholder",
|
|
6555
|
-
children: value !== void 0 ? formatDate(value,
|
|
6580
|
+
children: value !== void 0 ? formatDate(value, zone) : "Select a date"
|
|
6556
6581
|
}),
|
|
6557
6582
|
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-datetimepicker__chevron" })
|
|
6558
6583
|
]
|
|
@@ -6567,13 +6592,13 @@ function DateTimePicker({ element, blockId }) {
|
|
|
6567
6592
|
/* @__PURE__ */ jsx(ClockIcon, { className: "sbk-datetimepicker__icon" }),
|
|
6568
6593
|
/* @__PURE__ */ jsx("span", {
|
|
6569
6594
|
className: value !== void 0 ? "sbk-datetimepicker__value" : "sbk-datetimepicker__placeholder",
|
|
6570
|
-
children: value !== void 0 ? formatTime$1(value,
|
|
6595
|
+
children: value !== void 0 ? formatTime$1(value, zone) : "Select a time"
|
|
6571
6596
|
}),
|
|
6572
6597
|
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "sbk-datetimepicker__chevron" })
|
|
6573
6598
|
]
|
|
6574
6599
|
}), /* @__PURE__ */ jsxs("p", {
|
|
6575
6600
|
className: "sbk-datetimepicker__timezone",
|
|
6576
|
-
children: ["Time zone: ", timeZoneLabel(
|
|
6601
|
+
children: ["Time zone: ", timeZoneLabel(zone)]
|
|
6577
6602
|
})]
|
|
6578
6603
|
})]
|
|
6579
6604
|
}),
|
|
@@ -9872,6 +9897,24 @@ function DotIcon({ status }) {
|
|
|
9872
9897
|
children: /* @__PURE__ */ jsx("path", { d: "M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0" })
|
|
9873
9898
|
});
|
|
9874
9899
|
}
|
|
9900
|
+
/** How a status reads aloud. The icons are hidden from assistive technology, so this stands in. */
|
|
9901
|
+
const STATUS_LABELS = {
|
|
9902
|
+
complete: "complete",
|
|
9903
|
+
in_progress: "in progress",
|
|
9904
|
+
pending: "pending",
|
|
9905
|
+
error: "failed"
|
|
9906
|
+
};
|
|
9907
|
+
/**
|
|
9908
|
+
* A status as text that only screen readers get, set after a title (", in progress"), so a toggle
|
|
9909
|
+
* or task header is announced with the state its icon shows. An unknown status says nothing.
|
|
9910
|
+
*/
|
|
9911
|
+
function StatusText({ status }) {
|
|
9912
|
+
const label = STATUS_LABELS[status];
|
|
9913
|
+
return label ? /* @__PURE__ */ jsx("span", {
|
|
9914
|
+
className: "sbk-visually-hidden",
|
|
9915
|
+
children: `, ${label}`
|
|
9916
|
+
}) : null;
|
|
9917
|
+
}
|
|
9875
9918
|
/** The glyph on a toggle bar (the plan's or a task_card's): check, warning or spinner. */
|
|
9876
9919
|
function StatusIcon({ status }) {
|
|
9877
9920
|
if (status === "complete") return /* @__PURE__ */ jsx(CheckCircleIcon, {});
|
|
@@ -9898,7 +9941,8 @@ function aggregateStatus(statuses) {
|
|
|
9898
9941
|
/**
|
|
9899
9942
|
* One step of the plan. As in Slack, a thin line joins each step's icon to the next, its details
|
|
9900
9943
|
* start open, and the title is a button that hides and shows them instantly; a task with nothing
|
|
9901
|
-
* to reveal gets
|
|
9944
|
+
* to reveal gets the same header as plain text, with no caret. The status, shown as an icon, is
|
|
9945
|
+
* also in the header as text for screen readers.
|
|
9902
9946
|
*/
|
|
9903
9947
|
function TaskRow({ task }) {
|
|
9904
9948
|
const [open, setOpen] = useState(true);
|
|
@@ -9919,21 +9963,28 @@ function TaskRow({ task }) {
|
|
|
9919
9963
|
]
|
|
9920
9964
|
}), /* @__PURE__ */ jsxs("div", {
|
|
9921
9965
|
className: "sbk-plan__task-content",
|
|
9922
|
-
children: [/* @__PURE__ */ jsxs("button", {
|
|
9966
|
+
children: [expandable ? /* @__PURE__ */ jsxs("button", {
|
|
9923
9967
|
type: "button",
|
|
9924
9968
|
className: "sbk-plan__task-header",
|
|
9925
9969
|
"aria-expanded": open,
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9970
|
+
onClick: () => setOpen((value) => !value),
|
|
9971
|
+
children: [
|
|
9972
|
+
/* @__PURE__ */ jsx("span", {
|
|
9973
|
+
className: "sbk-plan__task-title",
|
|
9974
|
+
children: task.title
|
|
9975
|
+
}),
|
|
9976
|
+
/* @__PURE__ */ jsx(StatusText, { status: task.status }),
|
|
9977
|
+
/* @__PURE__ */ jsx("span", {
|
|
9978
|
+
className: "sbk-plan__task-caret",
|
|
9979
|
+
children: /* @__PURE__ */ jsx(CaretIcon, { direction: open ? "down" : "right" })
|
|
9980
|
+
})
|
|
9981
|
+
]
|
|
9982
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
9983
|
+
className: "sbk-plan__task-header",
|
|
9930
9984
|
children: [/* @__PURE__ */ jsx("span", {
|
|
9931
9985
|
className: "sbk-plan__task-title",
|
|
9932
9986
|
children: task.title
|
|
9933
|
-
}),
|
|
9934
|
-
className: "sbk-plan__task-caret",
|
|
9935
|
-
children: /* @__PURE__ */ jsx(CaretIcon, { direction: open ? "down" : "right" })
|
|
9936
|
-
}) : null]
|
|
9987
|
+
}), /* @__PURE__ */ jsx(StatusText, { status: task.status })]
|
|
9937
9988
|
}), open && expandable ? /* @__PURE__ */ jsxs("div", {
|
|
9938
9989
|
className: "sbk-plan__task-body",
|
|
9939
9990
|
children: [
|
|
@@ -9982,6 +10033,7 @@ function Plan({ block }) {
|
|
|
9982
10033
|
className: "sbk-plan__pill-title",
|
|
9983
10034
|
children: block.title
|
|
9984
10035
|
}),
|
|
10036
|
+
/* @__PURE__ */ jsx(StatusText, { status }),
|
|
9985
10037
|
/* @__PURE__ */ jsx("span", {
|
|
9986
10038
|
className: "sbk-plan__pill-chevron",
|
|
9987
10039
|
children: /* @__PURE__ */ jsx(CaretIcon, { direction: expanded ? "down" : "right" })
|
|
@@ -10395,33 +10447,35 @@ function TaskCard({ block }) {
|
|
|
10395
10447
|
const [expanded, setExpanded] = useState(false);
|
|
10396
10448
|
const sources = Array.isArray(block.sources) ? block.sources : [];
|
|
10397
10449
|
const expandable = Boolean(block.details || block.output || sources.length > 0);
|
|
10450
|
+
const pillContent = /* @__PURE__ */ jsxs("span", {
|
|
10451
|
+
className: "sbk-task-card__pill-content",
|
|
10452
|
+
children: [
|
|
10453
|
+
/* @__PURE__ */ jsx("span", {
|
|
10454
|
+
className: "sbk-task-card__pill-icon",
|
|
10455
|
+
children: /* @__PURE__ */ jsx(StatusIcon, { status: block.status })
|
|
10456
|
+
}),
|
|
10457
|
+
/* @__PURE__ */ jsx("span", {
|
|
10458
|
+
className: "sbk-task-card__pill-title",
|
|
10459
|
+
children: block.title
|
|
10460
|
+
}),
|
|
10461
|
+
/* @__PURE__ */ jsx(StatusText, { status: block.status }),
|
|
10462
|
+
expandable ? /* @__PURE__ */ jsx("span", {
|
|
10463
|
+
className: "sbk-task-card__pill-chevron",
|
|
10464
|
+
children: /* @__PURE__ */ jsx(CaretIcon, { direction: expanded ? "down" : "right" })
|
|
10465
|
+
}) : null
|
|
10466
|
+
]
|
|
10467
|
+
});
|
|
10398
10468
|
return /* @__PURE__ */ jsxs("div", {
|
|
10399
10469
|
className: "sbk-task-card",
|
|
10400
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
10470
|
+
children: [expandable ? /* @__PURE__ */ jsx("button", {
|
|
10401
10471
|
type: "button",
|
|
10402
10472
|
className: "sbk-task-card__pill",
|
|
10403
10473
|
"aria-expanded": expanded,
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
children:
|
|
10409
|
-
className: "sbk-task-card__pill-content",
|
|
10410
|
-
children: [
|
|
10411
|
-
/* @__PURE__ */ jsx("span", {
|
|
10412
|
-
className: "sbk-task-card__pill-icon",
|
|
10413
|
-
children: /* @__PURE__ */ jsx(StatusIcon, { status: block.status })
|
|
10414
|
-
}),
|
|
10415
|
-
/* @__PURE__ */ jsx("span", {
|
|
10416
|
-
className: "sbk-task-card__pill-title",
|
|
10417
|
-
children: block.title
|
|
10418
|
-
}),
|
|
10419
|
-
expandable ? /* @__PURE__ */ jsx("span", {
|
|
10420
|
-
className: "sbk-task-card__pill-chevron",
|
|
10421
|
-
children: /* @__PURE__ */ jsx(CaretIcon, { direction: expanded ? "down" : "right" })
|
|
10422
|
-
}) : null
|
|
10423
|
-
]
|
|
10424
|
-
})
|
|
10474
|
+
onClick: () => setExpanded((value) => !value),
|
|
10475
|
+
children: pillContent
|
|
10476
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
10477
|
+
className: "sbk-task-card__pill",
|
|
10478
|
+
children: pillContent
|
|
10425
10479
|
}), /* @__PURE__ */ jsx(Collapse, {
|
|
10426
10480
|
open: expanded,
|
|
10427
10481
|
children: /* @__PURE__ */ jsxs("div", {
|