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.mjs CHANGED
@@ -545,7 +545,7 @@ var GENERATED_ICONS = {
545
545
  "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"] }
546
546
  };
547
547
 
548
- // src/common-components/Icon.js
548
+ // src/common-components/nativeIconNames.js
549
549
  var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
550
550
  "add",
551
551
  "appointment",
@@ -557,12 +557,12 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
557
557
  "block",
558
558
  "book",
559
559
  "bulb",
560
+ "callTranscript",
560
561
  "calling",
561
562
  "callingHangup",
562
563
  "callingMade",
563
564
  "callingMissed",
564
565
  "callingVoicemail",
565
- "callTranscript",
566
566
  "campaigns",
567
567
  "cap",
568
568
  "checkCircle",
@@ -591,13 +591,13 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
591
591
  "enroll",
592
592
  "exclamation",
593
593
  "exclamationCircle",
594
- "facebook",
595
594
  "faceHappy",
596
595
  "faceHappyFilled",
597
596
  "faceNeutral",
598
597
  "faceNeutralFilled",
599
598
  "faceSad",
600
599
  "faceSadFilled",
600
+ "facebook",
601
601
  "favoriteHollow",
602
602
  "file",
603
603
  "filledXCircleIcon",
@@ -738,6 +738,8 @@ var NATIVE_ICON_NAMES = /* @__PURE__ */ new Set([
738
738
  "zoomIn",
739
739
  "zoomOut"
740
740
  ]);
741
+
742
+ // src/common-components/Icon.js
741
743
  var NATIVE_COLORS = /* @__PURE__ */ new Set(["inherit", "alert", "warning", "success"]);
