hs-uix 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +3 -1
  2. package/common-components.d.ts +319 -68
  3. package/dist/calendar.js +355 -57
  4. package/dist/calendar.mjs +356 -57
  5. package/dist/common-components.js +3546 -88
  6. package/dist/common-components.mjs +3530 -84
  7. package/dist/datatable.js +108 -18
  8. package/dist/datatable.mjs +108 -18
  9. package/dist/experimental.js +2876 -0
  10. package/dist/experimental.mjs +2883 -0
  11. package/dist/feed.js +267 -38
  12. package/dist/feed.mjs +260 -37
  13. package/dist/filter.js +1379 -0
  14. package/dist/filter.mjs +1334 -0
  15. package/dist/form.js +222 -26
  16. package/dist/form.mjs +227 -27
  17. package/dist/index.js +3208 -287
  18. package/dist/index.mjs +3156 -283
  19. package/dist/kanban.js +282 -62
  20. package/dist/kanban.mjs +273 -61
  21. package/dist/safe.js +9207 -0
  22. package/dist/safe.mjs +9298 -0
  23. package/dist/utils.js +491 -75
  24. package/dist/utils.mjs +491 -75
  25. package/experimental.d.ts +1 -0
  26. package/filter.d.ts +1 -0
  27. package/index.d.ts +45 -3
  28. package/package.json +19 -1
  29. package/safe.d.ts +1 -0
  30. package/src/calendar/README.md +74 -5
  31. package/src/calendar/index.d.ts +95 -1
  32. package/src/common-components/README.md +140 -1
  33. package/src/datatable/README.md +0 -2
  34. package/src/experimental/README.md +126 -0
  35. package/src/experimental/index.d.ts +346 -0
  36. package/src/feed/README.md +69 -0
  37. package/src/feed/index.d.ts +103 -0
  38. package/src/filter/README.md +148 -0
  39. package/src/filter/index.d.ts +221 -0
  40. package/src/form/README.md +132 -4
  41. package/src/form/index.d.ts +82 -1
  42. package/src/kanban/README.md +119 -6
  43. package/src/kanban/index.d.ts +153 -2
  44. package/src/safe/README.md +108 -0
  45. package/src/safe/index.d.ts +158 -0
  46. package/src/utils/README.md +39 -0
  47. package/src/wizard/README.md +158 -0
  48. package/src/wizard/index.d.ts +138 -0
  49. package/utils.d.ts +17 -0
package/dist/feed.js CHANGED
@@ -29,7 +29,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // src/feed/index.js
30
30
  var feed_exports = {};
31
31
  __export(feed_exports, {
32
- Feed: () => Feed
32
+ DEFAULT_FEED_TYPE_PRESETS: () => DEFAULT_FEED_TYPE_PRESETS,
33
+ Feed: () => Feed,
34
+ applyTypePreset: () => applyTypePreset,
35
+ flushBuffer: () => flushBuffer,
36
+ lookupTypePreset: () => lookupTypePreset,
37
+ partitionNewItems: () => partitionNewItems,
38
+ toTimestampMs: () => toTimestampMs
33
39
  });
34
40
  module.exports = __toCommonJS(feed_exports);
35
41
 
@@ -551,7 +557,7 @@ var GENERATED_ICONS = {
551
557
  "ZoomOut": { "viewBox": "0 0 32 32", "paths": ["M14.42 26.75c2.85 0 5.47-.97 7.56-2.6l-.03.02 5.28 5.34a1.619 1.619 0 0 0 2.76-1.15c0-.45-.18-.85-.47-1.14l-5.33-5.33c1.59-2.06 2.55-4.68 2.55-7.52C26.74 7.54 21.2 2 14.37 2S2 7.55 2 14.38s5.54 12.37 12.37 12.37h.05m0-21.55c5.06 0 9.16 4.1 9.16 9.16s-4.1 9.16-9.16 9.16-9.16-4.1-9.16-9.16c.01-5.05 4.11-9.14 9.16-9.15Zm-4.31 10.78h8.62c.89 0 1.62-.72 1.62-1.62s-.72-1.62-1.62-1.62h-8.62c-.89 0-1.62.72-1.62 1.62s.72 1.62 1.62 1.62"] }
552
558
  };
553
559
 
554
- // src/common-components/Icon.js
560
+ // src/common-components/nativeIconNames.js
555
561
  var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
556
562
  "add",
557
563
  "appointment",
@@ -563,12 +569,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
563
569
  "block",
564
570
  "book",
565
571
  "bulb",
572
+ "callTranscript",
566
573
  "calling",
567
574
  "callingHangup",
568
575
  "callingMade",
569
576
  "callingMissed",
570
577
  "callingVoicemail",
571
- "callTranscript",
572
578
  "campaigns",
573
579
  "cap",
574
580
  "checkCircle",
@@ -597,13 +603,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
597
603
  "enroll",
598
604
  "exclamation",
