@limetech/lime-crm-building-blocks 1.109.2 → 1.110.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 (44) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/lime-crm-building-blocks.cjs.js +1 -1
  3. package/dist/cjs/limebb-data-cells.cjs.entry.js +137 -0
  4. package/dist/cjs/limebb-feed-timeline-item.cjs.entry.js +21 -4
  5. package/dist/cjs/limebb-kanban-group.cjs.entry.js +1 -1
  6. package/dist/cjs/limebb-kanban-item.cjs.entry.js +6 -3
  7. package/dist/cjs/loader.cjs.js +1 -1
  8. package/dist/collection/collection-manifest.json +1 -0
  9. package/dist/collection/components/data-cells/data-cells.css +210 -0
  10. package/dist/collection/components/data-cells/data-cells.js +217 -0
  11. package/dist/collection/components/data-cells/data-cells.types.js +1 -0
  12. package/dist/collection/components/feed/feed-item/feed-timeline-item.js +21 -4
  13. package/dist/collection/components/kanban/kanban-group/kanban-group.css +6 -3
  14. package/dist/collection/components/kanban/kanban-item/kanban-item.css +2 -3
  15. package/dist/collection/components/kanban/kanban-item/kanban-item.js +5 -2
  16. package/dist/collection/components/kanban/kanban.js +1 -1
  17. package/dist/collection/util/format-relative-date.js +30 -0
  18. package/dist/components/feed-timeline-item.js +21 -4
  19. package/dist/components/kanban-group.js +1 -1
  20. package/dist/components/kanban-item.js +6 -3
  21. package/dist/components/limebb-data-cells.d.ts +11 -0
  22. package/dist/components/limebb-data-cells.js +162 -0
  23. package/dist/components/limebb-percentage-visualizer.js +1 -217
  24. package/dist/components/percentage-visualizer.js +219 -0
  25. package/dist/esm/lime-crm-building-blocks.js +1 -1
  26. package/dist/esm/limebb-data-cells.entry.js +133 -0
  27. package/dist/esm/limebb-feed-timeline-item.entry.js +21 -4
  28. package/dist/esm/limebb-kanban-group.entry.js +1 -1
  29. package/dist/esm/limebb-kanban-item.entry.js +6 -3
  30. package/dist/esm/loader.js +1 -1
  31. package/dist/lime-crm-building-blocks/lime-crm-building-blocks.esm.js +1 -1
  32. package/dist/lime-crm-building-blocks/p-37982f06.entry.js +1 -0
  33. package/dist/lime-crm-building-blocks/p-4584e9c8.entry.js +1 -0
  34. package/dist/lime-crm-building-blocks/{p-97678875.entry.js → p-b38bc613.entry.js} +1 -1
  35. package/dist/lime-crm-building-blocks/p-b47a03cd.entry.js +1 -0
  36. package/dist/types/components/data-cells/data-cells.d.ts +43 -0
  37. package/dist/types/components/data-cells/data-cells.types.d.ts +40 -0
  38. package/dist/types/components/feed/feed-item/feed-timeline-item.d.ts +1 -0
  39. package/dist/types/components/kanban/kanban.d.ts +1 -1
  40. package/dist/types/components.d.ts +87 -4
  41. package/dist/types/util/format-relative-date.d.ts +10 -0
  42. package/package.json +3 -3
  43. package/dist/lime-crm-building-blocks/p-52fb8909.entry.js +0 -1
  44. package/dist/lime-crm-building-blocks/p-a8c47132.entry.js +0 -1