742
744
  var NATIVE_SIZE_TOKENS = {
743
745
  sm: "sm",
@@ -950,6 +952,7 @@ var CollectionFilterControl = ({
950
952
  { key: name, direction: "row", align: "center", gap: "xs" },
951
953
  h4(DateInput, {
952
954
  name: `${controlName}-from`,
955
+ label: filter.fromLabel ?? labels.dateFrom,
953
956
  placeholder: filter.fromLabel ?? labels.dateFrom,
954
957
  format: "medium",
955
958
  value: rangeValue.from ?? null,
@@ -958,6 +961,7 @@ var CollectionFilterControl = ({
958
961
  h4(Icon, { name: "right", size: "sm" }),
959
962
  h4(DateInput, {
960
963
  name: `${controlName}-to`,
964
+ label: filter.toLabel ?? labels.dateTo,
961
965
  placeholder: filter.toLabel ?? labels.dateTo,
962
966
  format: "medium",
963
967
  value: rangeValue.to ?? null,
@@ -1158,6 +1162,110 @@ var buildActiveFilterChips = (filters, values = {}, options = {}) => {
1158
1162
  return chips;
1159
1163
  };
1160
1164
 
1165
+ // src/feed/feedLiveBuffer.js
1166
+ var isDateValueObject = (v) => v != null && typeof v === "object" && typeof v.year === "number" && typeof v.month === "number" && typeof v.date === "number";
1167
+ var toTimestampMs = (value) => {
1168
+ if (value == null || value === "") return null;
1169
+ if (value instanceof Date) {
1170
+ const time2 = value.getTime();
1171
+ return Number.isNaN(time2) ? null : time2;
1172
+ }
1173
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
1174
+ if (isDateValueObject(value)) {
1175
+ return new Date(value.year, value.month, value.date, value.hour || 0, value.minute || 0).getTime();
1176
+ }
1177
+ const parsed = new Date(value);
1178
+ const time = parsed.getTime();
1179
+ return Number.isNaN(time) ? null : time;
1180
+ };
1181
+ var defaultGetId = (item, index) => (item == null ? void 0 : item.id) ?? (item == null ? void 0 : item.key) ?? index;
1182
+ var partitionNewItems = (prevNewestTs, items, getTs, options = {}) => {
1183
+ const { knownIds = null, getId = defaultGetId } = options;
1184
+ const safeItems = Array.isArray(items) ? items : [];
1185
+ const firstLoad = prevNewestTs == null;
1186
+ const visible = [];
1187
+ const visibleIds = [];
1188
+ const buffered = [];
1189
+ const bufferedIds = [];
1190
+ let newestTs = typeof prevNewestTs === "number" ? prevNewestTs : null;
1191
+ safeItems.forEach((item, index) => {
1192
+ const id = getId(item, index);
1193
+ const ts = toTimestampMs(typeof getTs === "function" ? getTs(item) : void 0);
1194
+ const isKnown = knownIds != null && id !== void 0 && knownIds.has(id);
1195
+ const isNewArrival = !firstLoad && !isKnown && ts != null && ts > prevNewestTs;
1196
+ if (isNewArrival) {
1197
+ buffered.push(item);
1198
+ bufferedIds.push(id);
1199
+ return;
1200
+ }
1201
+ visible.push(item);
1202
+ visibleIds.push(id);
1203
+ if (ts != null && (newestTs == null || ts > newestTs)) newestTs = ts;
1204
+ });
1205
+ return { visible, buffered, visibleIds, bufferedIds, newestTs };
1206
+ };
1207
+ var flushBuffer = (visible, buffered, getTs) => {
1208
+ const safeVisible = Array.isArray(visible) ? visible : [];
1209
+ const safeBuffered = Array.isArray(buffered) ? buffered : [];
1210
+ const items = [...safeBuffered, ...safeVisible];
1211
+ let newestTs = null;
1212
+ items.forEach((item) => {
1213
+ const ts = toTimestampMs(typeof getTs === "function" ? getTs(item) : void 0);
1214
+ if (ts != null && (newestTs == null || ts > newestTs)) newestTs = ts;
1215
+ });
1216
+ return { items, flushed: safeBuffered, newestTs };
1217
+ };
1218
+
1219
+ // src/feed/feedTypePresets.js
1220
+ var hasValue = (value) => value != null && value !== false && value !== "";
1221
+ var DEFAULT_FEED_TYPE_PRESETS = {
1222
+ call: { icon: "calling", label: "Call" },
1223
+ email: { icon: "email", label: "Email" },
1224
+ incoming_email: { icon: "inbox", label: "Incoming email" },
1225
+ forwarded_email: { icon: "forward", label: "Forwarded email" },
1226
+ meeting: { icon: "appointment", label: "Meeting" },
1227
+ note: { icon: "comment", label: "Note" },
1228
+ task: { icon: "tasks", label: "Task" },
1229
+ sms: { icon: "messages", label: "SMS" },
1230
+ whatsapp: { icon: "messages", label: "WhatsApp" },
1231
+ linkedin_message: { icon: "linkedin", label: "LinkedIn message" },
1232
+ postal_mail: { icon: "send", label: "Postal mail" },
1233
+ conversation: { icon: "questionAnswer", label: "Conversation" }
1234
+ };
1235
+ var lookupTypePreset = (type, presets) => {
1236
+ if (!presets || typeof presets !== "object") return null;
1237
+ if (type == null || type === "") return null;
1238
+ if (Object.prototype.hasOwnProperty.call(presets, type)) return presets[type];
1239
+ const lower = String(type).toLowerCase();
1240
+ if (Object.prototype.hasOwnProperty.call(presets, lower)) return presets[lower];
1241
+ const snake = lower.replace(/[\s-]+/g, "_");
1242
+ if (Object.prototype.hasOwnProperty.call(presets, snake)) return presets[snake];
1243
+ return null;
1244
+ };
1245
+ var applyTypePreset = (item, typePresets) => {
1246
+ if (item == null || typeof item !== "object") return item;
1247
+ const preset = lookupTypePreset(item.type, typePresets);
1248
+ if (!preset || typeof preset !== "object") return item;
1249
+ let next = null;
1250
+ const fill = (key, value) => {
1251
+ if (next === null) next = { ...item };
1252
+ next[key] = value;
1253
+ };
1254
+ if (!hasValue(item.icon) && !hasValue(item.iconName) && hasValue(preset.icon)) {
1255
+ fill("iconName", preset.icon);
1256
+ }
1257
+ if (!hasValue(item.iconColor) && hasValue(preset.color)) {
1258
+ fill("iconColor", preset.color);
1259
+ }
1260
+ if (!hasValue(item.typeLabel) && hasValue(preset.label)) {
1261
+ fill("typeLabel", preset.label);
1262
+ }
1263
+ if (item.statusVariant == null && item.outcomeVariant == null && item.severityVariant == null && hasValue(preset.statusVariant)) {
1264
+ fill("statusVariant", preset.statusVariant);
1265
+ }
1266
+ return next ?? item;
1267
+ };
1268
+
1161
1269
  // src/feed/Feed.jsx
1162
1270
  var DEFAULT_LABELS3 = {
1163
1271
  search: "Search activity...",
@@ -1171,6 +1279,8 @@ var DEFAULT_LABELS3 = {
1171
1279
  loadingMessage: "This should only take a moment.",
1172
1280
  loadingMore: "Loading...",
1173
1281
  loadMore: "View more",
1282
+ newItems: (count) => count === 1 ? "Show 1 new item" : `Show ${count} new items`,
1283
+ newItemTag: "New",
1174
1284
  collapseAll: "Collapse all",
1175
1285
  expandAll: "Expand all",
1176
1286
  emptyTitle: "No activity yet",
@@ -1183,7 +1293,15 @@ var DEFAULT_LABELS3 = {
1183
1293
  var DEFAULT_RECORD_LABEL = { singular: "item", plural: "items" };
1184
1294
  var DEFAULT_SEARCH_FIELDS = ["title", "subject", "body", "description", "content", "preview", "type", "typeLabel", "actorName", "author"];
1185
1295
  var DEFAULT_PAGE_SIZE = 5;
1186
- var hasValue = (value) => value != null && value !== false && value !== "";
1296
+ var EMPTY_ITEMS = [];
1297
+ var INITIAL_LIVE_STATE = {
1298
+ source: null,
1299
+ watermark: null,
1300
+ bufferedKeys: EMPTY_ITEMS,
1301
+ knownKeys: EMPTY_ITEMS,
1302
+ newKeys: EMPTY_ITEMS
1303
+ };
1304
+ var hasValue2 = (value) => value != null && value !== false && value !== "";
1187
1305
  var keepWordsTogether = (value) => typeof value === "string" ? value.replace(/\s+/g, "\xA0") : value;
1188
1306
  var getItemKey = (item, index, getKey) => {
1189
1307
  if (typeof getKey === "function") return getKey(item, index);
@@ -1210,11 +1328,11 @@ var pickHeaderActions = (item) => item == null ? void 0 : item.headerActions;
1210
1328
  var itemHasExpandableContent = (item, fields) => {
1211
1329
  if ((item == null ? void 0 : item.collapsible) === false) return false;
1212
1330
  if ((item == null ? void 0 : item.collapsible) === true) return true;
1213
- if (hasValue(pickBody(item))) return true;
1214
- if (hasValue(pickActor(item))) return true;
1215
- if (hasValue(item == null ? void 0 : item.actions)) return true;
1216
- if (hasValue(item == null ? void 0 : item.footer)) return true;
1217
- if (hasValue(item == null ? void 0 : item.meta) || hasValue(item == null ? void 0 : item.metadata)) return true;
1331
+ if (hasValue2(pickBody(item))) return true;
1332
+ if (hasValue2(pickActor(item))) return true;
1333
+ if (hasValue2(item == null ? void 0 : item.actions)) return true;
1334
+ if (hasValue2(item == null ? void 0 : item.footer)) return true;
1335
+ if (hasValue2(item == null ? void 0 : item.meta) || hasValue2(item == null ? void 0 : item.metadata)) return true;
1218
1336
  if (Array.isArray(fields)) {
1219
1337
  if (fields.some((f) => ["body", "footer"].includes(f.placement ?? "body"))) return true;
1220
1338
  }
@@ -1296,7 +1414,7 @@ var getRecordLabel = (recordLabel, count) => {
1296
1414
  var FeedActorAvatar = ({ item, avatarSize }) => {
1297
1415
  const actor = pickActor(item);
1298
1416
  const avatar = (item == null ? void 0 : item.avatar) ?? pickActorAvatar(actor);
1299
- if (!hasValue(avatar)) return null;
1417
+ if (!hasValue2(avatar)) return null;
1300
1418
  return /* @__PURE__ */ React8.createElement(
1301
1419
  AvatarStack,
1302
1420
  {
@@ -1308,10 +1426,10 @@ var FeedActorAvatar = ({ item, avatarSize }) => {
1308
1426
  );
1309
1427
  };
1310
1428
  var FeedTypeIcon = ({ item, iconSize }) => {
1311
- if (hasValue(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
1429
+ if (hasValue2(item == null ? void 0 : item.icon) && typeof item.icon !== "string") return item.icon;
1312
1430
  const iconName = typeof (item == null ? void 0 : item.icon) === "string" ? item.icon : item == null ? void 0 : item.iconName;
1313
- if (!hasValue(iconName)) return null;
1314
- return /* @__PURE__ */ React8.createElement(Icon, { name: iconName, size: iconSize, purpose: "decorative" });
1431
+ if (!hasValue2(iconName)) return null;
1432
+ return /* @__PURE__ */ React8.createElement(Icon, { name: iconName, size: iconSize, color: item == null ? void 0 : item.iconColor, purpose: "decorative" });
1315
1433
  };
1316
1434
  var FeedActions = ({ actions }) => {
1317
1435
  if (!Array.isArray(actions) || actions.length === 0) return actions || null;
@@ -1334,7 +1452,7 @@ var FeedField = ({ field, item, index }) => {
1334
1452
  if (field.visible && !field.visible(item)) return null;
1335
1453
  const value = getValue(item, field.field);
1336
1454
  const rendered = field.render ? field.render(value, item, index) : value;
1337
- if (!hasValue(rendered)) return null;
1455
+ if (!hasValue2(rendered)) return null;
1338
1456
  if (field.href) {
1339
1457
  const href = typeof field.href === "function" ? field.href(item) : field.href;
1340
1458
  return /* @__PURE__ */ React8.createElement(Link2, { href }, rendered);
@@ -1371,7 +1489,7 @@ var renderPlacedFields = ({ fields, placement, item, index, inline = false }) =>
1371
1489
  return /* @__PURE__ */ React8.createElement(Flex4, { direction: "column", gap: "xs" }, nodes);
1372
1490
  };
1373
1491
  var renderHeaderActions = (headerActions) => {
1374
- if (!hasValue(headerActions)) return null;
1492
+ if (!hasValue2(headerActions)) return null;
1375
1493
  if (!Array.isArray(headerActions)) return headerActions;
1376
1494
  return /* @__PURE__ */ React8.createElement(Inline, { gap: "sm", align: "center" }, headerActions.filter(Boolean).map((action, index) => /* @__PURE__ */ React8.createElement(
1377
1495
  Link2,
@@ -1393,6 +1511,8 @@ var DefaultFeedItem = ({
1393
1511
  collapsible,
1394
1512
  expanded,
1395
1513
  onToggleExpanded,
1514
+ isNew,
1515
+ newItemTagLabel,
1396
1516
  renderActor,
1397
1517
  renderTimestamp,
1398
1518
  renderMeta,
@@ -1412,7 +1532,7 @@ var DefaultFeedItem = ({
1412
1532
  const body = pickBody(item);
1413
1533
  const avatar = /* @__PURE__ */ React8.createElement(FeedActorAvatar, { item, avatarSize });
1414
1534
  const typeIcon = /* @__PURE__ */ React8.createElement(FeedTypeIcon, { item, iconSize });
1415
- const hasAvatarNode = hasValue(item == null ? void 0 : item.avatar) || hasValue(pickActorAvatar(rawActor));
1535
+ const hasAvatarNode = hasValue2(item == null ? void 0 : item.avatar) || hasValue2(pickActorAvatar(rawActor));
1416
1536
  const titleFields = fieldsForPlacement(fields, "title");
1417
1537
  const titleField = titleFields.length > 0 ? /* @__PURE__ */ React8.createElement(FeedField, { field: titleFields[0], item, index }) : null;
1418
1538
  const subtitleFields = renderPlacedFields({ fields, placement: "subtitle", item, index, inline: true });
@@ -1420,8 +1540,8 @@ var DefaultFeedItem = ({
1420
1540
  const bodyFields = renderPlacedFields({ fields, placement: "body", item, index });
1421
1541
  const footerFields = renderPlacedFields({ fields, placement: "footer", item, index, inline: true });
1422
1542
  const titleContent = titleField ?? (item == null ? void 0 : item.title) ?? (item == null ? void 0 : item.subject);
1423
- const title = hasValue(item == null ? void 0 : item.href) ? /* @__PURE__ */ React8.createElement(Link2, { href: item.href }, titleContent) : titleContent;
1424
- const titleText = hasValue(title) ? /* @__PURE__ */ React8.createElement(Text2, { format: { fontWeight: "demibold" }, truncate: true }, title) : null;
1543
+ const title = hasValue2(item == null ? void 0 : item.href) ? /* @__PURE__ */ React8.createElement(Link2, { href: item.href }, titleContent) : titleContent;
1544
+ const titleText = hasValue2(title) ? /* @__PURE__ */ React8.createElement(Text2, { format: { fontWeight: "demibold" }, truncate: true }, title) : null;
1425
1545
  const headerLeft = /* @__PURE__ */ React8.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "nowrap" }, collapsible ? /* @__PURE__ */ React8.createElement(Box2, { flex: "none", alignSelf: "center" }, /* @__PURE__ */ React8.createElement(Link2, { variant: "dark", onClick: onToggleExpanded }, /* @__PURE__ */ React8.createElement(
1426
1546
  Icon,
1427
1547
  {
@@ -1429,10 +1549,10 @@ var DefaultFeedItem = ({
1429
1549
  size: "md",
1430
1550
  screenReaderText: expanded ? "Collapse" : "Expand"
1431
1551
  }
1432
- ))) : null, typeIcon, titleText);
1433
- const headerRight = hasValue(headerActions) || hasValue(timestamp) || hasValue(type) ? /* @__PURE__ */ React8.createElement(Inline, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue(type) && /* @__PURE__ */ React8.createElement(Text2, { variant: "microcopy" }, type), hasValue(timestamp) && /* @__PURE__ */ React8.createElement(Text2, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
1552
+ ))) : null, typeIcon, titleText, isNew ? /* @__PURE__ */ React8.createElement(Box2, { flex: "none" }, /* @__PURE__ */ React8.createElement(Tag2, { variant: "info" }, newItemTagLabel ?? "New")) : null);
1553
+ const headerRight = hasValue2(headerActions) || hasValue2(timestamp) || hasValue2(type) ? /* @__PURE__ */ React8.createElement(Inline, { gap: "sm", align: "center" }, renderHeaderActions(headerActions), hasValue2(type) && /* @__PURE__ */ React8.createElement(Text2, { variant: "microcopy" }, type), hasValue2(timestamp) && /* @__PURE__ */ React8.createElement(Text2, { variant: "microcopy" }, formatTimestamp(timestamp))) : null;
1434
1554
  const showBody = !collapsible || expanded;
1435
- return /* @__PURE__ */ React8.createElement(Flex4, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ React8.createElement(Flex4, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ React8.createElement(Box2, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ React8.createElement(Flex4, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue(actor) || hasValue(subtitleFields) || hasValue(status)) && /* @__PURE__ */ React8.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue(actor) && /* @__PURE__ */ React8.createElement(Text2, { variant: "microcopy" }, actor), subtitleFields, hasValue(status) && /* @__PURE__ */ React8.createElement(StatusTag, { variant: statusVariant, hollow: true }, status)), hasValue(body) && /* @__PURE__ */ React8.createElement(Text2, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ React8.createElement(List, { variant: "inline-divided" }, meta) : metaFields : hasValue(meta) ? /* @__PURE__ */ React8.createElement(Inline, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue(actions) && /* @__PURE__ */ React8.createElement(FeedActions, { actions }), (hasValue(footer) || hasValue(footerFields)) && /* @__PURE__ */ React8.createElement(Inline, { gap: "xs", align: "center" }, footerFields, footer)) : null);
1555
+ return /* @__PURE__ */ React8.createElement(Flex4, { direction: "column", gap: compact ? "xs" : "sm" }, /* @__PURE__ */ React8.createElement(Flex4, { direction: "row", justify: "between", align: "center", gap: "sm", wrap: "nowrap" }, /* @__PURE__ */ React8.createElement(Box2, { flex: 1 }, headerLeft), headerRight), showBody ? /* @__PURE__ */ React8.createElement(Flex4, { direction: "column", gap: compact ? "xs" : "sm" }, (hasAvatarNode || hasValue2(actor) || hasValue2(subtitleFields) || hasValue2(status)) && /* @__PURE__ */ React8.createElement(Flex4, { direction: "row", align: "center", gap: "xs", wrap: "wrap" }, hasAvatarNode ? avatar : null, hasValue2(actor) && /* @__PURE__ */ React8.createElement(Text2, { variant: "microcopy" }, actor), subtitleFields, hasValue2(status) && /* @__PURE__ */ React8.createElement(StatusTag, { variant: statusVariant, hollow: true }, status)), hasValue2(body) && /* @__PURE__ */ React8.createElement(Text2, null, body), bodyFields, Array.isArray(meta) ? meta.length > 0 ? /* @__PURE__ */ React8.createElement(List, { variant: "inline-divided" }, meta) : metaFields : hasValue2(meta) ? /* @__PURE__ */ React8.createElement(Inline, { gap: "xs" }, meta) : metaFields ? metaFields : null, hasValue2(actions) && /* @__PURE__ */ React8.createElement(FeedActions, { actions }), (hasValue2(footer) || hasValue2(footerFields)) && /* @__PURE__ */ React8.createElement(Inline, { gap: "xs", align: "center" }, footerFields, footer)) : null);
1436
1556
  };
1437
1557
  var applyTab = (items, activeTab, tabField) => {
1438
1558
  if (!activeTab || activeTab === "all") return items;
@@ -1656,10 +1776,19 @@ var Feed = ({
1656
1776
  collapsedIds,
1657
1777
  onCollapsedIdsChange,
1658
1778
  showCollapseToggle = true,
1659
- alignToolbarWithGroups = "auto"
1779
+ alignToolbarWithGroups = "auto",
1780
+ newItemsBehavior = "immediate",
1781
+ onNewItemsFlush,
1782
+ highlightNew = false,
1783
+ typePresets
1660
1784
  }) => {
1661
- const labels = { ...DEFAULT_LABELS3, ...labelOverrides || {} };
1662
- const safeItems = Array.isArray(items) ? items : [];
1785
+ const labels = useMemo(() => ({ ...DEFAULT_LABELS3, ...labelOverrides || {} }), [labelOverrides]);
1786
+ const safeItems = Array.isArray(items) ? items : EMPTY_ITEMS;
1787
+ const resolvedTypePresets = typePresets === true ? DEFAULT_FEED_TYPE_PRESETS : typePresets;
1788
+ const presetItems = useMemo(
1789
+ () => resolvedTypePresets ? safeItems.map((item) => applyTypePreset(item, resolvedTypePresets)) : safeItems,
1790
+ [safeItems, resolvedTypePresets]
1791
+ );
1663
1792
  const [internalTab, setInternalTab] = useState2(tabValue ?? defaultTab ?? "all");
1664
1793
  const [internalSearch, setInternalSearch] = useState2(searchValue ?? "");
1665
1794
  const [internalFilters, setInternalFilters] = useState2(filterValues ?? defaultFilterValues);
@@ -1672,6 +1801,47 @@ var Feed = ({
1672
1801
  return [];
1673
1802
  };
1674
1803
  const [internalCollapsedIds, setInternalCollapsedIds] = useState2(computeInitialCollapsed);
1804
+ const bufferNewItems = newItemsBehavior === "pill";
1805
+ const highlightMs = typeof highlightNew === "number" && highlightNew > 0 ? highlightNew : 0;
1806
+ const trackNewItems = bufferNewItems || highlightMs > 0;
1807
+ const [liveState, setLiveState] = useState2(INITIAL_LIVE_STATE);
1808
+ const liveKeyOf = (item, index) => getItemKey(item, index, getKey);
1809
+ if (trackNewItems && liveState.source !== presetItems) {
1810
+ const firstLoad = liveState.source === null;
1811
+ const partition = partitionNewItems(liveState.watermark, presetItems, pickTimestamp, {
1812
+ knownIds: new Set(liveState.knownKeys),
1813
+ getId: liveKeyOf
1814
+ });
1815
+ const now = Date.now();
1816
+ const keptNewKeys = highlightMs > 0 ? liveState.newKeys.filter((entry) => now - entry.at < highlightMs) : EMPTY_ITEMS;
1817
+ if (bufferNewItems) {
1818
+ setLiveState({
1819
+ source: presetItems,
1820
+ watermark: partition.newestTs,
1821
+ bufferedKeys: partition.bufferedIds,
1822
+ knownKeys: partition.visibleIds,
1823
+ newKeys: keptNewKeys
1824
+ });
1825
+ } else {
1826
+ const { newestTs } = flushBuffer(partition.visible, partition.buffered, pickTimestamp);
1827
+ setLiveState({
1828
+ source: presetItems,
1829
+ watermark: newestTs ?? partition.newestTs,
1830
+ bufferedKeys: EMPTY_ITEMS,
1831
+ knownKeys: [...partition.visibleIds, ...partition.bufferedIds],
1832
+ newKeys: highlightMs > 0 && !firstLoad ? [...keptNewKeys, ...partition.bufferedIds.map((key) => ({ key, at: now }))] : keptNewKeys
1833
+ });
1834
+ }
1835
+ }
1836
+ const bufferedKeySet = useMemo(() => new Set(liveState.bufferedKeys), [liveState.bufferedKeys]);
1837
+ const newKeySet = useMemo(
1838
+ () => new Set(liveState.newKeys.map((entry) => entry.key)),
1839
+ [liveState.newKeys]
1840
+ );
1841
+ const sourceItems = useMemo(() => {
1842
+ if (!bufferNewItems || bufferedKeySet.size === 0) return presetItems;
1843
+ return presetItems.filter((item, index) => !bufferedKeySet.has(getItemKey(item, index, getKey)));
1844
+ }, [presetItems, bufferNewItems, bufferedKeySet, getKey]);
1675
1845
  const activeTab = tabValue !== void 0 ? tabValue : internalTab;
1676
1846
  const activeSearch = searchValue !== void 0 ? searchValue : internalSearch;
1677
1847
  const activeFilters = filterValues !== void 0 ? filterValues : internalFilters;
@@ -1694,6 +1864,17 @@ var Feed = ({
1694
1864
  useEffect(() => {
1695
1865
  if (Array.isArray(collapsedIds)) setInternalCollapsedIds(collapsedIds);
1696
1866
  }, [collapsedIds]);
1867
+ useEffect(() => {
1868
+ if (!highlightMs || liveState.newKeys.length === 0) return void 0;
1869
+ const earliestAt = Math.min(...liveState.newKeys.map((entry) => entry.at));
1870
+ const timer = setTimeout(() => {
1871
+ setLiveState((prev) => ({
1872
+ ...prev,
1873
+ newKeys: prev.newKeys.filter((entry) => Date.now() - entry.at < highlightMs)
1874
+ }));
1875
+ }, Math.max(16, earliestAt + highlightMs - Date.now()));
1876
+ return () => clearTimeout(timer);
1877
+ }, [highlightMs, liveState.newKeys]);
1697
1878
  const emitParamsChange = (next) => {
1698
1879
  if (typeof onParamsChange === "function") {
1699
1880
  onParamsChange({ tab: activeTab, search: activeSearch, filters: activeFilters, sort: activeSort, ...next });
@@ -1747,13 +1928,40 @@ var Feed = ({
1747
1928
  setCollapsedIds(collapsibleKeys);
1748
1929
  };
1749
1930
  const handleExpandAll = () => setCollapsedIds([]);
1931
+ const handleFlushNewItems = () => {
1932
+ const flushedItems = [];
1933
+ const flushedKeys = [];
1934
+ const keptItems = [];
1935
+ presetItems.forEach((item, index) => {
1936
+ const key = getItemKey(item, index, getKey);
1937
+ if (bufferedKeySet.has(key)) {
1938
+ flushedItems.push(item);
1939
+ flushedKeys.push(key);
1940
+ } else {
1941
+ keptItems.push(item);
1942
+ }
1943
+ });
1944
+ const { newestTs } = flushBuffer(keptItems, flushedItems, pickTimestamp);
1945
+ const now = Date.now();
1946
+ setLiveState((prev) => ({
1947
+ source: presetItems,
1948
+ watermark: newestTs ?? prev.watermark,
1949
+ bufferedKeys: EMPTY_ITEMS,
1950
+ knownKeys: presetItems.map((item, index) => getItemKey(item, index, getKey)),
1951
+ newKeys: highlightMs > 0 ? [
1952
+ ...prev.newKeys.filter((entry) => now - entry.at < highlightMs),
1953
+ ...flushedKeys.map((key) => ({ key, at: now }))
1954
+ ] : prev.newKeys
1955
+ }));
1956
+ onNewItemsFlush == null ? void 0 : onNewItemsFlush(flushedItems);
1957
+ };
1750
1958
  const processedItems = useMemo(() => {
1751
- if (serverSide) return safeItems;
1752
- const tabbed = applyTab(safeItems, activeTab, tabField);
1959
+ if (serverSide) return sourceItems;
1960
+ const tabbed = applyTab(sourceItems, activeTab, tabField);
1753
1961
  const searched = applySearch(tabbed, activeSearch, searchFields);
1754
1962
  const filtered = applyFilters(searched, filters, activeFilters);
1755
1963
  return applySort(filtered, activeSort, sortOptions);
1756
- }, [safeItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
1964
+ }, [sourceItems, activeTab, tabField, activeSearch, searchFields, filters, activeFilters, activeSort, sortOptions, serverSide]);
1757
1965
  const visibleItems = useMemo(
1758
1966
  () => processedItems.slice(0, Math.max(0, resolvedMaxItems)),
1759
1967
  [processedItems, resolvedMaxItems]
@@ -1780,8 +1988,8 @@ var Feed = ({
1780
1988
  const canViewMore = visibleItems.length < processedItems.length;
1781
1989
  const shouldShowExternalLoadMore = hasMore && onLoadMore;
1782
1990
  const normalizedTabs = useMemo(
1783
- () => normalizeTabs(tabs, safeItems, tabField, labels),
1784
- [tabs, safeItems, tabField, labels]
1991
+ () => normalizeTabs(tabs, presetItems, tabField, labels),
1992
+ [tabs, presetItems, tabField, labels]
1785
1993
  );
1786
1994
  const resolvedShowTabs = showTabs ?? normalizedTabs.length > 1;
1787
1995
  const sortControl = Array.isArray(sortOptions) && sortOptions.length > 0 ? /* @__PURE__ */ React8.createElement(
@@ -1796,7 +2004,7 @@ var Feed = ({
1796
2004
  ) : null;
1797
2005
  const countControl = showItemCount ? /* @__PURE__ */ React8.createElement(CollectionCount, { text: itemCountLabel }) : null;
1798
2006
  const toolbarRight = sortControl || countControl ? /* @__PURE__ */ React8.createElement(Inline, { gap: "sm", align: "center" }, sortControl, countControl) : null;
1799
- const firstGroupHasLabel = groups.length > 0 && hasValue(groups[0].label);
2007
+ const firstGroupHasLabel = groups.length > 0 && hasValue2(groups[0].label);
1800
2008
  const hasLeftToolbarControls = resolvedShowSearch || Array.isArray(filters) && filters.length > 0 || activeChips.length > 0;
1801
2009
  const alignControlsWithFirstGroup = !renderToolbar && showToolbar && !loading && !error && processedItems.length > 0 && !hasLeftToolbarControls && !!toolbarRight && firstGroupHasLabel && (alignToolbarWithGroups === true || alignToolbarWithGroups === "auto" && (groupByDate || !!groupBy));
1802
2010
  const toolbarNode = renderToolbar ? renderToolbar({
@@ -1846,15 +2054,21 @@ var Feed = ({
1846
2054
  return activeCollapsedIds.includes(getItemKey(item, i >= 0 ? i : idx, getKey));
1847
2055
  });
1848
2056
  const collapseToggle = showCollapseToggle && collapsibleVisibleItems.length > 1 && !loading && !error ? /* @__PURE__ */ React8.createElement(Link2, { onClick: allCollapsed ? handleExpandAll : handleCollapseAll }, keepWordsTogether(allCollapsed ? labels.expandAll : labels.collapseAll)) : null;
1849
- if (hasValue(title) || hasValue(description) || hasValue(actions) || hasValue(children) || collapseToggle) {
1850
- const headerBody = /* @__PURE__ */ React8.createElement(Flex4, { direction: "column", gap: "xs" }, hasValue(title) && /* @__PURE__ */ React8.createElement(Text2, { format: { fontWeight: "demibold" } }, title), hasValue(description) && /* @__PURE__ */ React8.createElement(Text2, null, description), children);
1851
- const headerRight = hasValue(actions) || collapseToggle ? /* @__PURE__ */ React8.createElement(Inline, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
2057
+ if (hasValue2(title) || hasValue2(description) || hasValue2(actions) || hasValue2(children) || collapseToggle) {
2058
+ const headerBody = /* @__PURE__ */ React8.createElement(Flex4, { direction: "column", gap: "xs" }, hasValue2(title) && /* @__PURE__ */ React8.createElement(Text2, { format: { fontWeight: "demibold" } }, title), hasValue2(description) && /* @__PURE__ */ React8.createElement(Text2, null, description), children);
2059
+ const headerRight = hasValue2(actions) || collapseToggle ? /* @__PURE__ */ React8.createElement(Inline, { gap: "sm", align: "center" }, actions, collapseToggle) : null;
1852
2060
  content.push(
1853
2061
  /* @__PURE__ */ React8.createElement(Flex4, { key: "header", direction: "row", justify: "between", align: "start", gap: "sm" }, headerBody, headerRight)
1854
2062
  );
1855
2063
  }
1856
2064
  const bodyContent = [];
1857
2065
  if (toolbarNode) bodyContent.push(/* @__PURE__ */ React8.createElement(React8.Fragment, { key: "toolbar" }, toolbarNode));
2066
+ const pendingNewCount = bufferNewItems ? liveState.bufferedKeys.length : 0;
2067
+ if (pendingNewCount > 0 && !loading && !error) {
2068
+ bodyContent.push(
2069
+ /* @__PURE__ */ React8.createElement(Flex4, { key: "new-items-pill", direction: "row", justify: "center" }, /* @__PURE__ */ React8.createElement(Button3, { variant: "secondary", size: "small", onClick: handleFlushNewItems }, typeof labels.newItems === "function" ? labels.newItems(pendingNewCount) : labels.newItems))
2070
+ );
2071
+ }
1858
2072
  if (loading) {
1859
2073
  bodyContent.push(
1860
2074
  renderLoadingState ? renderLoadingState({ label: labels.loading }) : (
@@ -1871,14 +2085,14 @@ var Feed = ({
1871
2085
  message: labels.errorMessage
1872
2086
  }) : /* @__PURE__ */ React8.createElement(Alert, { key: "error", variant: "danger", title: typeof error === "string" ? error : labels.errorTitle }, /* @__PURE__ */ React8.createElement(Text2, null, labels.errorMessage))
1873
2087
  );
1874
- } else if (processedItems.length === 0) {
2088
+ } else if (processedItems.length === 0 && pendingNewCount === 0) {
1875
2089
  bodyContent.push(
1876
2090
  renderEmptyState ? renderEmptyState({ title: labels.emptyTitle, message: labels.emptyMessage }) : /* @__PURE__ */ React8.createElement(Tile, { key: "empty" }, /* @__PURE__ */ React8.createElement(Flex4, { direction: "column", align: "center", justify: "center" }, /* @__PURE__ */ React8.createElement(EmptyState, { title: labels.emptyTitle, layout: "vertical" }, /* @__PURE__ */ React8.createElement(Text2, null, labels.emptyMessage))))
1877
2091
  );
1878
2092
  } else {
1879
2093
  bodyContent.push(
1880
- /* @__PURE__ */ React8.createElement(Flex4, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group, groupIndex) => /* @__PURE__ */ React8.createElement(Flex4, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue(group.label) && (alignControlsWithFirstGroup && groupIndex === 0 ? /* @__PURE__ */ React8.createElement(Flex4, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ React8.createElement(Text2, { format: { fontWeight: "demibold" } }, group.label), toolbarRight) : /* @__PURE__ */ React8.createElement(Text2, { format: { fontWeight: "demibold" } }, group.label)), group.items.map((item, index) => {
1881
- const globalIndex = safeItems.indexOf(item);
2094
+ /* @__PURE__ */ React8.createElement(Flex4, { key: "items", direction: "column", gap: compact ? "xs" : gap }, groups.map((group, groupIndex) => /* @__PURE__ */ React8.createElement(Flex4, { key: group.key, direction: "column", gap: compact ? "xs" : gap }, hasValue2(group.label) && (alignControlsWithFirstGroup && groupIndex === 0 ? /* @__PURE__ */ React8.createElement(Flex4, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ React8.createElement(Text2, { format: { fontWeight: "demibold" } }, group.label), toolbarRight) : /* @__PURE__ */ React8.createElement(Text2, { format: { fontWeight: "demibold" } }, group.label)), group.items.map((item, index) => {
2095
+ const globalIndex = presetItems.indexOf(item);
1882
2096
  const itemIndex = globalIndex >= 0 ? globalIndex : index;
1883
2097
  const key = getItemKey(item, itemIndex, getKey);
1884
2098
  const node = renderItem ? renderItem(item, itemIndex) : /* @__PURE__ */ React8.createElement(
@@ -1893,6 +2107,8 @@ var Feed = ({
1893
2107
  collapsible: collapsible !== false && itemHasExpandableContent(item, fields),
1894
2108
  expanded: !activeCollapsedIds.includes(key),
1895
2109
  onToggleExpanded: () => toggleItemExpanded(key),
2110
+ isNew: highlightMs > 0 && newKeySet.has(key),
2111
+ newItemTagLabel: labels.newItemTag,
1896
2112
  renderActor,
1897
2113
  renderTimestamp,
1898
2114
  renderMeta,
@@ -1939,6 +2155,13 @@ var Feed = ({
1939
2155
  if (container === "card" || container === "tile") return /* @__PURE__ */ React8.createElement(Tile, { compact: true }, feed);
1940
2156
  return feed;
1941
2157
  };
2158
+ Feed.displayName = "Feed";
1942
2159
  export {
1943
- Feed
2160
+ DEFAULT_FEED_TYPE_PRESETS,
2161
+ Feed,
2162
+ applyTypePreset,
2163
+ flushBuffer,
2164
+ lookupTypePreset,
2165
+ partitionNewItems,
2166
+ toTimestampMs
1944
2167
  };