599
605
  "exclamationCircle",
600
- "facebook",
601
606
  "faceHappy",
602
607
  "faceHappyFilled",
603
608
  "faceNeutral",
604
609
  "faceNeutralFilled",
605
610
  "faceSad",
606
611
  "faceSadFilled",
612
+ "facebook",
607
613
  "favoriteHollow",
608
614
  "file",
609
615
  "filledXCircleIcon",
@@ -744,6 +750,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
744
750
  "zoomIn",
745
751
  "zoomOut"
746
752
  ]);
753
+
754
+ // src/common-components/Icon.js
747
755
  var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
748
756
  var NATIVE_SIZE_TOKENS = {
749
757
  sm: "sm",
@@ -956,6 +964,7 @@ var CollectionFilterControl = ({
956
964
  { key: name, direction: "row", align: "center", gap: "xs" },
957
965
  h4(import_ui_extensions6.DateInput, {
958
966
  name: `${controlName}-from`,
967
+ label: filter.fromLabel ?? labels.dateFrom,
959
968
  placeholder: filter.fromLabel ?? labels.dateFrom,
960
969
  format: "medium",
961
970
  value: rangeValue.from ?? null,
@@ -964,6 +973,7 @@ var CollectionFilterControl = ({
964
973
  h4(Icon, { name: "right", size: "sm" }),
965
974
  h4(import_ui_extensions6.DateInput, {
966
975
  name: `${controlName}-to`,
976
+ label: filter.toLabel ?? labels.dateTo,
967
977
  placeholder: filter.toLabel ?? labels.dateTo,
968
978
  format: "medium",
969
979
  value: rangeValue.to ?? null,
@@ -1164,6 +1174,110 @@ var buildActiveFilterChips = (filters, values = {}, options = {}) => {
1164
1174
  return chips;
1165
1175
  };
1166
1176
 
1177
+ // src/feed/feedLiveBuffer.js
1178
+ var isDateValueObject = (v) => v != null && typeof v === "object" && typeof v.year === "number" && typeof v.month === "number" && typeof v.date === "number";
1179
+ var toTimestampMs = (value) => {
1180
+ if (value == null || value === "") return null;
1181
+ if (value instanceof Date) {
1182
+ const time2 = value.getTime();
1183
+ return Number.isNaN(time2) ? null : time2;
1184
+ }
1185
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
1186
+ if (isDateValueObject(value)) {
1187
+ return new Date(value.year, value.month, value.date, value.hour || 0, value.minute || 0).getTime();
1188
+ }
1189
+ const parsed = new Date(value);
1190
+ const time = parsed.getTime();
1191
+ return Number.isNaN(time) ? null : time;
1192
+ };
1193
+ var defaultGetId = (item, index) => (item == null ? void 0 : item.id) ?? (item == null ? void 0 : item.key) ?? index;
1194
+ var partitionNewItems = (prevNewestTs, items, getTs, options = {}) => {
1195
+ const { knownIds = null, getId = defaultGetId } = options;
1196
+ const safeItems = Array.isArray(items) ? items : [];
1197
+ const firstLoad = prevNewestTs == null;
1198
+ const visible = [];
1199
+ const visibleIds = [];
1200
+ const buffered = [];
1201
+ const bufferedIds = [];
1202
+ let newestTs = typeof prevNewestTs === "number" ? prevNewestTs : null;
1203
+ safeItems.forEach((item, index) => {
1204
+ const id = getId(item, index);
1205
+ const ts = toTimestampMs(typeof getTs === "function" ? getTs(item) : void 0);
1206
+ const isKnown = knownIds != null && id !== void 0 && knownIds.has(id);
1207
+ const isNewArrival = !firstLoad && !isKnown && ts != null && ts > prevNewestTs;
1208
+ if (isNewArrival) {
1209
+ buffered.push(item);
1210
+ bufferedIds.push(id);
1211
+ return;
1212
+ }
1213
+ visible.push(item);
1214
+ visibleIds.push(id);
1215
+ if (ts != null && (newestTs == null || ts > newestTs)) newestTs = ts;
1216
+ });
1217
+ return { visible, buffered, visibleIds, bufferedIds, newestTs };
1218
+ };
1219
+ var flushBuffer = (visible, buffered, getTs) => {
1220
+ const safeVisible = Array.isArray(visible) ? visible : [];
1221
+ const safeBuffered = Array.isArray(buffered) ? buffered : [];
1222
+ const items = [...safeBuffered, ...safeVisible];
1223
+ let newestTs = null;
1224
+ items.forEach((item) => {
1225
+ const ts = toTimestampMs(typeof getTs === "function" ? getTs(item) : void 0);
1226
+ if (ts != null && (newestTs == null || ts > newestTs)) newestTs = ts;
1227
+ });
1228
+ return { items, flushed: safeBuffered, newestTs };
1229
+ };
1230
+
1231
+ // src/feed/feedTypePresets.js
1232
+ var hasValue = (value) => value != null && value !== false && value !== "";
1233
+ var DEFAULT_FEED_TYPE_PRESETS = {
1234
+ call: { icon: "calling", label: "Call" },
1235
+ email: { icon: "email", label: "Email" },
1236
+ incoming_email: { icon: "inbox", label: "Incoming email" },
1237
+ forwarded_email: { icon: "forward", label: "Forwarded email" },
1238
+ meeting: { icon: "appointment", label: "Meeting" },
1239
+ note: { icon: "comment", label: "Note" },
1240
+ task: { icon: "tasks", label: "Task" },
1241
+ sms: { icon: "messages", label: "SMS" },
1242
+ whatsapp: { icon: "messages", label: "WhatsApp" },
1243
+ linkedin_message: { icon: "linkedin", label: "LinkedIn message" },
1244
+ postal_mail: { icon: "send", label: "Postal mail" },
1245
+ conversation: { icon: "questionAnswer", label: "Conversation" }
1246
+ };
1247
+ var lookupTypePreset = (type, presets) => {
1248
+ if (!presets || typeof presets !== "object") return null;
1249
+ if (type == null || type === "") return null;
1250
+ if (Object.prototype.hasOwnProperty.call(presets, type)) return presets[type];
1251
+ const lower = String(type).toLowerCase();
1252
+ if (Object.prototype.hasOwnProperty.call(presets, lower)) return presets[lower];
1253
+ const snake = lower.replace(/[\s-]+/g, "_");
1254
+ if (Object.prototype.hasOwnProperty.call(presets, snake)) return presets[snake];
1255
+ return null;
1256
+ };
1257
+ var applyTypePreset = (item, typePresets) => {
1258
+ if (item == null || typeof item !== "object") return item;
1259
+ const preset = lookupTypePreset(item.type, typePresets);
1260
+ if (!preset || typeof preset !== "object") return item;
1261
+ let next = null;
1262
+ const fill = (key, value) => {
1263
+ if (next === null) next = { ...item };
1264
+ next[key] = value;
1265
+ };
1266
+ if (!hasValue(item.icon) && !hasValue(item.iconName) && hasValue(preset.icon)) {
1267
+ fill("iconName", preset.icon);
1268
+ }
1269
+ if (!hasValue(item.iconColor) && hasValue(preset.color)) {
1270
+ fill("iconColor", preset.color);
1271
+ }
1272
+ if (!hasValue(item.typeLabel) && hasValue(preset.label)) {
1273
+ fill("typeLabel", preset.label);
1274
+ }
1275
+ if (item.statusVariant == null && item.outcomeVariant == null && item.severityVariant == null && hasValue(preset.statusVariant)) {
1276
+ fill("statusVariant", preset.statusVariant);
1277
+ }
1278
+ return next ?? item;
1279
+ };
1280
+
1167
1281
  // src/feed/Feed.jsx
1168
1282
  var DEFAULT_LABELS3 = {
1169
1283
  search: "Search activity...",
@@ -1177,6 +1291,8 @@ var DEFAULT_LABELS3 = {
1177
1291
  loadingMessage: "This should only take a moment.",
1178
1292
  loadingMore: "Loading...",
1179
1293
  loadMore: "View more",
1294
+ newItems: (count) => count === 1 ? "Show 1 new item" : `Show ${count} new items`,
1295
+ newItemTag: "New",
1180
1296
  collapseAll: "Collapse all",
1181
1297
  expandAll: "Expand all",
1182
1298
  emptyTitle: "No activity yet",
@@ -1189,7 +1305,15 @@ var DEFAULT_LABELS3 = {
1189
1305
  var DEFAULT_RECORD_LABEL = { singular: "item", plural: "items" };
1190
1306
  var DEFAULT_SEARCH_FIELDS = ["title", "subject", "body", "description", "content", "preview", "type", "typeLabel", "actorName", "author"];
1191
1307
  var DEFAULT_PAGE_SIZE = 5;
1192
- var hasValue = (value) => value != null && value !== false && value !== "";
1308
+ var EMPTY_ITEMS = [];
1309
+ var INITIAL_LIVE_STATE = {
1310
+ source: null,
1311
+ watermark: null,
1312
+ bufferedKeys: EMPTY_ITEMS,
1313
+ knownKeys: EMPTY_ITEMS,
1314
+ newKeys: EMPTY_ITEMS
1315
+ };
1316
+ var hasValue2 = (value) => value != null && value !== false && value !== "";
1193
1317
  var keepWordsTogether = (value) => typeof value === "string" ? value.replace(/\s+/g, "\xA0") : value;
1194
1318
  var getItemKey = (item, index, getKey) => {
1195
1319
  if (typeof getKey === "function") return getKey(item, index);
@@ -1216,11 +1340,11 @@ var pickHeaderActions = (item) => item == null ? void 0 : item.headerActions;
1216
1340
  var itemHasExpandableContent = (item, fields) => {
1217
1341
  if ((item == null ? void 0 : item.collapsible) === false) return false;
1218
1342
  if ((item == null ? void 0 : item.collapsible) === true) return true;
1219
- if (hasValue(pickBody(item))) return true;
1220
- if (hasValue(pickActor(item))) return true;
1221
- if (hasValue(item == null ? void 0 : item.actions)) return true;
1222
- if (hasValue(item == null ? void 0 : item.footer)) return true;
1223
- if (hasValue(item == null ? void 0 : item.meta) || hasValue(item == null ? void 0 : item.metadata)) return true;
1343
+ if (hasValue2(pickBody(item))) return true;
1344
+ if (hasValue2(pickActor(item))) return true;
1345
+ if (hasValue2(item == null ? void 0 : item.actions)) return true;
1346
+ if (hasValue2(item == null ? void 0 : item.footer)) return true;
1347
+ if (hasValue2(item == null ? void 0 : item.meta) || hasValue2(item == null ? void 0 : item.metadata)) return true;
1224
1348
  if (Array.isArray(fields)) {
1225
1349
  if (fields.some((f) => ["body", "footer"].includes(f.placement ?? "body"))) return true;
1226
1350
  }
@@ -1302,7 +1426,7 @@ var getRecordLabel = (recordLabel, count) => {
1302
1426
  var FeedActorAvatar = ({ item, avatarSize }) => {
1303
1427
  const actor = pickActor(item);
1304
1428
  const avatar = (item == null ? void 0 : item.avatar) ?? pickActorAvatar(actor);
1305
- if (!hasValue(avatar)) return null;
1429
+ if (!hasValue2(avatar)) return null;
1306
1430
  return /* @__PURE__ */ import_react8.default.createElement(
1307
1431
  AvatarStack,
1308
1432
  {
@@ -1314,10 +1438,10 @@ var FeedActorAvatar = ({ item, avatarSize }) => {
1314
1438
  );
1315
1439
  };
1316
1440
  var FeedTypeIcon = ({ item, iconSize }) => {
1317
- if (hasValue(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
1441
+ if (hasValue2(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
1318
1442
  const iconName = typeof (item == null ? void 0 : item.icon) === "string" ? item.icon : item == null ? void 0 : item.iconName;
1319
- if (!hasValue(iconName)) return null;
1320
- return /* @__PURE__ */ import_react8.default.createElement(Icon, { name: iconName, size: iconSize, purpose: "decorative" });
1443
+ if (!hasValue2(iconName)) return null;
1444
+ return /* @__PURE__ */ import_react8.default.createElement(Icon, { name: iconName, size: iconSize, color: item == null ? void 0 : item.iconColor, purpose: "decorative" });
1321
1445
  };
1322
1446
  var FeedActions = ({ actions }) => {
1323
1447
  if (!Array.isArray(actions) || actions.length === 0) return actions || null;
@@ -1340,7 +1464,7 @@ var FeedField = ({ field, item, index }) => {
1340
1464
  if (field.visible && !field.visible(item)) return null;
1341
1465
  const value = getValue(item, field.field);
1342
1466
  const rendered = field.render ? field.render(value, item, index) : value;
1343
- if (!hasValue(rendered)) return null;
1467
+ if (!hasValue2(rendered)) return null;
1344
1468
  if (field.href) {
1345
1469
  const href = typeof field.href === "function" ? field.href(item) : field.href;
1346
1470
  return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { href }, rendered);
@@ -1377,7 +1501,7 @@ var renderPlacedFields = ({ fields, placement, item, index, inline = false }) =>
1377
1501
  return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: "xs" }, nodes);
1378
1502
  };
1379
1503
  var renderHeaderActions = (headerActions) => {
1380
- if (!hasValue(headerActions)) return null;
1504
+ if (!hasValue2(headerActions)) return null;
1381
1505
  if (!Array.isArray(headerActions)) return headerActions;
1382
1506
  return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "sm", align: "center" }, headerActions.filter(Boolean).map((action, index) => /* @__PURE__ */ import_react8.default.createElement(
1383
1507
  import_ui_extensions8.Link,
@@ -1399,6 +1523,8 @@ var DefaultFeedItem = ({
1399
1523
  collapsible,
1400
1524
  expanded,
1401
1525
  onToggleExpanded,
1526
+ isNew,
1527
+ newItemTagLabel,
1402
1528
  renderActor,
1403
1529
  renderTimestamp,
1404
1530
  renderMeta,
@@ -1418,7 +1544,7 @@ var DefaultFeedItem = ({
1418
1544
  const body = pickBody(item);
1419
1545
  const avatar = /* @__PURE__ */ import_react8.default.createElement(FeedActorAvatar, { item, avatarSize });
1420
1546
  const typeIcon = /* @__PURE__ */ import_react8.default.createElement(FeedTypeIcon, { item, iconSize });
1421
- const hasAvatarNode = hasValue(item == null ? void 0 : item.avatar) || hasValue(pickActorAvatar(rawActor));
1547
+ const hasAvatarNode = hasValue2(item == null ? void 0 : item.avatar) || hasValue2(pickActorAvatar(rawActor));
1422
1548
  const titleFields = fieldsForPlacement(fields, "title");
1423
1549
  const titleField = titleFields.length > 0 ? /* @__PURE__ */ import_react8.default.createElement(FeedField, { field: titleFields[0], item, index }) : null;
1424
1550
  const subtitleFields = renderPlacedFields({ fields, placement: "subtitle", item, index, inline: true });
@@ -1426,8 +1552,8 @@ var DefaultFeedItem = ({
1426
1552
  const bodyFields = renderPlacedFields({ fields, placement: "body", item, index });
1427
1553
  const footerFields = renderPlacedFields({ fields, placement: "footer", item, index, inline: true });
1428
1554
  const titleContent = titleField ?? (item == null ? void 0 : item.title) ?? (item == null ? void 0 : item.subject);
1429
- const title = hasValue(item == null ? void 0 : item.href) ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { href: item.href }, titleContent) : titleContent;
1430
- const titleText = hasValue(title) ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" }, truncate: true }, title) : null;
1555
+ const title = hasValue2(item == null ? void 0 : item.href) ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { href: item.href }, titleContent) : titleContent;
1556
+ const titleText = hasValue2(title) ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" }, truncate: true }, title) : null;
1431
1557
  const headerLeft = /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, collapsible ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Box, { flex: "none", alignSelf: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { variant: "dark", onClick: onToggleExpanded }, /* @__PURE__ */ import_react8.default.createElement(
1432
1558
  Icon,
1433
1559
  {
@@ -1435,10 +1561,10 @@ var DefaultFeedItem = ({
1435
1561
  size: "md",
1436
1562
  screenReaderText: expanded ? "Collapse" : "Expand"
1437
1563
  }
1438
- ))) : null, typeIcon, titleText);
1439
- const headerRight = hasValue(headerActions) || hasValue(timestamp) || hasValue(type) ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue(type) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, type), hasValue(timestamp) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
1564
+ ))) : null, typeIcon, titleText, isNew ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Box, { flex: "none" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tag, { variant: "info" }, newItemTagLabel ?? "New")) : null);
1565
+ const headerRight = hasValue2(headerActions) || hasValue2(timestamp) || hasValue2(type) ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue2(type) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, type), hasValue2(timestamp) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
1440
1566
  const showBody = !collapsible || expanded;
1441
- return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Box, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue(actor) || hasValue(subtitleFields) || hasValue(status)) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue(actor) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, actor), subtitleFields, hasValue(status) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.StatusTag, { variant: statusVariant, hollow: true }, status)), hasValue(body) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.List, { variant: "inline-divided" }, meta) : metaFields : hasValue(meta) ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue(actions) && /* @__PURE__ */ import_react8.default.createElement(FeedActions, { actions }), (hasValue(footer) || hasValue(footerFields)) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "xs", align: "center" }, footerFields, footer)) : null);
1567
+ return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Box, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue2(actor) || hasValue2(subtitleFields) || hasValue2(status)) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue2(actor) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { variant: "microcopy" }, actor), subtitleFields, hasValue2(status) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.StatusTag, { variant: statusVariant, hollow: true }, status)), hasValue2(body) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.List, { variant: "inline-divided" }, meta) : metaFields : hasValue2(meta) ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue2(actions) && /* @__PURE__ */ import_react8.default.createElement(FeedActions, { actions }), (hasValue2(footer) || hasValue2(footerFields)) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "xs", align: "center" }, footerFields, footer)) : null);
1442
1568
  };