@@ -0,0 +1,210 @@
1
+ @charset "UTF-8";
2
+ /**
3
+ * Note! This file is exported to `dist/scss/` in the published
4
+ * node module, for consumer projects to import.
5
+ * That means this file cannot import from any file that isn't
6
+ * also exported, keeping the same relative path.
7
+ *
8
+ * Or, just don't import anything, that works too.
9
+ */
10
+ /**
11
+ * This can be used on a trigger element that opens a dropdown menu or a popover.
12
+ */
13
+ /**
14
+ * This mixin will mask out the content that is close to
15
+ * the edges of a scrollable area.
16
+ * - If the scrollable content has `overflow-y`, use `vertically`
17
+ * as an argument for `$direction`.
18
+ - If the scrollable content has `overflow-x`, use `horizontally`
19
+ * as an argument for `$direction`.
20
+ *
21
+ * For the visual effect to work smoothly, we need to make sure that
22
+ * the size of the fade-out edge effect is the same as the
23
+ * internal paddings of the scrollable area. Otherwise, content of a
24
+ * scrollable area that does not have a padding will fade out before
25
+ * any scrolling has been done.
26
+ * This is why this mixin already adds paddings, which automatically
27
+ * default to the size of the fade-out effect.
28
+ * This size defaults to `1rem`, but to override the size use
29
+ * `--limel-top-edge-fade-height` & `--limel-bottom-edge-fade-height`
30
+ * when `vertically` argument is set, and use
31
+ * `--limel-left-edge-fade-width` & `--limel-right-edge-fade-width`
32
+ * when `horizontally` argument is set.
33
+ * Of course you can also programmatically increase and decrease the
34
+ * size of these variables for each edge, based on the amount of
35
+ * scrolling that has been done by the user. In this case, make sure
36
+ * to add a custom padding where the mixin is used, to override
37
+ * the paddings that are automatically added by the mixin in the
38
+ * compiled CSS code.
39
+ */
40
+ /**
41
+ * This mixin will add an animated underline to the bottom of an `a` elements.
42
+ * Note that you may need to add `all: unset;` –depending on your use case–
43
+ * before using this mixin.
44
+ */
45
+ /**
46
+ * This mixin creates a cross-browser font stack.
47
+ * - `sans-serif` can be used for the UI of the components.
48
+ * - `monospace` can be used for code.
49
+ *
50
+ * ⚠️ If we change the font stacks, we need to update
51
+ * 1. the consumer documentation in `README.md`, and
52
+ * 2. the CSS variables of `--kompendium-example-font-family`
53
+ * in the `<style>` tag of `index.html`.
54
+ */
55
+ /**
56
+ * This mixin is a hack, using old CSS syntax
57
+ * to enable you to truncate a piece of text,
58
+ * after a certain number of lines.
59
+ */
60
+ /**
61
+ * This mixin will add a chessboard background pattern,
62
+ * typically used to visualize transparency.
63
+ */
64
+ /**
65
+ * Make a container resizable by the user.
66
+ * This is used in the documentations and examples
67
+ * of some components, to demonstrate how the component
68
+ * behaves in a resizable container.
69
+ */
70
+ /**
71
+ * Drag to reorder mixins
72
+ */
73
+ /**
74
+ * The breakpoints below are used to create responsive designs
75
+ * in Lime's products. Therefore, they are here to get distributed
76
+ * to all components in other private repos, which rely on this `mixins`
77
+ * file, to create consistent styles.
78
+ *
79
+ * :::important
80
+ * In very rare cases you should used media queries!
81
+ * Nowadays, there are many better ways of achieving responsive design
82
+ * without media queries. For example, using CSS Grid, Flexbox, and their features.
83
+ * :::
84
+ */
85
+ /**
86
+ * Media query mixins for responsive design based on screen width.
87
+ * Note that these mixins do not detect the device type!
88
+ */
89
+ *,
90
+ *:before,
91
+ *:after {
92
+ box-sizing: border-box;
93
+ min-width: 0;
94
+ min-height: 0;
95
+ }
96
+
97
+ :host(limebb-data-cells) {
98
+ box-sizing: border-box;
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 0.5rem;
102
+ padding: var(--limebb-data-cells-padding);
103
+ }
104
+
105
+ div {
106
+ display: flex;
107
+ flex-direction: column;
108
+ }
109
+
110
+ dl {
111
+ display: flex;
112
+ flex-wrap: wrap;
113
+ gap: 0.75rem 1rem;
114
+ margin: 0;
115
+ padding: 0;
116
+ list-style: none;
117
+ }
118
+
119
+ dd,
120
+ dt {
121
+ display: flex;
122
+ align-items: center;
123
+ }
124
+ dd span,
125
+ dt span {
126
+ overflow: hidden;
127
+ white-space: nowrap;
128
+ text-overflow: ellipsis;
129
+ }
130
+
131
+ dd {
132
+ margin: 0;
133
+ font-size: var(--limel-theme-default-font-size);
134
+ line-height: 1.25rem;
135
+ height: 1.25rem;
136
+ font-weight: 500;
137
+ color: rgb(var(--contrast-1100));
138
+ }
139
+ dd a {
140
+ text-decoration: none;
141
+ border-radius: 1.25rem;
142
+ }
143
+ dd a:focus {
144
+ outline: none;
145
+ }
146
+ dd a:focus-visible {
147
+ outline: none;
148
+ box-shadow: var(--shadow-depth-8-focused);
149
+ }
150
+ dd a:not(.belongs-to) {
151
+ position: relative;
152
+ cursor: pointer;
153
+ transition: color 0.2s ease;
154
+ color: rgb(var(--color-blue-default));
155
+ }
156
+ dd a:not(.belongs-to):before {
157
+ transition: opacity 0.2s ease, transform 0.3s ease-out;
158
+ content: "";
159
+ position: absolute;
160
+ inset: auto 0 0 0;
161
+ width: calc(100% - 0.5rem);
162
+ margin: auto;
163
+ height: 0.125rem;
164
+ border-radius: 1rem;
165
+ background-color: currentColor;
166
+ opacity: 0;
167
+ transform: scale(0.6);
168
+ }
169
+ dd a:not(.belongs-to):hover {
170
+ color: rgb(var(--color-blue-default));
171
+ }
172
+ dd a:not(.belongs-to):hover:before {
173
+ opacity: 0.3;
174
+ transform: scale(1);
175
+ }
176
+ dd a.belongs-to {
177
+ transition: color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);
178
+ color: inherit;
179
+ background-color: rgb(var(--contrast-400));
180
+ padding: 1px 0.375rem;
181
+ }
182
+ dd a.belongs-to:hover, dd a.belongs-to:focus-visible {
183
+ color: rgb(var(--color-white));
184
+ background-color: rgb(var(--color-sky-default));
185
+ }
186
+ dd:has(.belongs-to) {
187
+ transform: translateX(-0.375rem);
188
+ }
189
+
190
+ dt {
191
+ order: 1;
192
+ transition: color 0.2s ease;
193
+ font-size: 0.65rem;
194
+ color: rgb(var(--contrast-800));
195
+ }
196
+ :host(:hover) dt, :host(:focus-within) dt {
197
+ color: rgb(var(--contrast-900));
198
+ }
199
+
200
+ .primary + .secondary {
201
+ padding-top: 0.5rem;
202
+ border-top: 1px dashed rgb(var(--contrast-500));
203
+ }
204
+
205
+ .secondary dd {
206
+ font-size: 0.75rem;
207
+ font-weight: 400;
208
+ line-height: 1rem;
209
+ height: 1rem;
210
+ }
@@ -0,0 +1,217 @@
1
+ import { h } from "@stencil/core";
2
+ import { PlatformServiceName, } from "@limetech/lime-web-components";
3
+ import { formatRelativeDate } from "../../util/format-relative-date";
4
+ /**
5
+ * Renders a list of key-value data cells in two visual groups:
6
+ * primary (prominent) and secondary (less dominant).
7
+ *
8
+ * @exampleComponent limebb-example-data-cells-basic
9
+ * @exampleComponent limebb-example-data-cells-primary-secondary
10
+ * @exampleComponent limebb-example-data-cells-typed-values
11
+ * @exampleComponent limebb-example-data-cells-relative-dates
12
+ *
13
+ * @beta
14
+ */
15
+ export class DataCells {
16
+ constructor() {
17
+ /**
18
+ * The data cells to render.
19
+ */
20
+ this.items = [];
21
+ this.renderCell = (item) => (h("div", null, h("dt", null, h("span", null, item.label)), h("dd", null, h("span", null, this.renderValue(item)))));
22
+ }
23
+ render() {
24
+ return [this.renderPrimary(), this.renderSecondary()];
25
+ }
26
+ renderPrimary() {
27
+ const items = this.items.filter((item) => item.value != null && item.value !== '' && !item.secondary);
28
+ if (items.length === 0) {
29
+ return;
30
+ }
31
+ return h("dl", { class: "primary" }, items.map(this.renderCell));
32
+ }
33
+ renderSecondary() {
34
+ const items = this.items.filter((item) => item.value != null && item.value !== '' && item.secondary);
35
+ if (items.length === 0) {
36
+ return;
37
+ }
38
+ return h("dl", { class: "secondary" }, items.map(this.renderCell));
39
+ }
40
+ renderValue(item) {
41
+ if (item.type === 'belongsto') {
42
+ const href = this.getBelongsToHref(item.field);
43
+ if (!href) {
44
+ return item.value;
45
+ }
46
+ return (h("a", { class: "belongs-to", href: href }, item.value));
47
+ }
48
+ if (item.type === 'phone') {
49
+ return h("a", { href: this.toTelUri(item.value) }, item.value);
50
+ }
51
+ if (item.type === 'email') {
52
+ return h("a", { href: `mailto:${item.value}` }, item.value);
53
+ }
54
+ if (item.type === 'link') {
55
+ return (h("a", { href: this.ensureUrlProtocol(item.value), target: "_blank", rel: "noopener" }, this.stripUrlPrefix(item.value)));
56
+ }
57
+ if (item.type === 'time' || item.type === 'date') {
58
+ return formatRelativeDate(new Date(item.value), this.language);
59
+ }
60
+ if (item.type === 'percent') {
61
+ return (h("limebb-percentage-visualizer", { value: Number(item.value), multiplier: 100, displayPercentageColors: true, reducePresence: false }));
62
+ }
63
+ return item.value;
64
+ }
65
+ toTelUri(phoneNumber) {
66
+ return `tel:${phoneNumber.replaceAll(' ', '')}`;
67
+ }
68
+ ensureUrlProtocol(url) {
69
+ if (/^https?:\/\//i.test(url)) {
70
+ return url;
71
+ }
72
+ return `https://${url}`;
73
+ }
74
+ stripUrlPrefix(url) {
75
+ return url.replace(/^https?:\/\//, '').replace(/^www\./, '');
76
+ }
77
+ getBelongsToHref(field) {
78
+ var _a;
79
+ if (!this.limeobject || !field) {
80
+ return undefined;
81
+ }
82
+ try {
83
+ const relatedObject = this.limeobject.getValue(field);
84
+ if (!relatedObject || typeof relatedObject !== 'object') {
85
+ return undefined;
86
+ }
87
+ const limetype = (_a = relatedObject.getLimetype) === null || _a === void 0 ? void 0 : _a.call(relatedObject);
88
+ if (!(limetype === null || limetype === void 0 ? void 0 : limetype.name) || !relatedObject.id) {
89
+ return undefined;
90
+ }
91
+ return `object/${limetype.name}/${relatedObject.id}`;
92
+ }
93
+ catch (_b) {
94
+ return undefined;
95
+ }
96
+ }
97
+ get language() {
98
+ var _a;
99
+ try {
100
+ return (_a = this.platform) === null || _a === void 0 ? void 0 : _a.get(PlatformServiceName.Application).getLanguage();
101
+ }
102
+ catch (_b) {
103
+ return navigator.language;
104
+ }
105
+ }
106
+ static get is() { return "limebb-data-cells"; }
107
+ static get encapsulation() { return "shadow"; }
108
+ static get originalStyleUrls() {
109
+ return {
110
+ "$": ["data-cells.scss"]
111
+ };
112
+ }
113
+ static get styleUrls() {
114
+ return {
115
+ "$": ["data-cells.css"]
116
+ };
117
+ }
118
+ static get properties() {
119
+ return {
120
+ "platform": {
121
+ "type": "unknown",
122
+ "mutable": false,
123
+ "complexType": {
124
+ "original": "LimeWebComponentPlatform",
125
+ "resolved": "LimeWebComponentPlatform",
126
+ "references": {
127
+ "LimeWebComponentPlatform": {
128
+ "location": "import",
129
+ "path": "@limetech/lime-web-components",
130
+ "id": "node_modules::LimeWebComponentPlatform"
131
+ }
132
+ }
133
+ },
134
+ "required": false,
135
+ "optional": false,
136
+ "docs": {
137
+ "tags": [{
138
+ "name": "inheritdoc",
139
+ "text": undefined
140
+ }],
141
+ "text": ""
142
+ },
143
+ "getter": false,
144
+ "setter": false
145
+ },
146
+ "context": {
147
+ "type": "unknown",
148
+ "mutable": false,
149
+ "complexType": {
150
+ "original": "LimeWebComponentContext",
151
+ "resolved": "LimeWebComponentContext",
152
+ "references": {
153
+ "LimeWebComponentContext": {
154
+ "location": "import",
155
+ "path": "@limetech/lime-web-components",
156
+ "id": "node_modules::LimeWebComponentContext"
157
+ }
158
+ }
159
+ },
160
+ "required": false,
161
+ "optional": false,
162
+ "docs": {
163
+ "tags": [{
164
+ "name": "inheritdoc",
165
+ "text": undefined
166
+ }],
167
+ "text": ""
168
+ },
169
+ "getter": false,
170
+ "setter": false
171
+ },
172
+ "limeobject": {
173
+ "type": "any",
174
+ "mutable": false,
175
+ "complexType": {
176
+ "original": "any",
177
+ "resolved": "any",
178
+ "references": {}
179
+ },
180
+ "required": false,
181
+ "optional": false,
182
+ "docs": {
183
+ "tags": [],
184
+ "text": "The source LimeObject. Used by interactive cell types\nlike `belongsto` to resolve related objects."
185
+ },
186
+ "getter": false,
187
+ "setter": false,
188
+ "attribute": "limeobject",
189
+ "reflect": false
190
+ },
191
+ "items": {
192
+ "type": "unknown",
193
+ "mutable": false,
194
+ "complexType": {
195
+ "original": "DataCell[]",
196
+ "resolved": "DataCell[]",
197
+ "references": {
198
+ "DataCell": {
199
+ "location": "import",
200
+ "path": "./data-cells.types",
201
+ "id": "src/components/data-cells/data-cells.types.ts::DataCell"
202
+ }
203
+ }
204
+ },
205
+ "required": false,
206
+ "optional": false,
207
+ "docs": {
208
+ "tags": [],
209
+ "text": "The data cells to render."
210
+ },
211
+ "getter": false,
212
+ "setter": false,
213
+ "defaultValue": "[]"
214
+ }
215
+ };
216
+ }
217
+ }
@@ -50,12 +50,29 @@ export class FeedTimelineItem {
50
50
  }
51
51
  };
