@assistant-ui/react-generative-ui 0.0.22 → 0.0.23
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/a2ui/convert.d.ts.map +1 -1
- package/dist/a2ui/convert.js +21 -4
- package/dist/a2ui/convert.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/slack/decodeBlockAction.d.ts.map +1 -1
- package/dist/slack/decodeBlockAction.js +64 -26
- package/dist/slack/decodeBlockAction.js.map +1 -1
- package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
- package/dist/slack/fromSlackBlocks.js +23 -0
- package/dist/slack/fromSlackBlocks.js.map +1 -1
- package/dist/slack/toSlackBlocks.d.ts.map +1 -1
- package/dist/slack/toSlackBlocks.js +74 -18
- package/dist/slack/toSlackBlocks.js.map +1 -1
- package/dist/slack/types.d.ts +20 -1
- package/dist/slack/types.d.ts.map +1 -1
- package/dist/slack.d.ts +1 -1
- package/dist/slack.d.ts.map +1 -1
- package/dist/teams/decodeSubmitData.d.ts.map +1 -1
- package/dist/teams/decodeSubmitData.js +24 -3
- package/dist/teams/decodeSubmitData.js.map +1 -1
- package/dist/teams/temporalId.d.ts +5 -0
- package/dist/teams/temporalId.d.ts.map +1 -0
- package/dist/teams/temporalId.js +28 -0
- package/dist/teams/temporalId.js.map +1 -0
- package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
- package/dist/teams/toAdaptiveCard.js +80 -1
- package/dist/teams/toAdaptiveCard.js.map +1 -1
- package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
- package/dist/teams/toTeamsAttachments.js.map +1 -1
- package/dist/teams/types.d.ts +19 -1
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/teams.d.ts +1 -1
- package/dist/teams.d.ts.map +1 -1
- package/dist/temporal.d.ts +33 -0
- package/dist/temporal.d.ts.map +1 -0
- package/dist/temporal.js +152 -0
- package/dist/temporal.js.map +1 -0
- package/dist/vocabulary/collectFormValues.d.ts +1 -1
- package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
- package/dist/vocabulary/collectFormValues.js +7 -3
- package/dist/vocabulary/collectFormValues.js.map +1 -1
- package/dist/vocabulary/data.d.ts +1 -1
- package/dist/vocabulary/interactive.d.ts +7 -1
- package/dist/vocabulary/interactive.d.ts.map +1 -1
- package/dist/vocabulary/interactive.js +62 -12
- package/dist/vocabulary/interactive.js.map +1 -1
- package/package.json +5 -5
- package/src/a2ui/convert.test.ts +223 -14
- package/src/a2ui/convert.ts +41 -7
- package/src/constants.ts +1 -0
- package/src/slack/datePicker.test.ts +612 -0
- package/src/slack/decodeBlockAction.ts +114 -41
- package/src/slack/fromSlackBlocks.ts +29 -0
- package/src/slack/toSlackBlocks.ts +123 -36
- package/src/slack/types.ts +24 -0
- package/src/slack.ts +2 -0
- package/src/teams/decodeSubmitData.test.ts +216 -0
- package/src/teams/decodeSubmitData.ts +49 -4
- package/src/teams/temporal.test.ts +766 -0
- package/src/teams/temporalId.ts +56 -0
- package/src/teams/toAdaptiveCard.ts +166 -2
- package/src/teams/toTeamsAttachments.ts +4 -1
- package/src/teams/types.ts +17 -0
- package/src/teams.ts +1 -0
- package/src/temporal.test.ts +319 -0
- package/src/temporal.ts +259 -0
- package/src/vocabulary/collectFormValues.test.ts +198 -1
- package/src/vocabulary/collectFormValues.ts +31 -2
- package/src/vocabulary/datepicker.dom.test.tsx +1148 -0
- package/src/vocabulary/interactive.dom.test.tsx +59 -11
- package/src/vocabulary/interactive.tsx +151 -15
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Action } from "../ir";
|
|
2
2
|
import { resolveFieldReferences } from "../fieldReferences";
|
|
3
|
+
import {
|
|
4
|
+
formatTemporalUnixSeconds,
|
|
5
|
+
mergeTemporalMinutes,
|
|
6
|
+
splitTemporalMinutes,
|
|
7
|
+
} from "../temporal";
|
|
8
|
+
import type { FieldMapping } from "./types";
|
|
3
9
|
|
|
4
10
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
5
11
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -9,7 +15,26 @@ const optionValue = (value: unknown): string | undefined =>
|
|
|
9
15
|
? value["value"]
|
|
10
16
|
: undefined;
|
|
11
17
|
|
|
12
|
-
const selectedValue = (
|
|
18
|
+
const selectedValue = (
|
|
19
|
+
action: Record<string, unknown>,
|
|
20
|
+
field?: FieldMapping,
|
|
21
|
+
): unknown => {
|
|
22
|
+
if (action["type"] === "datetimepicker" || "selected_date_time" in action) {
|
|
23
|
+
const seconds = action["selected_date_time"];
|
|
24
|
+
return seconds === null
|
|
25
|
+
? ""
|
|
26
|
+
: typeof seconds === "number"
|
|
27
|
+
? formatTemporalUnixSeconds(seconds, field?.offset)
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
if (action["type"] === "timepicker" || "selected_time" in action) {
|
|
31
|
+
const time = action["selected_time"];
|
|
32
|
+
if (time === null) return "";
|
|
33
|
+
const parts =
|
|
34
|
+
typeof time === "string" ? splitTemporalMinutes(time) : undefined;
|
|
35
|
+
return parts?.date === undefined ? parts?.time : undefined;
|
|
36
|
+
}
|
|
37
|
+
if (field?.part === "date" && action["selected_date"] === null) return "";
|
|
13
38
|
const selectedOption = optionValue(action["selected_option"]);
|
|
14
39
|
const selectedDate =
|
|
15
40
|
typeof action["selected_date"] === "string"
|
|
@@ -35,22 +60,40 @@ const selectedValue = (action: Record<string, unknown>): unknown => {
|
|
|
35
60
|
);
|
|
36
61
|
};
|
|
37
62
|
|
|
38
|
-
const fieldMappingsFromBlockId = (
|
|
39
|
-
blockId: string,
|
|
40
|
-
): readonly (readonly [string, string, boolean])[] => {
|
|
63
|
+
const fieldMappingsFromBlockId = (blockId: string): readonly FieldMapping[] => {
|
|
41
64
|
if (!/^aui:\d+:/.test(blockId)) return [];
|
|
42
65
|
const mappingStart = blockId.indexOf(":", 4);
|
|
43
66
|
if (mappingStart === -1) return [];
|
|
44
67
|
try {
|
|
45
68
|
const parsed: unknown = JSON.parse(blockId.slice(mappingStart + 1));
|
|
46
69
|
if (!Array.isArray(parsed)) return [];
|
|
47
|
-
return parsed.flatMap((entry):
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
return parsed.flatMap((entry): FieldMapping[] => {
|
|
71
|
+
if (
|
|
72
|
+
!Array.isArray(entry) ||
|
|
73
|
+
typeof entry[0] !== "string" ||
|
|
74
|
+
typeof entry[1] !== "string"
|
|
75
|
+
)
|
|
76
|
+
return [];
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
actionId: entry[0],
|
|
80
|
+
name: entry[1],
|
|
81
|
+
component: typeof entry[2] === "string" ? entry[2] : "",
|
|
82
|
+
...(entry[2] === "DatePicker" &&
|
|
83
|
+
(entry[3] === "time" || entry[3] === "datetime")
|
|
84
|
+
? {
|
|
85
|
+
inputType: entry[3],
|
|
86
|
+
...(typeof entry[4] === "string" ? { offset: entry[4] } : {}),
|
|
87
|
+
...((entry[5] === "date" || entry[5] === "time") &&
|
|
88
|
+
Number.isSafeInteger(entry[6]) &&
|
|
89
|
+
entry[6] >= 0
|
|
90
|
+
? { part: entry[5], pairId: entry[6] }
|
|
91
|
+
: {}),
|
|
92
|
+
}
|
|
93
|
+
: {}),
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
});
|
|
54
97
|
} catch {
|
|
55
98
|
return [];
|
|
56
99
|
}
|
|
@@ -64,43 +107,53 @@ const checkboxValue = (
|
|
|
64
107
|
? value["selected_options"].some((option) => optionValue(option) === name)
|
|
65
108
|
: undefined;
|
|
66
109
|
|
|
67
|
-
const stateFieldValue = (
|
|
68
|
-
value: unknown,
|
|
69
|
-
name: string,
|
|
70
|
-
isCheckbox: boolean,
|
|
71
|
-
): unknown => {
|
|
110
|
+
const stateFieldValue = (value: unknown, field: FieldMapping): unknown => {
|
|
72
111
|
if (!isRecord(value)) return undefined;
|
|
73
|
-
if (
|
|
74
|
-
return checkboxValue(value, name);
|
|
112
|
+
if (field.component === "Checkbox" && value["type"] === "checkboxes") {
|
|
113
|
+
return checkboxValue(value, field.name);
|
|
75
114
|
}
|
|
76
115
|
if (value["type"] === "plain_text_input") {
|
|
77
116
|
return typeof value["value"] === "string" ? value["value"] : undefined;
|
|
78
117
|
}
|
|
79
|
-
return selectedValue(value);
|
|
118
|
+
return selectedValue(value, field);
|
|
80
119
|
};
|
|
81
120
|
|
|
82
121
|
const fieldValuesFromState = (
|
|
83
122
|
stateValues: unknown,
|
|
84
|
-
|
|
123
|
+
action: Record<string, unknown>,
|
|
124
|
+
actionField: FieldMapping | undefined,
|
|
125
|
+
) => {
|
|
85
126
|
const fields: Record<string, unknown> = Object.create(null);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
writable: true,
|
|
100
|
-
});
|
|
127
|
+
const pairs = new Map<number, { name: string; date: string; time: string }>();
|
|
128
|
+
const collect = (raw: unknown, field: FieldMapping) => {
|
|
129
|
+
const value = stateFieldValue(raw, field);
|
|
130
|
+
if (field.part !== undefined && field.pairId !== undefined) {
|
|
131
|
+
const pair = pairs.get(field.pairId) ?? {
|
|
132
|
+
name: field.name,
|
|
133
|
+
date: "",
|
|
134
|
+
time: "",
|
|
135
|
+
};
|
|
136
|
+
pair[field.part] = typeof value === "string" ? value : "";
|
|
137
|
+
pairs.set(field.pairId, pair);
|
|
138
|
+
} else if (field.name && value !== undefined) {
|
|
139
|
+
fields[field.name] = value;
|
|
101
140
|
}
|
|
141
|
+
};
|
|
142
|
+
if (isRecord(stateValues)) {
|
|
143
|
+
for (const [blockId, rawBlockValues] of Object.entries(stateValues)) {
|
|
144
|
+
if (!isRecord(rawBlockValues)) continue;
|
|
145
|
+
for (const field of fieldMappingsFromBlockId(blockId)) {
|
|
146
|
+
if (Object.hasOwn(rawBlockValues, field.actionId))
|
|
147
|
+
collect(rawBlockValues[field.actionId], field);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (actionField?.inputType !== undefined) collect(action, actionField);
|
|
152
|
+
for (const pair of pairs.values()) {
|
|
153
|
+
if (pair.name)
|
|
154
|
+
fields[pair.name] = mergeTemporalMinutes(pair.date, pair.time);
|
|
102
155
|
}
|
|
103
|
-
return fields;
|
|
156
|
+
return { fields, pairs };
|
|
104
157
|
};
|
|
105
158
|
|
|
106
159
|
/**
|
|
@@ -140,7 +193,30 @@ export function decodeBlockAction(
|
|
|
140
193
|
}
|
|
141
194
|
}
|
|
142
195
|
|
|
143
|
-
const
|
|
196
|
+
const actionField =
|
|
197
|
+
typeof action["block_id"] === "string"
|
|
198
|
+
? fieldMappingsFromBlockId(action["block_id"]).find(
|
|
199
|
+
(field) => field.actionId === actionId,
|
|
200
|
+
)
|
|
201
|
+
: undefined;
|
|
202
|
+
const { fields, pairs } = fieldValuesFromState(
|
|
203
|
+
stateValues,
|
|
204
|
+
action,
|
|
205
|
+
actionField,
|
|
206
|
+
);
|
|
207
|
+
let input = selectedValue(action, actionField) ?? plainValue;
|
|
208
|
+
if (actionField?.pairId !== undefined) {
|
|
209
|
+
const pair = pairs.get(actionField.pairId);
|
|
210
|
+
input =
|
|
211
|
+
pair === undefined ? "" : mergeTemporalMinutes(pair.date, pair.time);
|
|
212
|
+
if (!input) return undefined;
|
|
213
|
+
}
|
|
214
|
+
if (
|
|
215
|
+
(action["type"] === "timepicker" ||
|
|
216
|
+
action["type"] === "datetimepicker") &&
|
|
217
|
+
input === undefined
|
|
218
|
+
)
|
|
219
|
+
return undefined;
|
|
144
220
|
|
|
145
221
|
const decoded = {
|
|
146
222
|
...Object.fromEntries(
|
|
@@ -149,10 +225,7 @@ export function decodeBlockAction(
|
|
|
149
225
|
type: actionId,
|
|
150
226
|
...(input !== undefined ? { $input: input } : {}),
|
|
151
227
|
};
|
|
152
|
-
return resolveFieldReferences(
|
|
153
|
-
decoded,
|
|
154
|
-
fieldValuesFromState(stateValues),
|
|
155
|
-
) as Action;
|
|
228
|
+
return resolveFieldReferences(decoded, fields) as Action;
|
|
156
229
|
} catch {
|
|
157
230
|
return undefined;
|
|
158
231
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyBounded } from "../convert/copyBounded";
|
|
2
2
|
import type { Action, UIElement } from "../ir";
|
|
3
|
+
import { formatTemporalUnixSeconds } from "../temporal";
|
|
3
4
|
import {
|
|
4
5
|
ACTIONS_ELEMENT_CAP,
|
|
5
6
|
CARD_ACTIONS_CAP,
|
|
@@ -331,6 +332,27 @@ const actionElementFrom = (
|
|
|
331
332
|
return selectFrom(element, warnings);
|
|
332
333
|
case "datepicker":
|
|
333
334
|
return datePickerFrom(element);
|
|
335
|
+
case "timepicker":
|
|
336
|
+
return {
|
|
337
|
+
$type: "DatePicker",
|
|
338
|
+
inputType: "time",
|
|
339
|
+
...(typeof element["initial_time"] === "string"
|
|
340
|
+
? { value: element["initial_time"] }
|
|
341
|
+
: {}),
|
|
342
|
+
$action: decodeAction(element["action_id"], element["value"]),
|
|
343
|
+
};
|
|
344
|
+
case "datetimepicker": {
|
|
345
|
+
const value =
|
|
346
|
+
typeof element["initial_date_time"] === "number"
|
|
347
|
+
? formatTemporalUnixSeconds(element["initial_date_time"])
|
|
348
|
+
: undefined;
|
|
349
|
+
return {
|
|
350
|
+
$type: "DatePicker",
|
|
351
|
+
inputType: "datetime",
|
|
352
|
+
...(value !== undefined ? { value } : {}),
|
|
353
|
+
$action: decodeAction(element["action_id"], element["value"]),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
334
356
|
case "checkboxes":
|
|
335
357
|
return Array.isArray(element["options"]) &&
|
|
336
358
|
element["options"].length > 1
|
|
@@ -372,6 +394,13 @@ const inputFrom = (
|
|
|
372
394
|
warnings: SlackConversionWarning[],
|
|
373
395
|
): UIElement[] => {
|
|
374
396
|
const element = block["element"];
|
|
397
|
+
if (
|
|
398
|
+
isRecord(element) &&
|
|
399
|
+
(element["type"] === "timepicker" || element["type"] === "datetimepicker")
|
|
400
|
+
) {
|
|
401
|
+
const picker = actionElementFrom(element, warnings);
|
|
402
|
+
return picker ? [{ ...picker, label: textOf(block["label"]) }] : [];
|
|
403
|
+
}
|
|
375
404
|
if (!isRecord(element) || element["type"] !== "plain_text_input") {
|
|
376
405
|
warn(
|
|
377
406
|
warnings,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { copyBounded } from "../convert/copyBounded";
|
|
7
7
|
import { isElement } from "../convert/isElement";
|
|
8
8
|
import { takeRun } from "../convert/takeRun";
|
|
9
|
+
import { classifyTemporal, splitTemporalMinutes } from "../temporal";
|
|
9
10
|
import {
|
|
10
11
|
normalizeSpec,
|
|
11
12
|
type NormalizedUIElement,
|
|
@@ -53,6 +54,7 @@ import {
|
|
|
53
54
|
buildDataTableBlock,
|
|
54
55
|
} from "./constants";
|
|
55
56
|
import type {
|
|
57
|
+
FieldMapping,
|
|
56
58
|
SlackActionElement,
|
|
57
59
|
SlackBlock,
|
|
58
60
|
SlackButtonElement,
|
|
@@ -73,17 +75,12 @@ type ConversionContext = {
|
|
|
73
75
|
readonly warnings: SlackConversionWarning[];
|
|
74
76
|
readonly omittedPasswordNames: Set<string>;
|
|
75
77
|
fieldBlockIdSequence: number;
|
|
78
|
+
dateTimePairSequence: number;
|
|
76
79
|
markdownCharacters: number;
|
|
77
80
|
markdownExhausted: boolean;
|
|
78
81
|
dataTableCharacters: number;
|
|
79
82
|
};
|
|
80
83
|
|
|
81
|
-
type FieldMapping = {
|
|
82
|
-
readonly actionId: string;
|
|
83
|
-
readonly name: string;
|
|
84
|
-
readonly component: string;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
84
|
const SLACK_BLOCK_ID_CAP = 255;
|
|
88
85
|
|
|
89
86
|
const INTERACTIVE_TYPES = new Set([
|
|
@@ -170,10 +167,21 @@ const serializeFieldBlockId = (
|
|
|
170
167
|
fields: readonly FieldMapping[],
|
|
171
168
|
): string =>
|
|
172
169
|
`aui:${sequence}:${JSON.stringify(
|
|
173
|
-
fields.map(
|
|
174
|
-
component
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
fields.map(
|
|
171
|
+
({ actionId, name, component, inputType, offset, part, pairId }) =>
|
|
172
|
+
inputType !== undefined
|
|
173
|
+
? [
|
|
174
|
+
actionId,
|
|
175
|
+
name,
|
|
176
|
+
component,
|
|
177
|
+
inputType,
|
|
178
|
+
offset ?? null,
|
|
179
|
+
part ?? null,
|
|
180
|
+
pairId ?? null,
|
|
181
|
+
]
|
|
182
|
+
: component === "Checkbox"
|
|
183
|
+
? [actionId, name, "Checkbox"]
|
|
184
|
+
: [actionId, name],
|
|
177
185
|
),
|
|
178
186
|
)}`;
|
|
179
187
|
|
|
@@ -365,7 +373,7 @@ const warnDroppedOptions = (
|
|
|
365
373
|
const toActionElement = (
|
|
366
374
|
element: NormalizedUIElement,
|
|
367
375
|
context: ConversionContext,
|
|
368
|
-
): SlackActionElement | undefined => {
|
|
376
|
+
): SlackActionElement | SlackActionElement[] | undefined => {
|
|
369
377
|
const { props, action } = element;
|
|
370
378
|
switch (element.type) {
|
|
371
379
|
case "Button":
|
|
@@ -425,6 +433,69 @@ const toActionElement = (
|
|
|
425
433
|
}
|
|
426
434
|
case "DatePicker": {
|
|
427
435
|
const rawDate = asString(props["value"]);
|
|
436
|
+
const inputType = props["inputType"];
|
|
437
|
+
if (inputType === "time" || inputType === "datetime") {
|
|
438
|
+
const actionId = asActionId(action, "DatePicker", context);
|
|
439
|
+
const temporal = classifyTemporal(rawDate);
|
|
440
|
+
const parts = splitTemporalMinutes(rawDate);
|
|
441
|
+
if (
|
|
442
|
+
(inputType === "time" && temporal.kind === "time") ||
|
|
443
|
+
(inputType === "datetime" && temporal.kind === "floating")
|
|
444
|
+
) {
|
|
445
|
+
if (parts?.droppedPrecision) {
|
|
446
|
+
warn(
|
|
447
|
+
context,
|
|
448
|
+
"dropped",
|
|
449
|
+
"DatePicker",
|
|
450
|
+
"Seconds were dropped because Slack timepickers only support minute precision.",
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const timePicker: SlackActionElement = {
|
|
454
|
+
type: "timepicker",
|
|
455
|
+
action_id: actionId,
|
|
456
|
+
initial_time: parts!.time,
|
|
457
|
+
};
|
|
458
|
+
return inputType === "time"
|
|
459
|
+
? timePicker
|
|
460
|
+
: [
|
|
461
|
+
{
|
|
462
|
+
type: "datepicker",
|
|
463
|
+
action_id: actionId,
|
|
464
|
+
initial_date: parts!.date!,
|
|
465
|
+
},
|
|
466
|
+
timePicker,
|
|
467
|
+
];
|
|
468
|
+
}
|
|
469
|
+
if (inputType === "datetime" && temporal.kind === "instant") {
|
|
470
|
+
if (
|
|
471
|
+
temporal.epochMs % 1000 !== 0 ||
|
|
472
|
+
/[1-9]/.test(temporal.subMillisecondDigits ?? "")
|
|
473
|
+
) {
|
|
474
|
+
warn(
|
|
475
|
+
context,
|
|
476
|
+
"dropped",
|
|
477
|
+
"DatePicker",
|
|
478
|
+
"Fractional seconds were dropped because Slack datetimepickers only support second precision.",
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
type: "datetimepicker",
|
|
483
|
+
action_id: actionId,
|
|
484
|
+
initial_date_time: Math.floor(temporal.epochMs / 1000),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (rawDate)
|
|
488
|
+
warn(
|
|
489
|
+
context,
|
|
490
|
+
"dropped",
|
|
491
|
+
"DatePicker",
|
|
492
|
+
`value did not match the ${inputType} format and was dropped.`,
|
|
493
|
+
);
|
|
494
|
+
return {
|
|
495
|
+
type: inputType === "time" ? "timepicker" : "datetimepicker",
|
|
496
|
+
action_id: actionId,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
428
499
|
const initialDate = DATE_PATTERN.test(rawDate) ? rawDate : undefined;
|
|
429
500
|
if (rawDate && initialDate === undefined) {
|
|
430
501
|
warn(
|
|
@@ -593,42 +664,57 @@ const convertActions = (
|
|
|
593
664
|
elements: readonly NormalizedUIElement[],
|
|
594
665
|
context: ConversionContext,
|
|
595
666
|
): SlackBlock[] => {
|
|
596
|
-
const converted = elements
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
)
|
|
667
|
+
const converted = elements.flatMap((source) => {
|
|
668
|
+
const result = toActionElement(source, context);
|
|
669
|
+
if (result === undefined) return [];
|
|
670
|
+
const controls = Array.isArray(result) ? result : [result];
|
|
671
|
+
const inputType =
|
|
672
|
+
source.type === "DatePicker" ? source.props["inputType"] : undefined;
|
|
673
|
+
const temporal =
|
|
674
|
+
inputType === "datetime"
|
|
675
|
+
? classifyTemporal(asString(source.props["value"]))
|
|
676
|
+
: undefined;
|
|
677
|
+
const pairId =
|
|
678
|
+
controls.length > 1 ? context.dateTimePairSequence++ : undefined;
|
|
679
|
+
return controls.map((element) => {
|
|
680
|
+
const name = asString(source.props["name"]);
|
|
681
|
+
const field: FieldMapping | undefined =
|
|
682
|
+
inputType === "time" || inputType === "datetime"
|
|
683
|
+
? {
|
|
684
|
+
actionId: element.action_id,
|
|
685
|
+
name,
|
|
686
|
+
component: source.type,
|
|
687
|
+
inputType,
|
|
688
|
+
...(temporal?.kind === "instant"
|
|
689
|
+
? { offset: temporal.offset }
|
|
690
|
+
: {}),
|
|
691
|
+
...(pairId !== undefined
|
|
692
|
+
? ({
|
|
693
|
+
part: element.type === "datepicker" ? "date" : "time",
|
|
694
|
+
pairId,
|
|
695
|
+
} as const)
|
|
696
|
+
: {}),
|
|
697
|
+
}
|
|
698
|
+
: name
|
|
699
|
+
? { actionId: element.action_id, name, component: source.type }
|
|
700
|
+
: undefined;
|
|
701
|
+
return { element, field };
|
|
702
|
+
});
|
|
703
|
+
});
|
|
609
704
|
const blocks: SlackBlock[] = [];
|
|
610
705
|
const groups: (typeof converted)[] = [];
|
|
611
706
|
let group: typeof converted = [];
|
|
612
707
|
let actionIds = new Set<string>();
|
|
613
|
-
const hasFieldName = (entry: (typeof converted)[number]): boolean => {
|
|
614
|
-
const name = entry.source.props["name"];
|
|
615
|
-
return typeof name === "string" && name.length > 0;
|
|
616
|
-
};
|
|
617
708
|
const fieldsFor = (entries: typeof converted): FieldMapping[] =>
|
|
618
|
-
entries.flatMap(({
|
|
619
|
-
const name = source.props["name"];
|
|
620
|
-
return typeof name === "string" && name.length > 0
|
|
621
|
-
? [{ actionId: element.action_id, name, component: source.type }]
|
|
622
|
-
: [];
|
|
623
|
-
});
|
|
709
|
+
entries.flatMap(({ field }) => (field === undefined ? [] : [field]));
|
|
624
710
|
for (const entry of converted) {
|
|
625
711
|
const repeatedMappedActionId =
|
|
626
712
|
actionIds.has(entry.element.action_id) &&
|
|
627
|
-
(
|
|
713
|
+
(entry.field !== undefined ||
|
|
628
714
|
group.some(
|
|
629
715
|
(candidate) =>
|
|
630
716
|
candidate.element.action_id === entry.element.action_id &&
|
|
631
|
-
|
|
717
|
+
candidate.field !== undefined,
|
|
632
718
|
));
|
|
633
719
|
if (group.length === ACTIONS_ELEMENT_CAP || repeatedMappedActionId) {
|
|
634
720
|
groups.push(group);
|
|
@@ -1690,6 +1776,7 @@ export function toSlackBlocks(
|
|
|
1690
1776
|
warnings: [],
|
|
1691
1777
|
omittedPasswordNames: new Set(),
|
|
1692
1778
|
fieldBlockIdSequence: 0,
|
|
1779
|
+
dateTimePairSequence: 0,
|
|
1693
1780
|
markdownCharacters: 0,
|
|
1694
1781
|
markdownExhausted: false,
|
|
1695
1782
|
dataTableCharacters: 0,
|
package/src/slack/types.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { UIElement } from "../ir";
|
|
2
2
|
|
|
3
|
+
export type FieldMapping = {
|
|
4
|
+
readonly actionId: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly component: string;
|
|
7
|
+
readonly inputType?: "time" | "datetime";
|
|
8
|
+
readonly offset?: string;
|
|
9
|
+
readonly part?: "date" | "time";
|
|
10
|
+
readonly pairId?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
3
13
|
/** A Slack plain-text composition object. */
|
|
4
14
|
export interface SlackPlainText {
|
|
5
15
|
readonly type: "plain_text";
|
|
@@ -46,6 +56,18 @@ export interface SlackDatePickerElement {
|
|
|
46
56
|
readonly initial_date?: string;
|
|
47
57
|
}
|
|
48
58
|
|
|
59
|
+
export interface SlackTimePickerElement {
|
|
60
|
+
readonly type: "timepicker";
|
|
61
|
+
readonly action_id: string;
|
|
62
|
+
readonly initial_time?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface SlackDateTimePickerElement {
|
|
66
|
+
readonly type: "datetimepicker";
|
|
67
|
+
readonly action_id: string;
|
|
68
|
+
readonly initial_date_time?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
/** A Slack checkbox-group element. */
|
|
50
72
|
export interface SlackCheckboxesElement {
|
|
51
73
|
readonly type: "checkboxes";
|
|
@@ -76,6 +98,8 @@ export type SlackActionElement =
|
|
|
76
98
|
| SlackButtonElement
|
|
77
99
|
| SlackStaticSelectElement
|
|
78
100
|
| SlackDatePickerElement
|
|
101
|
+
| SlackTimePickerElement
|
|
102
|
+
| SlackDateTimePickerElement
|
|
79
103
|
| SlackCheckboxesElement
|
|
80
104
|
| SlackRadioButtonsElement;
|
|
81
105
|
|
package/src/slack.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type {
|
|
|
20
20
|
SlackDataTableRawNumberCell,
|
|
21
21
|
SlackDataTableRawTextCell,
|
|
22
22
|
SlackDatePickerElement,
|
|
23
|
+
SlackDateTimePickerElement,
|
|
23
24
|
SlackDividerBlock,
|
|
24
25
|
SlackHeaderBlock,
|
|
25
26
|
SlackImageBlock,
|
|
@@ -33,5 +34,6 @@ export type {
|
|
|
33
34
|
SlackSectionBlock,
|
|
34
35
|
SlackStaticSelectElement,
|
|
35
36
|
SlackTextObject,
|
|
37
|
+
SlackTimePickerElement,
|
|
36
38
|
ToSlackBlocksOptions,
|
|
37
39
|
} from "./slack/types";
|