1443
1569
  var applyTab = (items, activeTab, tabField) => {
1444
1570
  if (!activeTab || activeTab === "all") return items;
@@ -1662,10 +1788,19 @@ var Feed = ({
1662
1788
  collapsedIds,
1663
1789
  onCollapsedIdsChange,
1664
1790
  showCollapseToggle = true,
1665
- alignToolbarWithGroups = "auto"
1791
+ alignToolbarWithGroups = "auto",
1792
+ newItemsBehavior = "immediate",
1793
+ onNewItemsFlush,
1794
+ highlightNew = false,
1795
+ typePresets
1666
1796
  }) => {
1667
- const labels = { ...DEFAULT_LABELS3, ...labelOverrides || {} };
1668
- const safeItems = Array.isArray(items) ? items : [];
1797
+ const labels = (0, import_react8.useMemo)(() => ({ ...DEFAULT_LABELS3, ...labelOverrides || {} }), [labelOverrides]);
1798
+ const safeItems = Array.isArray(items) ? items : EMPTY_ITEMS;
1799
+ const resolvedTypePresets = typePresets === true ? DEFAULT_FEED_TYPE_PRESETS : typePresets;
1800
+ const presetItems = (0, import_react8.useMemo)(
1801
+ () => resolvedTypePresets ? safeItems.map((item) => applyTypePreset(item, resolvedTypePresets)) : safeItems,
1802
+ [safeItems, resolvedTypePresets]
1803
+ );
1669
1804
  const [internalTab, setInternalTab] = (0, import_react8.useState)(tabValue ?? defaultTab ?? "all");
1670
1805
  const [internalSearch, setInternalSearch] = (0, import_react8.useState)(searchValue ?? "");
1671
1806
  const [internalFilters, setInternalFilters] = (0, import_react8.useState)(filterValues ?? defaultFilterValues);
@@ -1678,6 +1813,47 @@ var Feed = ({
1678
1813
  return [];
1679
1814
  };
1680
1815
  const [internalCollapsedIds, setInternalCollapsedIds] = (0, import_react8.useState)(computeInitialCollapsed);
1816
+ const bufferNewItems = newItemsBehavior === "pill";
1817
+ const highlightMs = typeof highlightNew === "number" && highlightNew > 0 ? highlightNew : 0;
1818
+ const trackNewItems = bufferNewItems || highlightMs > 0;
1819
+ const [liveState, setLiveState] = (0, import_react8.useState)(INITIAL_LIVE_STATE);
1820
+ const liveKeyOf = (item, index) => getItemKey(item, index, getKey);
1821
+ if (trackNewItems && liveState.source !== presetItems) {
1822
+ const firstLoad = liveState.source === null;
1823
+ const partition = partitionNewItems(liveState.watermark, presetItems, pickTimestamp, {
1824
+ knownIds: new Set(liveState.knownKeys),
1825
+ getId: liveKeyOf
1826
+ });
1827
+ const now = Date.now();
1828
+ const keptNewKeys = highlightMs > 0 ? liveState.newKeys.filter((entry) => now - entry.at < highlightMs) : EMPTY_ITEMS;
1829
+ if (bufferNewItems) {
1830
+ setLiveState({
1831
+ source: presetItems,
1832
+ watermark: partition.newestTs,
1833
+ bufferedKeys: partition.bufferedIds,
1834
+ knownKeys: partition.visibleIds,
1835
+ newKeys: keptNewKeys
1836
+ });
1837
+ } else {
1838
+ const { newestTs } = flushBuffer(partition.visible, partition.buffered, pickTimestamp);
1839
+ setLiveState({
1840
+ source: presetItems,
1841
+ watermark: newestTs ?? partition.newestTs,
1842
+ bufferedKeys: EMPTY_ITEMS,
1843
+ knownKeys: [...partition.visibleIds, ...partition.bufferedIds],
1844
+ newKeys: highlightMs > 0 && !firstLoad ? [...keptNewKeys, ...partition.bufferedIds.map((key) => ({ key, at: now }))] : keptNewKeys
1845
+ });
1846
+ }
1847
+ }
1848
+ const bufferedKeySet = (0, import_react8.useMemo)(() => new Set(liveState.bufferedKeys), [liveState.bufferedKeys]);
1849
+ const newKeySet = (0, import_react8.useMemo)(
1850
+ () => new Set(liveState.newKeys.map((entry) => entry.key)),
1851
+ [liveState.newKeys]
1852
+ );
1853
+ const sourceItems = (0, import_react8.useMemo)(() => {
1854
+ if (!bufferNewItems || bufferedKeySet.size === 0) return presetItems;
1855
+ return presetItems.filter((item, index) => !bufferedKeySet.has(getItemKey(item, index, getKey)));
1856
+ }, [presetItems, bufferNewItems, bufferedKeySet, getKey]);
1681
1857
  const activeTab = tabValue !== void 0 ? tabValue : internalTab;
1682
1858
  const activeSearch = searchValue !== void 0 ? searchValue : internalSearch;
1683
1859
  const activeFilters = filterValues !== void 0 ? filterValues : internalFilters;
@@ -1700,6 +1876,17 @@ var Feed = ({
1700
1876
  (0, import_react8.useEffect)(() => {
1701
1877
  if (Array.isArray(collapsedIds)) setInternalCollapsedIds(collapsedIds);
1702
1878
  }, [collapsedIds]);
1879
+ (0, import_react8.useEffect)(() => {
1880
+ if (!highlightMs || liveState.newKeys.length === 0) return void 0;
1881
+ const earliestAt = Math.min(...liveState.newKeys.map((entry) => entry.at));
1882
+ const timer = setTimeout(() => {
1883
+ setLiveState((prev) => ({
1884
+ ...prev,
1885
+ newKeys: prev.newKeys.filter((entry) => Date.now() - entry.at < highlightMs)
1886
+ }));
1887
+ }, Math.max(16, earliestAt + highlightMs - Date.now()));
1888
+ return () => clearTimeout(timer);
1889
+ }, [highlightMs, liveState.newKeys]);
1703
1890
  const emitParamsChange = (next) => {
1704
1891
  if (typeof onParamsChange === "function") {
1705
1892
  onParamsChange({ tab: activeTab, search: activeSearch, filters: activeFilters, sort: activeSort, ...next });
@@ -1753,13 +1940,40 @@ var Feed = ({
1753
1940
  setCollapsedIds(collapsibleKeys);
1754
1941
  };
1755
1942
  const handleExpandAll = () => setCollapsedIds([]);
1943
+ const handleFlushNewItems = () => {
1944
+ const flushedItems = [];
1945
+ const flushedKeys = [];
1946
+ const keptItems = [];
1947
+ presetItems.forEach((item, index) => {
1948
+ const key = getItemKey(item, index, getKey);
1949
+ if (bufferedKeySet.has(key)) {
1950
+ flushedItems.push(item);
1951
+ flushedKeys.push(key);
1952
+ } else {
1953
+ keptItems.push(item);
1954
+ }
1955
+ });
1956
+ const { newestTs } = flushBuffer(keptItems, flushedItems, pickTimestamp);
1957
+ const now = Date.now();
1958
+ setLiveState((prev) => ({
1959
+ source: presetItems,
1960
+ watermark: newestTs ?? prev.watermark,
1961
+ bufferedKeys: EMPTY_ITEMS,
1962
+ knownKeys: presetItems.map((item, index) => getItemKey(item, index, getKey)),
1963
+ newKeys: highlightMs > 0 ? [
1964
+ ...prev.newKeys.filter((entry) => now - entry.at < highlightMs),
1965
+ ...flushedKeys.map((key) => ({ key, at: now }))
1966
+ ] : prev.newKeys
1967
+ }));
1968
+ onNewItemsFlush == null ? void 0 : onNewItemsFlush(flushedItems);
1969
+ };
1756
1970
  const processedItems = (0, import_react8.useMemo)(() => {
1757
- if (serverSide) return safeItems;
1758
- const tabbed = applyTab(safeItems, activeTab, tabField);
1971
+ if (serverSide) return sourceItems;
1972
+ const tabbed = applyTab(sourceItems, activeTab, tabField);
1759
1973
  const searched = applySearch(tabbed, activeSearch, searchFields);
1760
1974
  const filtered = applyFilters(searched, filters, activeFilters);
1761
1975
  return applySort(filtered, activeSort, sortOptions);
1762
- }, [safeItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
1976
+ }, [sourceItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
1763
1977
  const visibleItems = (0, import_react8.useMemo)(
1764
1978
  () => processedItems.slice(0, Math.max(0, resolvedMaxItems)),
1765
1979
  [processedItems, resolvedMaxItems]
@@ -1786,8 +2000,8 @@ var Feed = ({
1786
2000
  const canViewMore = visibleItems.length < processedItems.length;
1787
2001
  const shouldShowExternalLoadMore = hasMore && onLoadMore;
1788
2002
  const normalizedTabs = (0, import_react8.useMemo)(
1789
- () => normalizeTabs(tabs, safeItems, tabField, labels),
1790
- [tabs, safeItems, tabField, labels]
2003
+ () => normalizeTabs(tabs, presetItems, tabField, labels),
2004
+ [tabs, presetItems, tabField, labels]
1791
2005
  );
1792
2006
  const resolvedShowTabs = showTabs ?? normalizedTabs.length > 1;
1793
2007
  const sortControl = Array.isArray(sortOptions) && sortOptions.length > 0 ? /* @__PURE__ */ import_react8.default.createElement(
@@ -1802,7 +2016,7 @@ var Feed = ({
1802
2016
  ) : null;
1803
2017
  const countControl = showItemCount ? /* @__PURE__ */ import_react8.default.createElement(CollectionCount, { text: itemCountLabel }) : null;
1804
2018
  const toolbarRight = sortControl || countControl ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "sm", align: "center" }, sortControl, countControl) : null;
1805
- const firstGroupHasLabel = groups.length > 0 && hasValue(groups[0].label);
2019
+ const firstGroupHasLabel = groups.length > 0 && hasValue2(groups[0].label);
1806
2020
  const hasLeftToolbarControls = resolvedShowSearch || Array.isArray(filters) && filters.length > 0 || activeChips.length > 0;
1807
2021
  const alignControlsWithFirstGroup = !renderToolbar && showToolbar && !loading && !error && processedItems.length > 0 && !hasLeftToolbarControls && !!toolbarRight && firstGroupHasLabel && (alignToolbarWithGroups === true || alignToolbarWithGroups === "auto" && (groupByDate || !!groupBy));
1808
2022
  const toolbarNode = renderToolbar ? renderToolbar({
@@ -1852,15 +2066,21 @@ var Feed = ({
1852
2066
  return activeCollapsedIds.includes(getItemKey(item, i >= 0 ? i : idx, getKey));
1853
2067
  });
1854
2068
  const collapseToggle = showCollapseToggle && collapsibleVisibleItems.length > 1 && !loading && !error ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Link, { onClick: allCollapsed ? handleExpandAll : handleCollapseAll }, keepWordsTogether(allCollapsed ? labels.expandAll : labels.collapseAll)) : null;
1855
- if (hasValue(title) || hasValue(description) || hasValue(actions) || hasValue(children) || collapseToggle) {
1856
- const headerBody = /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: "xs" }, hasValue(title) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, title), hasValue(description) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, description), children);
1857
- const headerRight = hasValue(actions) || collapseToggle ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
2069
+ if (hasValue2(title) || hasValue2(description) || hasValue2(actions) || hasValue2(children) || collapseToggle) {
2070
+ const headerBody = /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", gap: "xs" }, hasValue2(title) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, title), hasValue2(description) && /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, description), children);
2071
+ const headerRight = hasValue2(actions) || collapseToggle ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Inline, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
1858
2072
  content.push(
1859
2073
  /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { key: "header", direction: "row", justify: "between", align: "start", gap: "sm" }, headerBody, headerRight)
1860
2074
  );
1861
2075
  }
1862
2076
  const bodyContent = [];
1863
2077
  if (toolbarNode) bodyContent.push(/* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, { key: "toolbar" }, toolbarNode));
2078
+ const pendingNewCount = bufferNewItems ? liveState.bufferedKeys.length : 0;
2079
+ if (pendingNewCount > 0 && !loading && !error) {
2080
+ bodyContent.push(
2081
+ /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { key: "new-items-pill", direction: "row", justify: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Button, { variant: "secondary", size: "small", onClick: handleFlushNewItems }, typeof labels.newItems === "function" ? labels.newItems(pendingNewCount) : labels.newItems))
2082
+ );
2083
+ }
1864
2084
  if (loading) {
1865
2085
  bodyContent.push(
1866
2086
  renderLoadingState ? renderLoadingState({ label: labels.loading }) : (
@@ -1877,14 +2097,14 @@ var Feed = ({
1877
2097
  message: labels.errorMessage
1878
2098
  }) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Alert, { key: "error", variant: "danger", title: typeof error === "string" ? error : labels.errorTitle }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, labels.errorMessage))
1879
2099
  );
1880
- } else if (processedItems.length === 0) {
2100
+ } else if (processedItems.length === 0 && pendingNewCount === 0) {
1881
2101
  bodyContent.push(
1882
2102
  renderEmptyState ? renderEmptyState({ title: labels.emptyTitle, message: labels.emptyMessage }) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tile, { key: "empty" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, null, labels.emptyMessage))))
1883
2103
  );
1884
2104
  } else {
1885
2105
  bodyContent.push(
1886
- /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group, groupIndex) => /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue(group.label) && (alignControlsWithFirstGroup && groupIndex === 0 ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, group.label), toolbarRight) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, group.label)), group.items.map((item, index) => {
1887
- const globalIndex = safeItems.indexOf(item);
2106
+ /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group, groupIndex) => /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue2(group.label) && (alignControlsWithFirstGroup && groupIndex === 0 ? /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, group.label), toolbarRight) : /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Text, { format: { fontWeight: "demibold" } }, group.label)), group.items.map((item, index) => {
2107
+ const globalIndex = presetItems.indexOf(item);
1888
2108
  const itemIndex = globalIndex >= 0 ? globalIndex : index;
1889
2109
  const key = getItemKey(item, itemIndex, getKey);
1890
2110
  const node = renderItem ? renderItem(item, itemIndex) : /* @__PURE__ */ import_react8.default.createElement(
@@ -1899,6 +2119,8 @@ var Feed = ({
1899
2119
  collapsible: collapsible !== false && itemHasExpandableContent(item, fields),
1900
2120
  expanded: !activeCollapsedIds.includes(key),
1901
2121
  onToggleExpanded: () => toggleItemExpanded(key),
2122
+ isNew: highlightMs > 0 && newKeySet.has(key),
2123
+ newItemTagLabel: labels.newItemTag,
1902
2124
  renderActor,
1903
2125
  renderTimestamp,
1904
2126
  renderMeta,
@@ -1945,7 +2167,14 @@ var Feed = ({
1945
2167
  if (container === "card" || container === "tile") return /* @__PURE__ */ import_react8.default.createElement(import_ui_extensions8.Tile, { compact: true }, feed);
1946
2168
  return feed;
1947
2169
  };
2170
+ Feed.displayName = "Feed";
1948
2171
  // Annotate the CommonJS export names for ESM import in node:
1949
2172
  0 && (module.exports = {
1950
- Feed
2173
+ DEFAULT_FEED_TYPE_PRESETS,
2174
+ Feed,
2175
+ applyTypePreset,
2176
+ flushBuffer,
2177
+ lookupTypePreset,
2178
+ partitionNewItems,
2179
+ toTimestampMs
1951
2180
  });