@limetech/lime-crm-building-blocks 1.118.0 → 1.119.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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/lime-crm-building-blocks.cjs.js +1 -1
- package/dist/cjs/limebb-alert-dialog.cjs.entry.js +1 -1
- package/dist/cjs/limebb-browser.cjs.entry.js +2 -2
- package/dist/cjs/limebb-chat-icon-list_3.cjs.entry.js +4 -4
- package/dist/cjs/limebb-chat-list.cjs.entry.js +336 -68
- package/dist/cjs/limebb-composer-toolbar.cjs.entry.js +2 -2
- package/dist/cjs/limebb-currency-picker.cjs.entry.js +1 -1
- package/dist/cjs/limebb-dashboard-widget.cjs.entry.js +1 -1
- package/dist/cjs/limebb-data-cells.cjs.entry.js +22 -1
- package/dist/cjs/limebb-date-picker.cjs.entry.js +1 -1
- package/dist/cjs/limebb-date-range.cjs.entry.js +1 -1
- package/dist/cjs/limebb-document-chips.cjs.entry.js +1 -1
- package/dist/cjs/limebb-document-item.cjs.entry.js +2 -2
- package/dist/cjs/limebb-document-picker.cjs.entry.js +1 -1
- package/dist/cjs/limebb-feed-item-thumbnail-file-info.cjs.entry.js +1 -1
- package/dist/cjs/limebb-feed-timeline-item.cjs.entry.js +1 -1
- package/dist/cjs/limebb-feed.cjs.entry.js +1 -1
- package/dist/cjs/limebb-icon-picker.cjs.entry.js +1 -1
- package/dist/cjs/limebb-info-tile-format.cjs.entry.js +1 -1
- package/dist/cjs/limebb-info-tile.cjs.entry.js +1 -1
- package/dist/cjs/limebb-kanban-group.cjs.entry.js +1 -1
- package/dist/cjs/limebb-kanban-item.cjs.entry.js +1 -1
- package/dist/cjs/limebb-lime-query-builder.cjs.entry.js +1 -1
- package/dist/cjs/limebb-lime-query-filter-builder_3.cjs.entry.js +2 -2
- package/dist/cjs/limebb-lime-query-filter-comparison_2.cjs.entry.js +1 -1
- package/dist/cjs/limebb-lime-query-filter-group_3.cjs.entry.js +3 -3
- package/dist/cjs/limebb-lime-query-order-by-item.cjs.entry.js +2 -2
- package/dist/cjs/limebb-lime-query-response-format-builder.cjs.entry.js +1 -1
- package/dist/cjs/limebb-lime-query-response-format-editor_2.cjs.entry.js +1 -1
- package/dist/cjs/limebb-live-docs-info.cjs.entry.js +2 -2
- package/dist/cjs/limebb-locale-picker.cjs.entry.js +1 -1
- package/dist/cjs/limebb-mention-group-counter.cjs.entry.js +2 -2
- package/dist/cjs/limebb-navigation-button_2.cjs.entry.js +2 -2
- package/dist/cjs/limebb-notification-item.cjs.entry.js +1 -1
- package/dist/cjs/limebb-percentage-visualizer.cjs.entry.js +2 -2
- package/dist/cjs/limebb-text-editor.cjs.entry.js +1 -1
- package/dist/cjs/limebb-trend-indicator.cjs.entry.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/alert-dialog/alert-dialog.js +1 -1
- package/dist/collection/components/browser/browser.js +2 -2
- package/dist/collection/components/chat-list/chat-icon-list/chat-icon-list.js +1 -1
- package/dist/collection/components/chat-list/chat-item/chat-item.js +2 -2
- package/dist/collection/components/chat-list/chat-list.css +10 -10
- package/dist/collection/components/chat-list/chat-list.js +93 -25
- package/dist/collection/components/chat-list/process-chat-items.js +111 -45
- package/dist/collection/components/chat-list/scroll-anchor.js +93 -0
- package/dist/collection/components/chat-list/typing-indicator/typing-indicator.js +1 -1
- package/dist/collection/components/chat-list/typing-indicator-scroll.js +41 -0
- package/dist/collection/components/composer-toolbar/composer-toolbar.js +2 -2
- package/dist/collection/components/currency-picker/currency-picker.js +1 -1
- package/dist/collection/components/dashboard-widget/dashboard-widget.js +1 -1
- package/dist/collection/components/data-cells/data-cells.js +22 -1
- package/dist/collection/components/date-picker/date-picker.js +1 -1
- package/dist/collection/components/date-range/date-range.js +1 -1
- package/dist/collection/components/document-chips/document-chips.js +1 -1
- package/dist/collection/components/document-picker/document-item/document-item.js +2 -2
- package/dist/collection/components/document-picker/document-picker.js +1 -1
- package/dist/collection/components/feed/feed-item/feed-timeline-item.js +1 -1
- package/dist/collection/components/feed/feed-item-thumbnail-file-info/feed-item-thumbnail-file-info.js +1 -1
- package/dist/collection/components/feed/feed.js +1 -1
- package/dist/collection/components/icon-picker/icon-picker.js +1 -1
- package/dist/collection/components/info-tile/format/config/info-tile-format.js +1 -1
- package/dist/collection/components/info-tile/info-tile.js +1 -1
- package/dist/collection/components/kanban/kanban-group/kanban-group.js +1 -1
- package/dist/collection/components/kanban/kanban-item/kanban-item.js +1 -1
- package/dist/collection/components/lime-query-builder/expressions/lime-query-filter-comparison.js +1 -1
- package/dist/collection/components/lime-query-builder/expressions/lime-query-filter-group.js +2 -2
- package/dist/collection/components/lime-query-builder/expressions/lime-query-filter-not.js +1 -1
- package/dist/collection/components/lime-query-builder/lime-query-builder.js +1 -1
- package/dist/collection/components/lime-query-builder/lime-query-response-format-builder.js +1 -1
- package/dist/collection/components/lime-query-builder/limetype-field/limetype-field.js +1 -1
- package/dist/collection/components/lime-query-builder/order-by/order-by-editor.js +1 -1
- package/dist/collection/components/lime-query-builder/order-by/order-by-item.js +2 -2
- package/dist/collection/components/lime-query-builder/response-format/response-format-item.js +1 -1
- package/dist/collection/components/limeobject/file-viewer/live-docs-info.js +2 -2
- package/dist/collection/components/locale-picker/locale-picker.js +1 -1
- package/dist/collection/components/notification-list/notification-item/notification-item.js +1 -1
- package/dist/collection/components/percentage-visualizer/percentage-visualizer.js +2 -2
- package/dist/collection/components/summary-popover/summary-popover.js +2 -2
- package/dist/collection/components/text-editor/mention-group-counter.js +2 -2
- package/dist/collection/components/text-editor/text-editor.js +1 -1
- package/dist/collection/components/trend-indicator/trend-indicator.js +1 -1
- package/dist/components/chat-icon-list.js +1 -1
- package/dist/components/chat-item.js +1 -1
- package/dist/components/currency-picker.js +1 -1
- package/dist/components/date-picker.js +1 -1
- package/dist/components/document-item.js +1 -1
- package/dist/components/feed-item-thumbnail-file-info.js +1 -1
- package/dist/components/feed-timeline-item.js +1 -1
- package/dist/components/kanban-group.js +1 -1
- package/dist/components/kanban-item.js +1 -1
- package/dist/components/lime-query-filter-comparison.js +1 -1
- package/dist/components/lime-query-filter-expression.js +1 -1
- package/dist/components/limebb-alert-dialog.js +1 -1
- package/dist/components/limebb-browser.js +1 -1
- package/dist/components/limebb-chat-list.js +1 -1
- package/dist/components/limebb-composer-toolbar.js +1 -1
- package/dist/components/limebb-dashboard-widget.js +1 -1
- package/dist/components/limebb-data-cells.js +1 -1
- package/dist/components/limebb-date-range.js +1 -1
- package/dist/components/limebb-document-chips.js +1 -1
- package/dist/components/limebb-document-picker.js +1 -1
- package/dist/components/limebb-feed.js +1 -1
- package/dist/components/limebb-icon-picker.js +1 -1
- package/dist/components/limebb-info-tile-format.js +1 -1
- package/dist/components/limebb-info-tile.js +1 -1
- package/dist/components/limebb-lime-query-builder.js +1 -1
- package/dist/components/limebb-lime-query-response-format-builder.js +1 -1
- package/dist/components/limebb-locale-picker.js +1 -1
- package/dist/components/limebb-mention-group-counter.js +1 -1
- package/dist/components/limebb-text-editor.js +1 -1
- package/dist/components/limebb-trend-indicator.js +1 -1
- package/dist/components/limetype-field.js +1 -1
- package/dist/components/live-docs-info.js +1 -1
- package/dist/components/notification-item.js +1 -1
- package/dist/components/order-by-editor.js +1 -1
- package/dist/components/order-by-item.js +1 -1
- package/dist/components/percentage-visualizer.js +1 -1
- package/dist/components/response-format-item.js +1 -1
- package/dist/components/summary-popover.js +1 -1
- package/dist/components/typing-indicator.js +1 -1
- package/dist/esm/lime-crm-building-blocks.js +1 -1
- package/dist/esm/limebb-alert-dialog.entry.js +1 -1
- package/dist/esm/limebb-browser.entry.js +2 -2
- package/dist/esm/limebb-chat-icon-list_3.entry.js +4 -4
- package/dist/esm/limebb-chat-list.entry.js +337 -69
- package/dist/esm/limebb-composer-toolbar.entry.js +2 -2
- package/dist/esm/limebb-currency-picker.entry.js +1 -1
- package/dist/esm/limebb-dashboard-widget.entry.js +1 -1
- package/dist/esm/limebb-data-cells.entry.js +22 -1
- package/dist/esm/limebb-date-picker.entry.js +1 -1
- package/dist/esm/limebb-date-range.entry.js +1 -1
- package/dist/esm/limebb-document-chips.entry.js +1 -1
- package/dist/esm/limebb-document-item.entry.js +2 -2
- package/dist/esm/limebb-document-picker.entry.js +1 -1
- package/dist/esm/limebb-feed-item-thumbnail-file-info.entry.js +1 -1
- package/dist/esm/limebb-feed-timeline-item.entry.js +1 -1
- package/dist/esm/limebb-feed.entry.js +1 -1
- package/dist/esm/limebb-icon-picker.entry.js +1 -1
- package/dist/esm/limebb-info-tile-format.entry.js +1 -1
- package/dist/esm/limebb-info-tile.entry.js +1 -1
- package/dist/esm/limebb-kanban-group.entry.js +1 -1
- package/dist/esm/limebb-kanban-item.entry.js +1 -1
- package/dist/esm/limebb-lime-query-builder.entry.js +1 -1
- package/dist/esm/limebb-lime-query-filter-builder_3.entry.js +2 -2
- package/dist/esm/limebb-lime-query-filter-comparison_2.entry.js +1 -1
- package/dist/esm/limebb-lime-query-filter-group_3.entry.js +3 -3
- package/dist/esm/limebb-lime-query-order-by-item.entry.js +2 -2
- package/dist/esm/limebb-lime-query-response-format-builder.entry.js +1 -1
- package/dist/esm/limebb-lime-query-response-format-editor_2.entry.js +1 -1
- package/dist/esm/limebb-live-docs-info.entry.js +2 -2
- package/dist/esm/limebb-locale-picker.entry.js +1 -1
- package/dist/esm/limebb-mention-group-counter.entry.js +2 -2
- package/dist/esm/limebb-navigation-button_2.entry.js +2 -2
- package/dist/esm/limebb-notification-item.entry.js +1 -1
- package/dist/esm/limebb-percentage-visualizer.entry.js +2 -2
- package/dist/esm/limebb-text-editor.entry.js +1 -1
- package/dist/esm/limebb-trend-indicator.entry.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/lime-crm-building-blocks/lime-crm-building-blocks.esm.js +1 -1
- package/dist/lime-crm-building-blocks/{p-685dd8a1.entry.js → p-02a3530f.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-52fb2a66.entry.js → p-16ad65dc.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-6cb2d9dd.entry.js → p-2079ae59.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-c3b13800.entry.js → p-2aae332a.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-b3ee6683.entry.js → p-4edfa72a.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-b36ad4e8.entry.js → p-5058bc60.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-8477cb5d.entry.js → p-52fd0169.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-21ec697f.entry.js → p-54ae7814.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-35a51259.entry.js → p-54bb577b.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/p-59b992b3.entry.js +1 -0
- package/dist/lime-crm-building-blocks/{p-1a61674f.entry.js → p-5ef41ad0.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-f375d69a.entry.js → p-640c41a3.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-528f3635.entry.js → p-65485470.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-4ddd75bc.entry.js → p-655968ce.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-8a05a0e5.entry.js → p-67551f9d.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-152edb10.entry.js → p-750dbb18.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-bac3d599.entry.js → p-83e93ebd.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-fa41a9e6.entry.js → p-8a820ede.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/p-8da95b3a.entry.js +1 -0
- package/dist/lime-crm-building-blocks/{p-943c8589.entry.js → p-999c1628.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-62437a1d.entry.js → p-aea85d91.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-9ae55a96.entry.js → p-b17327da.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-71191041.entry.js → p-b3db6a41.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-6fdee70a.entry.js → p-b6895996.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-cff9bccd.entry.js → p-b92eedb5.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-aaeecca6.entry.js → p-c019f7f2.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-fdf269f7.entry.js → p-c3648986.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-ebe6040f.entry.js → p-cc07285b.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-ffb847d0.entry.js → p-cee4e6a6.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-18fc1a06.entry.js → p-d153e06d.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-be8dc8ae.entry.js → p-e33feb37.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-fcedbc77.entry.js → p-e4005988.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-a48a6ff8.entry.js → p-e8a0bbb8.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/{p-eec0a0c8.entry.js → p-ebeddc29.entry.js} +1 -1
- package/dist/lime-crm-building-blocks/p-ece5c8ad.entry.js +1 -0
- package/dist/lime-crm-building-blocks/{p-5a1f0a2b.entry.js → p-fa808d3a.entry.js} +1 -1
- package/dist/types/components/chat-list/chat-list.d.ts +13 -3
- package/dist/types/components/chat-list/process-chat-items.d.ts +20 -4
- package/dist/types/components/chat-list/scroll-anchor.d.ts +46 -0
- package/dist/types/components/chat-list/typing-indicator-scroll.d.ts +18 -0
- package/dist/types/components/data-cells/data-cells.d.ts +1 -0
- package/dist/types/components.d.ts +4 -4
- package/package.json +1 -1
- package/dist/lime-crm-building-blocks/p-5fc88ece.entry.js +0 -1
- package/dist/lime-crm-building-blocks/p-b10faec5.entry.js +0 -1
- package/dist/lime-crm-building-blocks/p-f23ea0e7.entry.js +0 -1
|
@@ -15,7 +15,7 @@ function getProcessedItemTimestamp(item) {
|
|
|
15
15
|
/**
|
|
16
16
|
* Processes a list of chat list inputs by adding date headers and a "new items" indicator.
|
|
17
17
|
*
|
|
18
|
-
* @param items - The list of items to process. Pre-sorted by `timestamp` in
|
|
18
|
+
* @param items - The list of items to process. Pre-sorted by `timestamp` in ascending order.
|
|
19
19
|
* @param dateTimeFormatter - A service used to format dates for the date headers.
|
|
20
20
|
* @param lastVisitedTimestamp - The timestamp of the user's last visit. Used to position the
|
|
21
21
|
* "new items" indicator.
|
|
@@ -29,16 +29,18 @@ function processChatItems(items, dateTimeFormatter, lastVisitedTimestamp) {
|
|
|
29
29
|
processedItems: [],
|
|
30
30
|
lastVisitedTimestamp: lastVisitedTimestamp,
|
|
31
31
|
dateTimeFormatter: dateTimeFormatter,
|
|
32
|
-
newItemIndicatorInserted: false,
|
|
33
|
-
lastProcessedDate: null,
|
|
34
|
-
hasUnseenNotifications: items.some((item) => lastVisitedTimestamp &&
|
|
35
|
-
new Date(item.timestamp) > lastVisitedTimestamp),
|
|
36
32
|
};
|
|
37
|
-
// Add processors here, in the order they should be executed
|
|
33
|
+
// Add processors here, in the order they should be executed.
|
|
34
|
+
// `insertNewItemIndicator` runs after `insertDateGroups` so the
|
|
35
|
+
// indicator can be placed at top level when the seen/unseen
|
|
36
|
+
// boundary falls between two groups, and only inside a group when
|
|
37
|
+
// the boundary falls within a single day. That placement makes
|
|
38
|
+
// `reverseProcessedItems` a pure two-level reverse — no group-
|
|
39
|
+
// boundary hoisting needed.
|
|
38
40
|
const processors = [
|
|
39
41
|
insertNotifications,
|
|
40
|
-
insertNewItemIndicator,
|
|
41
42
|
insertDateGroups,
|
|
43
|
+
insertNewItemIndicator,
|
|
42
44
|
];
|
|
43
45
|
const finalContext = createPipeline(initialContext, processors);
|
|
44
46
|
return finalContext.processedItems;
|
|
@@ -47,6 +49,32 @@ function createPipeline(initialContext, processors) {
|
|
|
47
49
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
48
50
|
return processors.reduce((context, processor) => processor(context), initialContext);
|
|
49
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Reverses the chronological order of a processed-items array.
|
|
54
|
+
*
|
|
55
|
+
* Pure two-level reverse: the top-level array is reversed, and each
|
|
56
|
+
* group's items are reversed. `insertNewItemIndicator` places the
|
|
57
|
+
* indicator at top level when the seen/unseen boundary falls
|
|
58
|
+
* between groups, and only nests it inside a group when the
|
|
59
|
+
* boundary falls within a single day — so reversal never needs to
|
|
60
|
+
* move an indicator across levels to preserve its separator role.
|
|
61
|
+
*
|
|
62
|
+
* Used by `limebb-chat-list` when rendering in `newest-on-top`
|
|
63
|
+
* layout, so that DOM order continues to match visual order
|
|
64
|
+
* (newest-first in that layout), keeping copy/paste, screen-reader,
|
|
65
|
+
* and tab order aligned with what the user sees.
|
|
66
|
+
*
|
|
67
|
+
* @param items - processed items in ascending-by-timestamp order
|
|
68
|
+
* @returns processed items in descending-by-timestamp order
|
|
69
|
+
*/
|
|
70
|
+
function reverseProcessedItems(items) {
|
|
71
|
+
return items.toReversed().map((item) => {
|
|
72
|
+
if (item.type !== 'group') {
|
|
73
|
+
return item;
|
|
74
|
+
}
|
|
75
|
+
return Object.assign(Object.assign({}, item), { items: item.items.toReversed() });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
50
78
|
/**
|
|
51
79
|
*
|
|
52
80
|
* @param context
|
|
@@ -67,61 +95,99 @@ function insertNotifications(context) {
|
|
|
67
95
|
* @param context
|
|
68
96
|
*/
|
|
69
97
|
function insertNewItemIndicator(context) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
if (!context.lastVisitedTimestamp) {
|
|
99
|
+
return context;
|
|
100
|
+
}
|
|
101
|
+
const groups = context.processedItems;
|
|
102
|
+
const lastVisited = context.lastVisitedTimestamp;
|
|
103
|
+
// Walk groups newest → oldest. Items and groups are sorted
|
|
104
|
+
// ascending, so at most one group straddles the seen/unseen
|
|
105
|
+
// boundary; the rest are entirely seen or entirely unseen. In an
|
|
106
|
+
// active chat where the newest item is already seen, the loop
|
|
107
|
+
// exits in O(1).
|
|
108
|
+
for (let g = groups.length - 1; g >= 0; g--) {
|
|
109
|
+
const group = groups[g];
|
|
110
|
+
if (group.type !== 'group') {
|
|
111
|
+
continue;
|
|
81
112
|
}
|
|
82
|
-
|
|
113
|
+
const seenIndex = findNewestSeenIndex(group.items, lastVisited);
|
|
114
|
+
if (seenIndex === -1) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
return placeIndicatorAtBoundary(context, groups, g, group, seenIndex);
|
|
83
118
|
}
|
|
84
|
-
|
|
85
|
-
context.newItemIndicatorInserted = newItemIndicatorInserted;
|
|
119
|
+
// No seen item found — everything is unseen, no boundary to mark.
|
|
86
120
|
return context;
|
|
87
121
|
}
|
|
122
|
+
function findNewestSeenIndex(items, lastVisited) {
|
|
123
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
124
|
+
const timestamp = getProcessedItemTimestamp(items[i]);
|
|
125
|
+
if (timestamp === undefined) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (new Date(timestamp) <= lastVisited) {
|
|
129
|
+
return i;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return -1;
|
|
133
|
+
}
|
|
134
|
+
function placeIndicatorAtBoundary(context, groups, groupIndex, group, seenIndex) {
|
|
135
|
+
const isLastItemInGroup = seenIndex === group.items.length - 1;
|
|
136
|
+
const isLastGroup = groupIndex === groups.length - 1;
|
|
137
|
+
// Newest seen item is the very last item overall → everything is
|
|
138
|
+
// seen, nothing to mark.
|
|
139
|
+
if (isLastItemInGroup && isLastGroup) {
|
|
140
|
+
return context;
|
|
141
|
+
}
|
|
142
|
+
// Seen item is last in its group → boundary is between groups.
|
|
143
|
+
// Place the indicator at top level so reversal stays a pure
|
|
144
|
+
// two-level reverse.
|
|
145
|
+
if (isLastItemInGroup) {
|
|
146
|
+
return Object.assign(Object.assign({}, context), { processedItems: [
|
|
147
|
+
...groups.slice(0, groupIndex + 1),
|
|
148
|
+
{ type: 'newItemIndicator' },
|
|
149
|
+
...groups.slice(groupIndex + 1),
|
|
150
|
+
] });
|
|
151
|
+
}
|
|
152
|
+
// Boundary falls inside a single day — indicator stays nested.
|
|
153
|
+
const newGroupItems = [
|
|
154
|
+
...group.items.slice(0, seenIndex + 1),
|
|
155
|
+
{ type: 'newItemIndicator' },
|
|
156
|
+
...group.items.slice(seenIndex + 1),
|
|
157
|
+
];
|
|
158
|
+
return Object.assign(Object.assign({}, context), { processedItems: [
|
|
159
|
+
...groups.slice(0, groupIndex),
|
|
160
|
+
Object.assign(Object.assign({}, group), { items: newGroupItems }),
|
|
161
|
+
...groups.slice(groupIndex + 1),
|
|
162
|
+
] });
|
|
163
|
+
}
|
|
88
164
|
/**
|
|
89
165
|
*
|
|
90
166
|
* @param context
|
|
91
167
|
*/
|
|
92
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
93
168
|
function insertDateGroups(context) {
|
|
94
169
|
const newProcessedItems = [];
|
|
95
170
|
let currentGroup = null;
|
|
96
171
|
let lastProcessedDate = null;
|
|
97
172
|
for (const item of context.processedItems) {
|
|
98
|
-
if (item.type
|
|
99
|
-
|
|
100
|
-
const currentDate = context.dateTimeFormatter.format(currentTimestamp, 'date');
|
|
101
|
-
if (currentDate !== lastProcessedDate) {
|
|
102
|
-
if (currentGroup) {
|
|
103
|
-
newProcessedItems.push(currentGroup);
|
|
104
|
-
}
|
|
105
|
-
currentGroup = {
|
|
106
|
-
type: 'group',
|
|
107
|
-
groupType: 'dateGroup',
|
|
108
|
-
heading: currentDate,
|
|
109
|
-
items: [],
|
|
110
|
-
};
|
|
111
|
-
lastProcessedDate = currentDate;
|
|
112
|
-
}
|
|
113
|
-
if (currentGroup) {
|
|
114
|
-
currentGroup.items.push(item);
|
|
115
|
-
}
|
|
173
|
+
if (item.type !== 'chat' && item.type !== 'iconList') {
|
|
174
|
+
continue;
|
|
116
175
|
}
|
|
117
|
-
|
|
176
|
+
const currentDate = context.dateTimeFormatter.format(new Date(item.item.timestamp), 'date');
|
|
177
|
+
if (currentDate !== lastProcessedDate) {
|
|
118
178
|
if (currentGroup) {
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
newProcessedItems.push(item);
|
|
179
|
+
newProcessedItems.push(currentGroup);
|
|
123
180
|
}
|
|
181
|
+
currentGroup = {
|
|
182
|
+
type: 'group',
|
|
183
|
+
groupType: 'dateGroup',
|
|
184
|
+
heading: currentDate,
|
|
185
|
+
items: [item],
|
|
186
|
+
};
|
|
187
|
+
lastProcessedDate = currentDate;
|
|
188
|
+
continue;
|
|
124
189
|
}
|
|
190
|
+
currentGroup === null || currentGroup === void 0 ? void 0 : currentGroup.items.push(item);
|
|
125
191
|
}
|
|
126
192
|
if (currentGroup) {
|
|
127
193
|
newProcessedItems.push(currentGroup);
|
|
@@ -129,14 +195,153 @@ function insertDateGroups(context) {
|
|
|
129
195
|
return Object.assign(Object.assign({}, context), { processedItems: newProcessedItems });
|
|
130
196
|
}
|
|
131
197
|
|
|
132
|
-
|
|
198
|
+
// "Near enough the newest edge to count as sticky." 50px is roughly
|
|
199
|
+
// two message-bubble margins; a user who is within that distance
|
|
200
|
+
// clearly wasn't scrolling back through history, so we treat their
|
|
201
|
+
// position as "stuck to newest" and re-pin on each items update.
|
|
202
|
+
const NEAR_NEWEST_THRESHOLD_PX = 50;
|
|
203
|
+
/**
|
|
204
|
+
* Keeps a scroll container stuck to its "newest" edge as items are
|
|
205
|
+
* appended, so long as the user hasn't scrolled away.
|
|
206
|
+
*
|
|
207
|
+
* We use imperative scrolling rather than `flex-direction: column-reverse`
|
|
208
|
+
* so DOM order matches visual order (copy/paste, screen readers, tab
|
|
209
|
+
* order all align with what the user sees). CSS `overflow-anchor: auto`
|
|
210
|
+
* — the default and left enabled here — solves a different problem:
|
|
211
|
+
* preserving scroll position when content is inserted *above* the
|
|
212
|
+
* viewport. It doesn't stick the scroll to the newest edge when items
|
|
213
|
+
* arrive below the viewport, which is what chat typically needs; that
|
|
214
|
+
* requires the imperative approach in this class.
|
|
215
|
+
*
|
|
216
|
+
* Lifecycle:
|
|
217
|
+
* - `beforeRender()` in `componentWillRender` — snapshot whether the
|
|
218
|
+
* user is near the newest edge, using the order the scroll position
|
|
219
|
+
* was last measured against. Capturing here (synchronously, before
|
|
220
|
+
* the new DOM lands) avoids the race a scroll-event listener would
|
|
221
|
+
* have with `componentDidRender`.
|
|
222
|
+
* - `afterRender()` in `componentDidRender` — if the snapshot said
|
|
223
|
+
* "near newest", re-pin to the newest edge (new `scrollHeight`).
|
|
224
|
+
* - `repinIfSticky()` for out-of-band re-pins (e.g. after the typing
|
|
225
|
+
* indicator's height transition finishes — its `scrollHeight`
|
|
226
|
+
* change lands after `componentDidRender`).
|
|
227
|
+
*
|
|
228
|
+
* Scroll-container contract: the passed element must itself be the
|
|
229
|
+
* scrolling ancestor (the element whose `scrollTop` / `scrollHeight`
|
|
230
|
+
* / `clientHeight` reflect the chat list's scroll state). In practice
|
|
231
|
+
* that is the component's host, which sets `overflow: hidden auto` on
|
|
232
|
+
* itself. If future refactors introduce an inner scrolling wrapper,
|
|
233
|
+
* pass that wrapper instead.
|
|
234
|
+
*/
|
|
235
|
+
class ScrollAnchor {
|
|
236
|
+
constructor(scrollContainer, getOrder, initialOrder) {
|
|
237
|
+
this.scrollContainer = scrollContainer;
|
|
238
|
+
this.getOrder = getOrder;
|
|
239
|
+
this.isSticky = true;
|
|
240
|
+
this.paintedOrder = initialOrder;
|
|
241
|
+
}
|
|
242
|
+
beforeRender() {
|
|
243
|
+
if (!this.scrollContainer.isConnected) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Measure against the order the scroll position was last painted
|
|
247
|
+
// with. If `order` just changed, the scroll position still refers
|
|
248
|
+
// to the previous layout until this render commits.
|
|
249
|
+
this.isSticky =
|
|
250
|
+
this.distanceFromNewestEdge(this.paintedOrder) <
|
|
251
|
+
NEAR_NEWEST_THRESHOLD_PX;
|
|
252
|
+
}
|
|
253
|
+
afterRender() {
|
|
254
|
+
if (!this.scrollContainer.isConnected) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
this.paintedOrder = this.getOrder();
|
|
258
|
+
if (this.isSticky) {
|
|
259
|
+
this.scrollToNewest();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
repinIfSticky() {
|
|
263
|
+
if (!this.scrollContainer.isConnected) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (this.isSticky) {
|
|
267
|
+
this.scrollToNewest();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
scrollToNewest() {
|
|
271
|
+
const target = this.getOrder() === 'oldest-on-top'
|
|
272
|
+
? this.scrollContainer.scrollHeight
|
|
273
|
+
: 0;
|
|
274
|
+
// Reading `scrollTop` is a cheap (already-measured) lookup, and
|
|
275
|
+
// short-circuiting avoids forcing a layout when we'd write the
|
|
276
|
+
// same value — important under high-frequency re-renders
|
|
277
|
+
// (streaming AI output).
|
|
278
|
+
if (this.scrollContainer.scrollTop === target) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
this.scrollContainer.scrollTop = target;
|
|
282
|
+
}
|
|
283
|
+
distanceFromNewestEdge(order) {
|
|
284
|
+
return order === 'oldest-on-top'
|
|
285
|
+
? this.scrollContainer.scrollHeight -
|
|
286
|
+
this.scrollContainer.scrollTop -
|
|
287
|
+
this.scrollContainer.clientHeight
|
|
288
|
+
: this.scrollContainer.scrollTop;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// The CSS property the typing-indicator container transitions.
|
|
293
|
+
// Mirrored from chat-list.scss `.typing-indicator-height-animation
|
|
294
|
+
// { transition: grid-template-rows ... }`. The runtime verifier below
|
|
295
|
+
// guards against the SCSS and the handler-side filter silently
|
|
296
|
+
// disagreeing.
|
|
297
|
+
const HEIGHT_ANIMATION_PROPERTY = 'grid-template-rows';
|
|
298
|
+
/**
|
|
299
|
+
* Returns a `transitionend` listener for the typing indicator's
|
|
300
|
+
* height transition. Filters by property name so unrelated
|
|
301
|
+
* transitions on the same element (e.g. opacity) don't trigger the
|
|
302
|
+
* callback.
|
|
303
|
+
* @param onHeightTransitionEnd
|
|
304
|
+
*/
|
|
305
|
+
function createHeightTransitionEndHandler(onHeightTransitionEnd) {
|
|
306
|
+
return (event) => {
|
|
307
|
+
if (event.propertyName !== HEIGHT_ANIMATION_PROPERTY) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
onHeightTransitionEnd();
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Verifies that the passed element actually transitions on the
|
|
315
|
+
* property the height-transition handler filters on. Surfaces SCSS
|
|
316
|
+
* drift loudly instead of silently breaking auto-scroll-on-indicator-
|
|
317
|
+
* appearance.
|
|
318
|
+
* @param element
|
|
319
|
+
*/
|
|
320
|
+
function verifyHeightAnimationProperty(element) {
|
|
321
|
+
if (!element) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const transitionProperty = globalThis.getComputedStyle(element).transitionProperty;
|
|
325
|
+
if (!transitionProperty) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const properties = new Set(transitionProperty.split(',').map((p) => p.trim()));
|
|
329
|
+
if (!properties.has('all') && !properties.has(HEIGHT_ANIMATION_PROPERTY)) {
|
|
330
|
+
console.warn(`[limebb-chat-list] Typing indicator is transitioning \`${transitionProperty}\` instead of \`${HEIGHT_ANIMATION_PROPERTY}\`. The auto-scroll on indicator appearance will not fire. Update the CSS or the handler so they match.`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const chatListCss = () => `@charset "UTF-8";:host(limebb-chat-list){--limel-top-edge-fade-height:0.75rem;isolation:isolate;--limel-overflow-mask-vertical:linear-gradient( to bottom, transparent 0%, black calc(0% + var(--limel-top-edge-fade-height, 1rem)), black calc(100% - var(--limel-bottom-edge-fade-height, 1rem)), transparent 100% );-webkit-mask-image:var(--limel-overflow-mask-vertical);mask-image:var(--limel-overflow-mask-vertical);padding-top:var(--limel-top-edge-fade-height, 1rem);padding-bottom:var(--limel-bottom-edge-fade-height, 1rem);box-sizing:border-box;display:flex;flex-direction:column;gap:0.5rem;height:100%;width:100%;min-width:0;min-height:0;padding:var(--limel-top-edge-fade-height) 0.5rem;overflow:hidden auto}:host(limebb-chat-list.is-empty){padding:0}*{box-sizing:border-box}limel-spinner{align-self:center;justify-self:center;margin:auto}.date-group{isolation:isolate;display:flex;flex-direction:inherit;gap:0.5rem}.date-heading{position:sticky;z-index:1;top:0.25rem;display:flex;border-radius:9rem;padding:0.25rem 0.5rem;margin:0 auto;width:fit-content;font-size:0.75rem;line-height:1;color:rgb(var(--contrast-900));backdrop-filter:blur(0.5rem);-webkit-backdrop-filter:blur(0.5rem);transition:color 0.2s ease, border-color 0.4s ease}.date-heading:hover{color:rgb(var(--contrast-1000));border-color:rgb(var(--contrast-500))}.date-heading:hover:before{opacity:1}.date-heading:before{transition:opacity 0.2s ease;content:"";position:absolute;z-index:-1;inset:0;opacity:0.6;border-radius:inherit;background-color:rgb(var(--contrast-100))}.new-items-indicator{position:relative;isolation:isolate;display:flex;align-items:center;justify-content:flex-end;margin:0.25rem 0}.new-items-indicator hr{border:none;position:absolute;width:100%;height:1px;background-color:rgb(var(--color-red-lighter))}.new-items-indicator h3{z-index:1;padding:0 0.5rem;border-radius:1rem;margin:0 1rem;font-size:small;line-height:0.75rem;text-transform:lowercase;color:rgb(var(--color-red-default));background-color:rgb(var(--contrast-100))}.typing-indicator-height-animation{--limebb-chat-typing-opacity-transition-speed:0.1s;--limebb-chat-typing-opacity-transition-delay:0s;--limebb-chat-typing-grid-template-rows-transition-speed:0.3s;position:sticky;z-index:1;transition:grid-template-rows var(--limebb-chat-typing-grid-template-rows-transition-speed) ease;display:grid;grid-template-rows:0}.typing-indicator-height-animation div{transition:opacity var(--limebb-chat-typing-opacity-transition-speed) ease var(--limebb-chat-typing-opacity-transition-delay);overflow:hidden;opacity:0}:host(limebb-chat-list[order=oldest-on-top]) .typing-indicator-height-animation{bottom:0}:host(limebb-chat-list[order=newest-on-top]) .typing-indicator-height-animation{top:0}:host(limebb-chat-list[is-typing-indicator-visible]) .typing-indicator-height-animation{--limebb-chat-typing-opacity-transition-speed:0.4s;--limebb-chat-typing-opacity-transition-delay:0.3s;--limebb-chat-typing-grid-template-rows-transition-speed:0.46s;grid-template-rows:1.5rem}:host(limebb-chat-list[is-typing-indicator-visible]) .typing-indicator-height-animation div{opacity:1}.date-group limebb-chat-item{--limebb-promoted-action-bar-grid-template-rows:0fr}:host(limebb-chat-list[order=oldest-on-top]) .date-group:last-of-type limebb-chat-item:last-of-type{--limebb-promoted-action-bar-grid-template-rows:1fr}:host(limebb-chat-list[order=newest-on-top]) .date-group:first-of-type limebb-chat-item:first-of-type{--limebb-promoted-action-bar-grid-template-rows:1fr}`;
|
|
133
335
|
|
|
134
336
|
const ChatList = class {
|
|
135
337
|
constructor(hostRef) {
|
|
136
338
|
index.registerInstance(this, hostRef);
|
|
137
339
|
/**
|
|
138
340
|
* List of items to display in the feed.
|
|
139
|
-
* These items must be sorted by their `timestamp` in
|
|
341
|
+
* These items must be sorted by their `timestamp` in ascending order
|
|
342
|
+
* (oldest first, newest last). This matches the natural visual order
|
|
343
|
+
* for the default `oldest-on-top` layout, keeping DOM order aligned
|
|
344
|
+
* with visual order for copy/paste, screen readers, and tab order.
|
|
140
345
|
*
|
|
141
346
|
* See the [ChatListItem](#/type/ChatListItem/) type for the kinds of
|
|
142
347
|
* items that can appear in the list.
|
|
@@ -150,29 +355,56 @@ const ChatList = class {
|
|
|
150
355
|
*
|
|
151
356
|
* :::important
|
|
152
357
|
* Using this prop will _not_ automatically sort the chat items for you!
|
|
153
|
-
* You still need to pass the items in the correct order (
|
|
154
|
-
* This prop
|
|
358
|
+
* You still need to pass the items in the correct order (ascending order by timestamp — oldest first).
|
|
359
|
+
* This prop only affects the direction of the elements in the user interface,
|
|
155
360
|
* such as the location of the typing indicator or the scrollbars.
|
|
156
361
|
* :::
|
|
157
362
|
*/
|
|
158
363
|
this.order = 'oldest-on-top';
|
|
159
364
|
this.processedItems = [];
|
|
365
|
+
this.handleHeightTransitionEnd = createHeightTransitionEndHandler(() => { var _a; return (_a = this.scrollAnchor) === null || _a === void 0 ? void 0 : _a.repinIfSticky(); });
|
|
160
366
|
}
|
|
161
367
|
handleItemsChange() {
|
|
162
368
|
let lastVisited = null;
|
|
163
369
|
if (this.lastVisitedTimestamp) {
|
|
164
370
|
lastVisited = parseToUTCDate(this.lastVisitedTimestamp);
|
|
165
371
|
}
|
|
166
|
-
|
|
372
|
+
const processed = processChatItems(this.items, this.dateTimeFormatter, lastVisited);
|
|
373
|
+
this.processedItems =
|
|
374
|
+
this.order === 'newest-on-top'
|
|
375
|
+
? reverseProcessedItems(processed)
|
|
376
|
+
: processed;
|
|
167
377
|
}
|
|
168
378
|
componentWillLoad() {
|
|
169
379
|
this.handleItemsChange();
|
|
380
|
+
this.scrollAnchor = new ScrollAnchor(this.host, () => this.order, this.order);
|
|
381
|
+
}
|
|
382
|
+
componentDidLoad() {
|
|
383
|
+
var _a;
|
|
384
|
+
verifyHeightAnimationProperty((_a = this.host.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.typing-indicator-height-animation'));
|
|
385
|
+
}
|
|
386
|
+
componentWillRender() {
|
|
387
|
+
if (this.items.length === 0) {
|
|
388
|
+
// Nothing to measure or scroll against. `isSticky` retains
|
|
389
|
+
// its last value (default `true`), so the first non-empty
|
|
390
|
+
// render still pins correctly.
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
this.scrollAnchor.beforeRender();
|
|
394
|
+
}
|
|
395
|
+
componentDidRender() {
|
|
396
|
+
if (this.items.length === 0) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
this.scrollAnchor.afterRender();
|
|
170
400
|
}
|
|
171
401
|
render() {
|
|
172
|
-
return (index.h(index.Host, { key: '
|
|
402
|
+
return (index.h(index.Host, { key: '002369dc1f47978696b4eb8ac7189f794568b0b4', class: {
|
|
173
403
|
'is-empty': this.items.length === 0 &&
|
|
174
404
|
!this.isTypingIndicatorVisible,
|
|
175
|
-
}, "aria-busy": this.isBusy(), role: "log" }, this.renderSpinner(),
|
|
405
|
+
}, "aria-busy": this.isBusy(), role: "log" }, this.renderSpinner(), this.order === 'newest-on-top' &&
|
|
406
|
+
this.renderTypingIndicatorBlock(), this.renderChatItems(), this.order === 'oldest-on-top' &&
|
|
407
|
+
this.renderTypingIndicatorBlock()));
|
|
176
408
|
}
|
|
177
409
|
renderSpinner() {
|
|
178
410
|
if (!this.loading) {
|
|
@@ -180,6 +412,14 @@ const ChatList = class {
|
|
|
180
412
|
}
|
|
181
413
|
return index.h("limel-spinner", null);
|
|
182
414
|
}
|
|
415
|
+
renderTypingIndicatorBlock() {
|
|
416
|
+
// The indicator's grid-template-rows transition grows the
|
|
417
|
+
// container over ~0.46s. `componentDidRender` fires before the
|
|
418
|
+
// transition starts, so `scrollHeight` at that moment doesn't
|
|
419
|
+
// include the final height. `handleHeightTransitionEnd` re-pins
|
|
420
|
+
// once the transition lands.
|
|
421
|
+
return (index.h("div", { class: "typing-indicator-height-animation", onTransitionEnd: this.handleHeightTransitionEnd }, index.h("div", null, this.renderTypingIndicator())));
|
|
422
|
+
}
|
|
183
423
|
renderTypingIndicator() {
|
|
184
424
|
if (!this.isTypingIndicatorVisible) {
|
|
185
425
|
return;
|
|
@@ -195,33 +435,54 @@ const ChatList = class {
|
|
|
195
435
|
case 'newItemIndicator': {
|
|
196
436
|
return this.renderIndicator();
|
|
197
437
|
}
|
|
198
|
-
|
|
438
|
+
case 'chat':
|
|
439
|
+
case 'iconList': {
|
|
440
|
+
// `insertDateGroups` wraps every `chat` / `iconList`
|
|
441
|
+
// in a `group`, so these variants shouldn't appear
|
|
442
|
+
// at top level. If the processor pipeline is ever
|
|
443
|
+
// reordered, render nothing rather than double-
|
|
444
|
+
// render once the wrapping group includes them.
|
|
199
445
|
return null;
|
|
200
446
|
}
|
|
447
|
+
default: {
|
|
448
|
+
return assertNever(item);
|
|
449
|
+
}
|
|
201
450
|
}
|
|
202
451
|
});
|
|
203
452
|
}
|
|
204
453
|
renderGroup(group) {
|
|
205
454
|
const chatItems = group.items.map((item) => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
455
|
+
switch (item.type) {
|
|
456
|
+
case 'chat': {
|
|
457
|
+
return this.renderChatItem(item.item);
|
|
458
|
+
}
|
|
459
|
+
case 'iconList': {
|
|
460
|
+
return this.renderIconList(item.item);
|
|
461
|
+
}
|
|
462
|
+
case 'newItemIndicator': {
|
|
463
|
+
return this.renderIndicator();
|
|
464
|
+
}
|
|
465
|
+
case 'group': {
|
|
466
|
+
// Date groups don't nest. A compile-time `never`
|
|
467
|
+
// here would require a separate nested-item type;
|
|
468
|
+
// this runtime null return is the pragmatic
|
|
469
|
+
// middle ground.
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
default: {
|
|
473
|
+
return assertNever(item);
|
|
474
|
+
}
|
|
214
475
|
}
|
|
215
476
|
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
477
|
+
// Key the section by the group's heading (unique per date, and
|
|
478
|
+
// stable across item additions). Must not vary with group
|
|
479
|
+
// content: if it did, appending a new message would change the
|
|
480
|
+
// key and cause Stencil to remount the whole section, briefly
|
|
481
|
+
// collapsing scrollHeight and scrolling the list to the top
|
|
482
|
+
// before `componentDidRender` scrolls it back.
|
|
483
|
+
const sectionKey = 'date-group-' + group.heading;
|
|
220
484
|
const headingId = `heading-${sectionKey}`.replaceAll(/[^a-zA-Z0-9_-]/g, '_');
|
|
221
485
|
const dateHeading = (index.h("a", { id: headingId, class: "date-heading", role: "heading", "aria-level": 2 }, group.heading));
|
|
222
|
-
if (this.order === 'oldest-on-top') {
|
|
223
|
-
return (index.h("section", { class: "date-group", key: sectionKey, "aria-labelledby": headingId }, chatItems, dateHeading));
|
|
224
|
-
}
|
|
225
486
|
return (index.h("section", { class: "date-group", key: sectionKey, "aria-labelledby": headingId }, dateHeading, chatItems));
|
|
226
487
|
}
|
|
227
488
|
renderChatItem(item) {
|
|
@@ -242,9 +503,13 @@ const ChatList = class {
|
|
|
242
503
|
get translator() {
|
|
243
504
|
return this.platform.get(index_esm.n.Translate);
|
|
244
505
|
}
|
|
506
|
+
get host() { return index.getElement(this); }
|
|
245
507
|
static get watchers() { return {
|
|
246
508
|
"items": [{
|
|
247
509
|
"handleItemsChange": 0
|
|
510
|
+
}],
|
|
511
|
+
"order": [{
|
|
512
|
+
"handleItemsChange": 0
|
|
248
513
|
}]
|
|
249
514
|
}; }
|
|
250
515
|
};
|
|
@@ -254,6 +519,9 @@ function parseToUTCDate(date) {
|
|
|
254
519
|
}
|
|
255
520
|
return new Date(date); // This will automatically be UTC
|
|
256
521
|
}
|
|
522
|
+
function assertNever(value) {
|
|
523
|
+
throw new Error(`Unexpected value in exhaustive switch: ${JSON.stringify(value)}`);
|
|
524
|
+
}
|
|
257
525
|
ChatList.style = chatListCss();
|
|
258
526
|
|
|
259
527
|
exports.limebb_chat_list = ChatList;
|
|
@@ -66,8 +66,8 @@ const ComposerToolbar = class {
|
|
|
66
66
|
}
|
|
67
67
|
render() {
|
|
68
68
|
return [
|
|
69
|
-
index.h("div", { key: '
|
|
70
|
-
index.h("slot", { key: '
|
|
69
|
+
index.h("div", { key: '6034b2945edfd9b613f606f8bb4fb0db81c1390f', class: "actions" }, index.h("slot", { key: '82b9bc14d80aedd3320af70bc63d3a908db6a7b2', name: "leading" }), this.renderFileInput(), this.renderTriggerActions(), index.h("slot", { key: '338fc1b7c7ec3f7dfedb123b7e09dd05f75fc45c', name: "trailing" })),
|
|
70
|
+
index.h("slot", { key: '57b277b772cbaf702e619ecbd6ff9032718b87b7', name: "primary-action" }),
|
|
71
71
|
];
|
|
72
72
|
}
|
|
73
73
|
get translator() {
|
|
@@ -1970,7 +1970,7 @@ const CurrencyPicker = class {
|
|
|
1970
1970
|
};
|
|
1971
1971
|
}
|
|
1972
1972
|
render() {
|
|
1973
|
-
return (index.h("limel-picker", { key: '
|
|
1973
|
+
return (index.h("limel-picker", { key: '12d166b930a2864eefbb16c4d66706dd02b54112', label: this.label || this.defaultLabel, value: this.createListItem(this.value), helperText: this.helperText, onChange: this.handleChange, badgeIcons: false, disabled: this.disabled, required: this.required, readonly: this.readonly, invalid: this.readonly, searcher: this.search }));
|
|
1974
1974
|
}
|
|
1975
1975
|
getCurrencyName(code) {
|
|
1976
1976
|
var _a;
|
|
@@ -18,7 +18,7 @@ const DashboardComponent = class {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
render() {
|
|
21
|
-
return [this.renderHeader(), index.h("slot", { key: '
|
|
21
|
+
return [this.renderHeader(), index.h("slot", { key: 'f5202c8c5d6ceb3e01420c6045ecb5589fed915a', name: "content" })];
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
DashboardComponent.style = dashboardWidgetCss();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var index = require('./index-C_OnxXhP.js');
|
|
4
4
|
var index_esm = require('./index.esm-DhBVH875.js');
|
|
5
|
+
var randomString = require('./random-string-BdZGIsGg.js');
|
|
5
6
|
|
|
6
7
|
const SECOND = 1000;
|
|
7
8
|
const MINUTE = 60 * SECOND;
|
|
@@ -100,7 +101,11 @@ const DataCells = class {
|
|
|
100
101
|
if (Number.isNaN(date.getTime())) {
|
|
101
102
|
return item.value;
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
+
const id = randomString.createRandomString();
|
|
105
|
+
return [
|
|
106
|
+
index.h("span", { id: id }, formatRelativeDate(date, this.language)),
|
|
107
|
+
index.h("limel-tooltip", { elementId: id, label: this.formatAbsoluteDate(date, item.type) }),
|
|
108
|
+
];
|
|
104
109
|
}
|
|
105
110
|
if (item.type === 'percent') {
|
|
106
111
|
return (index.h("limebb-percentage-visualizer", { value: Number(item.value), multiplier: 100, displayPercentageColors: true, reducePresence: false }));
|
|
@@ -148,6 +153,22 @@ const DataCells = class {
|
|
|
148
153
|
return navigator.language;
|
|
149
154
|
}
|
|
150
155
|
}
|
|
156
|
+
formatAbsoluteDate(date, type) {
|
|
157
|
+
var _a;
|
|
158
|
+
try {
|
|
159
|
+
const formatter = (_a = this.platform) === null || _a === void 0 ? void 0 : _a.get(index_esm.n.DateTimeFormatter);
|
|
160
|
+
if (formatter) {
|
|
161
|
+
return formatter.format(date, type);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (_b) {
|
|
165
|
+
// Fall through to default formatting.
|
|
166
|
+
}
|
|
167
|
+
return date.toLocaleString(this.language, {
|
|
168
|
+
dateStyle: 'long',
|
|
169
|
+
timeStyle: type === 'time' ? 'short' : undefined,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
151
172
|
};
|
|
152
173
|
DataCells.style = dataCellsCss();
|
|
153
174
|
|
|
@@ -75,7 +75,7 @@ const DatePicker = class {
|
|
|
75
75
|
if (this.value) {
|
|
76
76
|
this.shouldEmitValueAsString = typeof this.value === 'string';
|
|
77
77
|
}
|
|
78
|
-
return (index.h("limel-date-picker", { key: '
|
|
78
|
+
return (index.h("limel-date-picker", { key: 'f3276cfc57128c566248a97e5fea9a3dcc107888', disabled: this.disabled, readonly: this.readonly, invalid: this.invalid, label: this.label, placeholder: this.placeholder, helperText: this.helperText, required: this.required, value: this.parseDateValue(), type: this.type, language: this.language, formatter: this.formatter, onChange: this.handleChange }));
|
|
79
79
|
}
|
|
80
80
|
parseDateValue() {
|
|
81
81
|
const value = this.value;
|
|
@@ -23,7 +23,7 @@ const DateRange = class {
|
|
|
23
23
|
this.originalEndTime = this.endTime;
|
|
24
24
|
}
|
|
25
25
|
render() {
|
|
26
|
-
return (index.h("div", { key: '
|
|
26
|
+
return (index.h("div", { key: 'e00abfd31fcae2a4279ae2194696b5cffb76dd79', class: "date-pickers" }, this.renderStartDatePicker(), this.renderEndDatePicker()));
|
|
27
27
|
}
|
|
28
28
|
renderStartDatePicker() {
|
|
29
29
|
if (!this.originalStartTime) {
|
|
@@ -347,7 +347,7 @@ const DocumentChips = class {
|
|
|
347
347
|
this.revokeAllBlobUrls();
|
|
348
348
|
}
|
|
349
349
|
render() {
|
|
350
|
-
return (index.h(index.Host, { key: '
|
|
350
|
+
return (index.h(index.Host, { key: '93d00770917014b1de6e380db6b102859210fded', role: "list", "aria-label": this.accessibleLabel }, this.files.map((file) => this.renderFile(file))));
|
|
351
351
|
}
|
|
352
352
|
renderFile(file) {
|
|
353
353
|
const isReady = !file.uploadState || file.uploadState === 'done';
|