52
52
  this.bodyTextTall = (entries) => {
53
- if (!(entries === null || entries === void 0 ? void 0 : entries.length)) {
53
+ if (!this.bodyTextElement || !(entries === null || entries === void 0 ? void 0 : entries.length)) {
54
54
  return;
55
55
  }
56
- const height = entries[0].contentRect.height;
56
+ const width = entries[0].contentRect.width;
57
+ // Only re-evaluate when the container width changes.
58
+ // Height-only changes are caused by our own CSS (the collapse/expand
59
+ // animation via `grid-template-rows`), and reacting to them would
60
+ // either cause an infinite loop or disrupt the transition.
61
+ if (width === this.lastMeasuredWidth) {
62
+ return;
63
+ }
64
+ this.lastMeasuredWidth = width;
65
+ // Temporarily remove `min-height` to measure the natural content height.
66
+ // The `.is-tall` CSS sets a `min-height` (~164px) on this element,
67
+ // which inflates `scrollHeight` and would prevent `isTall` from ever
68
+ // becoming `false` again. We use `scrollHeight` instead of
69
+ // `contentRect.height`, because the latter reflects the
70
+ // CSS-constrained size (e.g. `grid-template-rows: 0fr`).
71
+ this.bodyTextElement.style.minHeight = '0';
72
+ const height = this.bodyTextElement.scrollHeight;
73
+ this.bodyTextElement.style.removeProperty('min-height');
57
74
  const maxHeight = this.isThreadMessage ? 48 : 160;
58
- this.isTall = height > maxHeight ? true : false;
75
+ this.isTall = height > maxHeight;
59
76
  if (this.bodyTextHeight === undefined) {
60
77
  this.bodyTextHeight = height;
61
78
  }
@@ -188,7 +205,7 @@ export class FeedTimelineItem {
188
205
  render() {
189
206
  var _a, _b;
190
207
  const helperTextId = `helper-text-${this.item.id}`;
191
- return (h(Host, { key: 'ae530cfe3aa5c54f8c1d3ceae6630b606f5ca57f', id: this.item.id, class: {
208
+ return (h(Host, { key: '032d6c6319e54b88fb00496c3427554d6b5ee8ea', id: this.item.id, class: {
192
209
  'has-unpromoted-actions': !!((_a = this.item.unpromotedActions) === null || _a === void 0 ? void 0 : _a.length),
193
210
  'has-author-picture': !!((_b = this.item.author) === null || _b === void 0 ? void 0 : _b.picture),
194
211
  'shows-less': !this.showMore,
@@ -191,6 +191,9 @@
191
191
  width: 0.75rem;
192
192
  }
193
193
 
194
+ /**
195
+ * @prop --limebb-kanban-group-max-width: Maximum width of the kanban group. Defaults to `22rem`.
196
+ */
194
197
  * {
195
198
  box-sizing: border-box;
196
199
  }
@@ -222,7 +225,7 @@
222
225
  limel-header {
223
226
  padding-right: 0.5rem;
224
227
  background-color: rgb(var(--limebb-kanban-group-primary-background));
225
- width: 22rem;
228
+ width: var(--limebb-kanban-group-max-width, 22rem);
226
229
  }
227
230
  limel-header:has(+ .items:empty) {
228
231
  width: 10rem;
@@ -244,7 +247,7 @@ limel-help {
244
247
  padding: 0 0.75rem 0.5rem 0.75rem;
245
248
  opacity: 0.6;
246
249
  max-height: 7rem;
247
- max-width: 22rem;
250
+ max-width: var(--limebb-kanban-group-max-width, 22rem);
248
251
  overflow-y: auto;
249
252
  }
250
253
 
@@ -280,7 +283,7 @@ limel-spinner {
280
283
  -webkit-overflow-scrolling: touch;
281
284
  }
282
285
  .items:not(:empty) {
283
- width: 22rem;
286
+ width: var(--limebb-kanban-group-max-width, 22rem);
284
287
  }
285
288
 
286
289
  limebb-kanban-item {
@@ -115,7 +115,6 @@
115
115
 
116
116
  .content {
117
117
  padding-top: 0.25rem;
118
- min-height: 3rem;
119
118
  }
120
119
 
121
120
  .header {
@@ -166,7 +165,7 @@
166
165
 
167
166
  .body {
168
167
  --limel-top-edge-fade-height: 0.5rem;
169
- --limel-bottom-edge-fade-height: 1rem;
168
+ --limel-bottom-edge-fade-height: 0.5rem;
170
169
  display: block;
171
170
  padding-left: 0.5rem;
172
171
  padding-right: 0.5rem;
@@ -342,5 +341,5 @@ limel-chip-set {
342
341
 
343
342
  slot[name=body] {
344
343
  display: block;
345
- padding: 0 0.5rem 0.5rem 0.5rem;
344
+ padding: 0.5rem;
346
345
  }
@@ -60,7 +60,7 @@ export class KanbanItemComponent {
60
60
  'is-selected': !!((_b = this.item) === null || _b === void 0 ? void 0 : _b.selected),
61
61
  }, style: {
62
62
  '--limebb-kanban-item-color-code': `${this.item.color}`,
63
- }, onClick: this.handleKanbanItemClick, onKeyDown: this.handleKeyDown }, this.renderHeader(), this.renderContent(), this.renderSlot(), this.renderFooter()));
63
+ }, onClick: this.handleKanbanItemClick, onKeyDown: this.handleKeyDown }, this.renderHeader(), this.renderContent(), this.renderSlot(), this.renderRelations(), this.renderFooter()));
64
64
  }
65
65
  renderHeader() {
66
66
  return (h("div", { class: "header" }, this.renderIcon(), this.renderHeading(), this.renderUnpromotedActionsMenu()));
@@ -76,7 +76,10 @@ export class KanbanItemComponent {
76
76
  } }));
77
77
  }
78
78
  renderContent() {
79
- return (h("div", { class: "content" }, this.renderValue(), this.renderRelations()));
79
+ if (!this.item.value) {
80
+ return;
81
+ }
82
+ return h("div", { class: "content" }, this.renderValue());
80
83
  }
81
84
  renderHeading() {
82
85
  if (!this.item.heading) {
@@ -13,7 +13,7 @@ import { h } from "@stencil/core";
13
13
  * @exampleComponent limebb-example-kanban-assignees
14
14
  * @exampleComponent limebb-example-kanban-unpromoted-actions
15
15
  * @exampleComponent limebb-example-kanban-group-insights
16
- * @exampleComponent limebb-example-kanban-primary-component
16
+ * @exampleComponent limebb-example-kanban-body-component
17
17
  *
18
18
  * @beta
19
19
  */
@@ -0,0 +1,30 @@
1
+ const SECOND = 1000;
2
+ const MINUTE = 60 * SECOND;
3
+ const HOUR = 60 * MINUTE;
4
+ const DAY = 24 * HOUR;
5
+ const WEEK = 7 * DAY;
6
+ const MONTH = 30 * DAY;
7
+ const YEAR = 365 * DAY;
8
+ const units = [
9
+ { unit: 'year', ms: YEAR },
10
+ { unit: 'month', ms: MONTH },
11
+ { unit: 'week', ms: WEEK },
12
+ { unit: 'day', ms: DAY },
13
+ { unit: 'hour', ms: HOUR },
14
+ { unit: 'minute', ms: MINUTE },
15
+ { unit: 'second', ms: SECOND },
16
+ ];
17
+ /**
18
+ * Formats a date as a localized relative time expression,
19
+ * e.g. "3 days ago" or "in 2 months".
20
+ *
21
+ * Uses the browser's native `Intl.RelativeTimeFormat` API.
22
+ * @param date
23
+ * @param language
24
+ */
25
+ export function formatRelativeDate(date, language) {
26
+ var _a;
27
+ const diff = date.getTime() - Date.now();
28
+ const { unit, ms } = (_a = units.find((u) => Math.abs(diff) >= u.ms)) !== null && _a !== void 0 ? _a : units.at(-1);
29
+ return new Intl.RelativeTimeFormat(language, { style: 'long' }).format(Math.round(diff / ms), unit);
30
+ }
@@ -55,12 +55,29 @@ const FeedTimelineItem = /*@__PURE__*/ proxyCustomElement(class FeedTimelineItem
55
55
  }
56
56
  };
57
57
  this.bodyTextTall = (entries) => {
58
- if (!(entries === null || entries === void 0 ? void 0 : entries.length)) {
58
+ if (!this.bodyTextElement || !(entries === null || entries === void 0 ? void 0 : entries.length)) {
59
59
  return;
60
60
  }
61
- const height = entries[0].contentRect.height;
61
+ const width = entries[0].contentRect.width;
62
+ // Only re-evaluate when the container width changes.
63
+ // Height-only changes are caused by our own CSS (the collapse/expand
64
+ // animation via `grid-template-rows`), and reacting to them would
65
+ // either cause an infinite loop or disrupt the transition.
66
+ if (width === this.lastMeasuredWidth) {
67
+ return;
68
+ }
69
+ this.lastMeasuredWidth = width;
70
+ // Temporarily remove `min-height` to measure the natural content height.
71
+ // The `.is-tall` CSS sets a `min-height` (~164px) on this element,
72
+ // which inflates `scrollHeight` and would prevent `isTall` from ever
73
+ // becoming `false` again. We use `scrollHeight` instead of
74
+ // `contentRect.height`, because the latter reflects the
75
+ // CSS-constrained size (e.g. `grid-template-rows: 0fr`).
76
+ this.bodyTextElement.style.minHeight = '0';
77
+ const height = this.bodyTextElement.scrollHeight;
78
+ this.bodyTextElement.style.removeProperty('min-height');
62
79
  const maxHeight = this.isThreadMessage ? 48 : 160;
63
- this.isTall = height > maxHeight ? true : false;
80
+ this.isTall = height > maxHeight;
64
81
  if (this.bodyTextHeight === undefined) {
65
82
  this.bodyTextHeight = height;
66
83
  }
@@ -193,7 +210,7 @@ const FeedTimelineItem = /*@__PURE__*/ proxyCustomElement(class FeedTimelineItem
193
210
  render() {
194
211
  var _a, _b;
195
212
  const helperTextId = `helper-text-${this.item.id}`;
196
- return (h(Host, { key: 'ae530cfe3aa5c54f8c1d3ceae6630b606f5ca57f', id: this.item.id, class: {
213
+ return (h(Host, { key: '032d6c6319e54b88fb00496c3427554d6b5ee8ea', id: this.item.id, class: {
197
214
  'has-unpromoted-actions': !!((_a = this.item.unpromotedActions) === null || _a === void 0 ? void 0 : _a.length),
198
215
  'has-author-picture': !!((_b = this.item.author) === null || _b === void 0 ? void 0 : _b.picture),
199
216
  'shows-less': !this.showMore,
@@ -4,7 +4,7 @@ import { d as defineCustomElement$3 } from './kanban-item.js';
4
4
  import { d as defineCustomElement$2 } from './navigation-button.js';
5
5
  import { d as defineCustomElement$1 } from './summary-popover.js';
6
6
 
7
- const kanbanGroupCss = "@charset \"UTF-8\";:host(limebb-feed-timeline-item.is-tall) .markdown-container{margin-bottom:0.5rem}:host(limebb-feed-timeline-item.is-tall) .body-text{--body-text-min-height-set-by-code:10rem;min-height:calc(var(--body-text-min-height-set-by-code) + 0.25rem)}:host(limebb-feed-timeline-item.is-tall) .body-text:after{transition:opacity 0.6s ease;content:\"\";opacity:0.26;pointer-events:none;position:absolute;bottom:-0.125rem;right:-0.125rem;left:-0.125rem;height:2.5rem;background:radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0)) 0 100%, linear-gradient(to bottom, rgb(var(--limebb-feed-item-background-color), 0) 0%, rgb(var(--limebb-feed-item-background-color), 90%) 100%);background-repeat:no-repeat;background-size:100% 0.5rem, 100% 100%}:host(limebb-feed-timeline-item.is-tall.shows-less) .markdown-container{grid-template-rows:0fr}:host(limebb-feed-timeline-item.is-tall.shows-less) .body-text:after{opacity:1}:host(limebb-feed-timeline-item.is-tall.shows-more) .markdown-container{grid-template-rows:1fr}:host(limebb-feed-timeline-item.is-tall.shows-more) #show-more-button limel-icon{rotate:180deg}.markdown-container{position:relative;display:grid}.markdown-container:not(.opened){transition:grid-template-rows min(var(--body-text-height) * 1.2ms, 500ms) cubic-bezier(1, 0.09, 0, 0.89)}.body-text{display:block;overflow:hidden}#show-more-button{all:unset;transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color);box-shadow:var(--button-shadow-normal);z-index:1;position:absolute;right:0;bottom:-0.6rem;left:0;margin:0 auto;display:flex;align-items:center;justify-content:center;border-radius:3rem;width:2rem;height:1rem}#show-more-button:hover,#show-more-button:focus,#show-more-button:focus-visible{will-change:color, background-color, box-shadow, transform}#show-more-button:hover,#show-more-button:focus-visible{transform:translate3d(0, -0.04rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color);box-shadow:var(--button-shadow-hovered)}#show-more-button:active{--limel-clickable-transform-timing-function:cubic-bezier(\n 0.83,\n -0.15,\n 0.49,\n 1.16\n );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}#show-more-button:hover,#show-more-button:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}#show-more-button:focus{outline:none}#show-more-button:focus-visible{outline:none;box-shadow:var(--shadow-depth-8-focused)}#show-more-button limel-icon{transition:rotate 0.2s ease 0.5s;color:var(--mdc-theme-primary);width:0.75rem}*{box-sizing:border-box}:host(limebb-kanban-group){position:relative;background-color:rgb(var(--limebb-kanban-group-primary-background));--header-top-right-left-border-radius:0.5rem;--limebb-kanban-group-secondary-background:var(--contrast-300);--limebb-kanban-group-primary-background:var(--contrast-600);scroll-snap-align:start;box-sizing:border-box;display:flex;flex-direction:column;border-radius:0.5rem}:host(limebb-kanban-group.secondary){border:1px dashed rgb(var(--contrast-700));background-color:rgb(var(--limebb-kanban-group-secondary-background))}:host(limebb-kanban-group.secondary) limebb-kanban-item{border:1px dashed rgb(var(--contrast-700))}:host(limebb-kanban-group.secondary) limel-header{background-color:rgb(var(--limebb-kanban-group-secondary-background))}limel-header{padding-right:0.5rem;background-color:rgb(var(--limebb-kanban-group-primary-background));width:22rem}limel-header:has(+.items:empty){width:10rem}limel-badge{--badge-background-color:rgb(var(--contrast-200))}limel-help{position:absolute;z-index:1;left:-0.25rem;top:-0.25rem}.group-summary{flex-shrink:0;padding:0 0.75rem 0.5rem 0.75rem;opacity:0.6;max-height:7rem;max-width:22rem;overflow-y:auto}.load-more-button,limel-spinner{align-self:center}.load-more-button{--icon-background-color:var(--lime-elevated-surface-background-color)}.items{--limel-overflow-mask-vertical:linear-gradient(\n to bottom,\n transparent 0%,\n black calc(0% + var(--limel-top-edge-fade-height, 1rem)),\n black calc(100% - var(--limel-bottom-edge-fade-height, 1rem)),\n transparent 100%\n );-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);--limel-top-edge-fade-height:0.5rem;--limel-bottom-edge-fade-height:2rem;flex-grow:1;display:flex;flex-direction:column;gap:0.75rem;padding:var(--limel-top-edge-fade-height) 0.5rem var(--limel-bottom-edge-fade-height) 0.5rem;overflow-y:auto;-webkit-overflow-scrolling:touch}.items:not(:empty){width:22rem}limebb-kanban-item{transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}limebb-kanban-item:hover,limebb-kanban-item:focus,limebb-kanban-item:focus-visible{will-change:color, background-color, box-shadow, transform}limebb-kanban-item:hover,limebb-kanban-item:focus-visible{transform:translate3d(0, 0.01rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}limebb-kanban-item:hover{box-shadow:var(--button-shadow-hovered)}limebb-kanban-item:active{--limel-clickable-transform-timing-function:cubic-bezier(\n 0.83,\n -0.15,\n 0.49,\n 1.16\n );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}limebb-kanban-item:hover,limebb-kanban-item:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}limebb-kanban-item:focus{outline:none}limebb-kanban-item:focus-visible{outline:none;box-shadow:var(--shadow-depth-8-focused)}limebb-kanban-item.is-selected{box-shadow:var(--shadow-focused-state)}limebb-kanban-item.is-selected:hover{box-shadow:var(--shadow-focused-state), var(--button-shadow-hovered)}";
7
+ const kanbanGroupCss = "@charset \"UTF-8\";:host(limebb-feed-timeline-item.is-tall) .markdown-container{margin-bottom:0.5rem}:host(limebb-feed-timeline-item.is-tall) .body-text{--body-text-min-height-set-by-code:10rem;min-height:calc(var(--body-text-min-height-set-by-code) + 0.25rem)}:host(limebb-feed-timeline-item.is-tall) .body-text:after{transition:opacity 0.6s ease;content:\"\";opacity:0.26;pointer-events:none;position:absolute;bottom:-0.125rem;right:-0.125rem;left:-0.125rem;height:2.5rem;background:radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0)) 0 100%, linear-gradient(to bottom, rgb(var(--limebb-feed-item-background-color), 0) 0%, rgb(var(--limebb-feed-item-background-color), 90%) 100%);background-repeat:no-repeat;background-size:100% 0.5rem, 100% 100%}:host(limebb-feed-timeline-item.is-tall.shows-less) .markdown-container{grid-template-rows:0fr}:host(limebb-feed-timeline-item.is-tall.shows-less) .body-text:after{opacity:1}:host(limebb-feed-timeline-item.is-tall.shows-more) .markdown-container{grid-template-rows:1fr}:host(limebb-feed-timeline-item.is-tall.shows-more) #show-more-button limel-icon{rotate:180deg}.markdown-container{position:relative;display:grid}.markdown-container:not(.opened){transition:grid-template-rows min(var(--body-text-height) * 1.2ms, 500ms) cubic-bezier(1, 0.09, 0, 0.89)}.body-text{display:block;overflow:hidden}#show-more-button{all:unset;transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color);box-shadow:var(--button-shadow-normal);z-index:1;position:absolute;right:0;bottom:-0.6rem;left:0;margin:0 auto;display:flex;align-items:center;justify-content:center;border-radius:3rem;width:2rem;height:1rem}#show-more-button:hover,#show-more-button:focus,#show-more-button:focus-visible{will-change:color, background-color, box-shadow, transform}#show-more-button:hover,#show-more-button:focus-visible{transform:translate3d(0, -0.04rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color);box-shadow:var(--button-shadow-hovered)}#show-more-button:active{--limel-clickable-transform-timing-function:cubic-bezier(\n 0.83,\n -0.15,\n 0.49,\n 1.16\n );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}#show-more-button:hover,#show-more-button:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}#show-more-button:focus{outline:none}#show-more-button:focus-visible{outline:none;box-shadow:var(--shadow-depth-8-focused)}#show-more-button limel-icon{transition:rotate 0.2s ease 0.5s;color:var(--mdc-theme-primary);width:0.75rem}*{box-sizing:border-box}:host(limebb-kanban-group){position:relative;background-color:rgb(var(--limebb-kanban-group-primary-background));--header-top-right-left-border-radius:0.5rem;--limebb-kanban-group-secondary-background:var(--contrast-300);--limebb-kanban-group-primary-background:var(--contrast-600);scroll-snap-align:start;box-sizing:border-box;display:flex;flex-direction:column;border-radius:0.5rem}:host(limebb-kanban-group.secondary){border:1px dashed rgb(var(--contrast-700));background-color:rgb(var(--limebb-kanban-group-secondary-background))}:host(limebb-kanban-group.secondary) limebb-kanban-item{border:1px dashed rgb(var(--contrast-700))}:host(limebb-kanban-group.secondary) limel-header{background-color:rgb(var(--limebb-kanban-group-secondary-background))}limel-header{padding-right:0.5rem;background-color:rgb(var(--limebb-kanban-group-primary-background));width:var(--limebb-kanban-group-max-width, 22rem)}limel-header:has(+.items:empty){width:10rem}limel-badge{--badge-background-color:rgb(var(--contrast-200))}limel-help{position:absolute;z-index:1;left:-0.25rem;top:-0.25rem}.group-summary{flex-shrink:0;padding:0 0.75rem 0.5rem 0.75rem;opacity:0.6;max-height:7rem;max-width:var(--limebb-kanban-group-max-width, 22rem);overflow-y:auto}.load-more-button,limel-spinner{align-self:center}.load-more-button{--icon-background-color:var(--lime-elevated-surface-background-color)}.items{--limel-overflow-mask-vertical:linear-gradient(\n to bottom,\n transparent 0%,\n black calc(0% + var(--limel-top-edge-fade-height, 1rem)),\n black calc(100% - var(--limel-bottom-edge-fade-height, 1rem)),\n transparent 100%\n );-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);--limel-top-edge-fade-height:0.5rem;--limel-bottom-edge-fade-height:2rem;flex-grow:1;display:flex;flex-direction:column;gap:0.75rem;padding:var(--limel-top-edge-fade-height) 0.5rem var(--limel-bottom-edge-fade-height) 0.5rem;overflow-y:auto;-webkit-overflow-scrolling:touch}.items:not(:empty){width:var(--limebb-kanban-group-max-width, 22rem)}limebb-kanban-item{transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}limebb-kanban-item:hover,limebb-kanban-item:focus,limebb-kanban-item:focus-visible{will-change:color, background-color, box-shadow, transform}limebb-kanban-item:hover,limebb-kanban-item:focus-visible{transform:translate3d(0, 0.01rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}limebb-kanban-item:hover{box-shadow:var(--button-shadow-hovered)}limebb-kanban-item:active{--limel-clickable-transform-timing-function:cubic-bezier(\n 0.83,\n -0.15,\n 0.49,\n 1.16\n );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}limebb-kanban-item:hover,limebb-kanban-item:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}limebb-kanban-item:focus{outline:none}limebb-kanban-item:focus-visible{outline:none;box-shadow:var(--shadow-depth-8-focused)}limebb-kanban-item.is-selected{box-shadow:var(--shadow-focused-state)}limebb-kanban-item.is-selected:hover{box-shadow:var(--shadow-focused-state), var(--button-shadow-hovered)}";
8
8
  const LimebbKanbanGroupStyle0 = kanbanGroupCss;
9
9
 
10
10
  var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {