ooxml-ui 1.13.0 → 1.14.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 (245) hide show
  1. package/custom-elements.json +379 -4
  2. package/dist/{chunk-D7JPVAYS.js → chunk-C3MXKNNK.js} +2139 -136
  3. package/dist/{chunk-EL2A3BDM.js → chunk-C5F7454A.js} +2 -2
  4. package/dist/{chunk-3XST4F6J.js → chunk-DSQBXLMO.js} +1 -1
  5. package/dist/{chunk-BFPHRWEB.js → chunk-LAVVSCRJ.js} +85 -44
  6. package/dist/controls.js +1 -1
  7. package/dist/dialog/options-dialog.d.ts +41 -0
  8. package/dist/dialog/options-dialog.d.ts.map +1 -1
  9. package/dist/docx/content-control-calendar.d.ts +7 -0
  10. package/dist/docx/content-control-dialog.d.ts +7 -0
  11. package/dist/docx/content-control-view.d.ts +2 -0
  12. package/dist/docx/editor-core.d.ts +7 -0
  13. package/dist/docx/equation-commands.d.ts +17 -0
  14. package/dist/docx/equation-dialog.d.ts +7 -0
  15. package/dist/docx/format-dialogs.d.ts +1 -1
  16. package/dist/docx/insert-controller.d.ts +4 -0
  17. package/dist/docx/locales/en.d.ts +118 -0
  18. package/dist/docx/locales/objects.d.ts +37 -0
  19. package/dist/docx/locales/shapes.d.ts +101 -0
  20. package/dist/docx/localization-strings.d.ts +118 -0
  21. package/dist/docx/note-editor.d.ts +7 -1
  22. package/dist/docx/parts-controller.d.ts +27 -1
  23. package/dist/docx/print-caret-affinity.d.ts +15 -0
  24. package/dist/docx/print-editing-index.d.ts +1 -0
  25. package/dist/docx/print-editing.d.ts +2 -1
  26. package/dist/docx/print-float-geometry.d.ts +28 -0
  27. package/dist/docx/print-float-locate.d.ts +25 -0
  28. package/dist/docx/print-float-selection.d.ts +24 -0
  29. package/dist/docx/print-float-text.d.ts +8 -0
  30. package/dist/docx/print-keyboard-navigation.d.ts +4 -3
  31. package/dist/docx/print-layout-view.d.ts +6 -0
  32. package/dist/docx/print-layout.d.ts +1 -1
  33. package/dist/docx/print-note-editing.d.ts +47 -0
  34. package/dist/docx/print-page-window.d.ts +2 -1
  35. package/dist/docx/print-picture.d.ts +7 -0
  36. package/dist/docx/print-story-editing.d.ts +16 -0
  37. package/dist/docx/print-vertical-step.d.ts +7 -0
  38. package/dist/docx/ribbon-action.d.ts +15 -0
  39. package/dist/docx/ribbon-icons.d.ts +3 -0
  40. package/dist/docx/schema.d.ts +1 -1
  41. package/dist/docx/shape-commands.d.ts +40 -0
  42. package/dist/docx/shape-draw.d.ts +12 -0
  43. package/dist/docx/shape-format-ribbon.d.ts +4 -0
  44. package/dist/docx/shape-node-view.d.ts +4 -0
  45. package/dist/docx/shape-picker.d.ts +5 -0
  46. package/dist/docx/shape-text-dialog.d.ts +4 -0
  47. package/dist/docx/table-model-adapter.d.ts +1 -1
  48. package/dist/docx/table-navigation.d.ts +8 -0
  49. package/dist/docx.js +3046 -376
  50. package/dist/index.d.ts +1 -1
  51. package/dist/index.js +4 -4
  52. package/dist/pptx/{chart-3d-view-scene-MYQRQDNF.mjs → chart-3d-view-scene-BEAJCQIS.mjs} +2 -2
  53. package/dist/pptx/{chart-3d-view-scene-WX23DR2E.cjs → chart-3d-view-scene-CFUVOILD.cjs} +38 -38
  54. package/dist/pptx/{chunk-VFPVEVF4.cjs → chunk-2JTHIDMD.cjs} +173 -873
  55. package/dist/pptx/{chunk-J3GEBMYK.cjs → chunk-4NBVWV6I.cjs} +83 -0
  56. package/dist/pptx/{chunk-J4HVGXPW.cjs → chunk-5DUBDWOU.cjs} +4544 -2587
  57. package/dist/pptx/{chunk-C5OJXCCB.cjs → chunk-5UGJC25J.cjs} +1 -1
  58. package/dist/pptx/{chunk-NAFUYAQP.mjs → chunk-DWUC6LZK.mjs} +1 -1
  59. package/dist/pptx/{chunk-DZUSNYRA.mjs → chunk-GUAP5VUN.mjs} +3775 -1854
  60. package/dist/pptx/{chunk-K3S2A64B.cjs → chunk-HULZNSHL.cjs} +13 -13
  61. package/dist/pptx/{chunk-CB2DZVKL.mjs → chunk-OJXPN2WX.mjs} +83 -0
  62. package/dist/pptx/{chunk-QCP2XYJW.mjs → chunk-P3YE5LT2.mjs} +1 -1
  63. package/dist/pptx/{chunk-DPOES4WM.mjs → chunk-VXKFXPCU.mjs} +116 -829
  64. package/dist/pptx/dom/index.cjs +253 -253
  65. package/dist/pptx/dom/index.mjs +8 -8
  66. package/dist/pptx/export/html2canvas-clone.cjs +2 -2
  67. package/dist/pptx/export/html2canvas-clone.mjs +1 -1
  68. package/dist/pptx/i18n/index.cjs +6 -6
  69. package/dist/pptx/i18n/index.mjs +1 -1
  70. package/dist/pptx/i18n/translations-en.d.ts.map +1 -1
  71. package/dist/pptx/index.cjs +3235 -3099
  72. package/dist/pptx/index.mjs +5 -5
  73. package/dist/pptx/render/chart-auto-title.d.ts +10 -15
  74. package/dist/pptx/render/chart-auto-title.d.ts.map +1 -1
  75. package/dist/pptx/render/chart-cartesian-bubbles.d.ts.map +1 -1
  76. package/dist/pptx/render/chart-combo-stock.cjs +3 -3
  77. package/dist/pptx/render/chart-combo-stock.mjs +1 -1
  78. package/dist/pptx/render/chart-data-label-text.d.ts +1 -1
  79. package/dist/pptx/render/chart-error-bars.d.ts.map +1 -1
  80. package/dist/pptx/render/chart-helpers.d.ts +3 -10
  81. package/dist/pptx/render/chart-helpers.d.ts.map +1 -1
  82. package/dist/pptx/render/chart-overlays-trendline-fit.d.ts +7 -9
  83. package/dist/pptx/render/chart-overlays-trendline-fit.d.ts.map +1 -1
  84. package/dist/pptx/render/chart-overlays.cjs +7 -7
  85. package/dist/pptx/render/chart-overlays.mjs +1 -1
  86. package/dist/pptx/render/chart-stock-candles.d.ts +0 -21
  87. package/dist/pptx/render/chart-stock-candles.d.ts.map +1 -1
  88. package/dist/pptx/render/chart-style-palette.d.ts +3 -28
  89. package/dist/pptx/render/chart-style-palette.d.ts.map +1 -1
  90. package/dist/pptx/render/chart-sunburst-hierarchy.d.ts +8 -28
  91. package/dist/pptx/render/chart-sunburst-hierarchy.d.ts.map +1 -1
  92. package/dist/pptx/render/chart-surface-treemap.cjs +4 -4
  93. package/dist/pptx/render/chart-surface-treemap.mjs +1 -1
  94. package/dist/pptx/render/chart-treemap-hierarchy.d.ts +7 -9
  95. package/dist/pptx/render/chart-treemap-hierarchy.d.ts.map +1 -1
  96. package/dist/pptx/render/chart-trendlines.d.ts +3 -17
  97. package/dist/pptx/render/chart-trendlines.d.ts.map +1 -1
  98. package/dist/pptx/render/chart-view-model-points.d.ts +2 -23
  99. package/dist/pptx/render/chart-view-model-points.d.ts.map +1 -1
  100. package/dist/pptx/render/chart-view-model.cjs +44 -44
  101. package/dist/pptx/render/chart-view-model.mjs +1 -1
  102. package/dist/pptx/render/chart-waterfall-map.cjs +6 -6
  103. package/dist/pptx/render/chart-waterfall-map.mjs +1 -1
  104. package/dist/pptx/render/customization/customization-catalog.d.ts +9 -2
  105. package/dist/pptx/render/customization/customization-catalog.d.ts.map +1 -1
  106. package/dist/pptx/render/customization/customization-controller.d.ts +9 -1
  107. package/dist/pptx/render/customization/customization-controller.d.ts.map +1 -1
  108. package/dist/pptx/render/customization/customization-descriptions.d.ts +5 -1
  109. package/dist/pptx/render/customization/customization-descriptions.d.ts.map +1 -1
  110. package/dist/pptx/render/customization/customization-entry-points.d.ts +46 -0
  111. package/dist/pptx/render/customization/customization-entry-points.d.ts.map +1 -0
  112. package/dist/pptx/render/customization/customization-reference.d.ts.map +1 -1
  113. package/dist/pptx/render/customization/customization-regions.d.ts +131 -0
  114. package/dist/pptx/render/customization/customization-regions.d.ts.map +1 -0
  115. package/dist/pptx/render/customization/customization-resolve-regions.d.ts +11 -0
  116. package/dist/pptx/render/customization/customization-resolve-regions.d.ts.map +1 -0
  117. package/dist/pptx/render/customization/customization-resolve.d.ts +21 -2
  118. package/dist/pptx/render/customization/customization-resolve.d.ts.map +1 -1
  119. package/dist/pptx/render/customization/customization-surfaces.d.ts +21 -0
  120. package/dist/pptx/render/customization/customization-surfaces.d.ts.map +1 -1
  121. package/dist/pptx/render/customization/customization-types.d.ts +28 -2
  122. package/dist/pptx/render/customization/customization-types.d.ts.map +1 -1
  123. package/dist/pptx/render/customization/index.d.ts +2 -0
  124. package/dist/pptx/render/customization/index.d.ts.map +1 -1
  125. package/dist/pptx/render/customization/ribbon-control-catalog-core.d.ts +34 -2
  126. package/dist/pptx/render/customization/ribbon-control-catalog-core.d.ts.map +1 -1
  127. package/dist/pptx/render/customization/ribbon-control-catalog.d.ts +34 -2
  128. package/dist/pptx/render/customization/ribbon-control-catalog.d.ts.map +1 -1
  129. package/dist/pptx/render/customization/ribbon-control-visibility.d.ts +6 -1
  130. package/dist/pptx/render/customization/ribbon-control-visibility.d.ts.map +1 -1
  131. package/dist/pptx/render/editor-chrome/ribbon-css.d.ts +1 -1
  132. package/dist/pptx/render/editor-chrome/ribbon-css.d.ts.map +1 -1
  133. package/dist/pptx/render/index.d.ts +1 -1
  134. package/dist/pptx/render/index.d.ts.map +1 -1
  135. package/dist/pptx/render/morph-plan.d.ts.map +1 -1
  136. package/dist/pptx/render/ole-actions.d.ts +5 -3
  137. package/dist/pptx/render/ole-actions.d.ts.map +1 -1
  138. package/dist/pptx/render/options/index.d.ts +2 -0
  139. package/dist/pptx/render/options/index.d.ts.map +1 -1
  140. package/dist/pptx/render/options/options-dialog-binding.d.ts +60 -0
  141. package/dist/pptx/render/options/options-dialog-binding.d.ts.map +1 -0
  142. package/dist/pptx/render/options/options-dialog-model.d.ts +35 -0
  143. package/dist/pptx/render/options/options-dialog-model.d.ts.map +1 -0
  144. package/dist/pptx/render/ribbon-insert-layout.d.ts +56 -0
  145. package/dist/pptx/render/ribbon-insert-layout.d.ts.map +1 -0
  146. package/dist/pptx/render/ribbon-insert-state.d.ts +42 -2
  147. package/dist/pptx/render/ribbon-insert-state.d.ts.map +1 -1
  148. package/dist/pptx/render/viewer-props-manifest.d.ts +54 -0
  149. package/dist/pptx/render/viewer-props-manifest.d.ts.map +1 -0
  150. package/dist/pptx/web-components/index.d.ts.map +1 -1
  151. package/dist/pptx/web-components/office-aliases.d.ts +13 -0
  152. package/dist/pptx/web-components/office-aliases.d.ts.map +1 -1
  153. package/dist/pptx/web-components/office-token-bridge.d.ts +1 -1
  154. package/dist/pptx/web-components/office-token-bridge.d.ts.map +1 -1
  155. package/dist/pptx/web-components/options-dialog.d.ts +7 -0
  156. package/dist/pptx/web-components/options-dialog.d.ts.map +1 -0
  157. package/dist/pptx/web-components/ribbon-insert-dom.d.ts +28 -1
  158. package/dist/pptx/web-components/ribbon-insert-dom.d.ts.map +1 -1
  159. package/dist/pptx/web-components/ribbon-insert-icons.d.ts.map +1 -1
  160. package/dist/pptx/web-components/ribbon-insert-styles.d.ts.map +1 -1
  161. package/dist/pptx/web-components/ribbon-insert-table-picker.d.ts +16 -0
  162. package/dist/pptx/web-components/ribbon-insert-table-picker.d.ts.map +1 -0
  163. package/dist/pptx/web-components/ribbon-insert-view.d.ts +2 -1
  164. package/dist/pptx/web-components/ribbon-insert-view.d.ts.map +1 -1
  165. package/dist/pptx/web-components/ribbon-overflow.d.ts.map +1 -1
  166. package/dist/teams/activity-feed.d.ts +36 -0
  167. package/dist/teams/app/calendar-app.d.ts +2 -0
  168. package/dist/teams/app/chat-app.d.ts +11 -0
  169. package/dist/teams/app/chat-people-dialog.d.ts +38 -0
  170. package/dist/teams/app/controller.d.ts +3 -2
  171. package/dist/teams/app/dialog-base.d.ts +18 -0
  172. package/dist/teams/app/meeting-details.d.ts +63 -0
  173. package/dist/teams/app/meeting-dialog.d.ts +103 -0
  174. package/dist/teams/app/new-chat-dialog.d.ts +42 -0
  175. package/dist/teams/app/notify-delivery.d.ts +28 -0
  176. package/dist/teams/app/settings-notifications.d.ts +9 -0
  177. package/dist/teams/app/store.d.ts +1 -1
  178. package/dist/teams/app/teams-app.d.ts +65 -3
  179. package/dist/teams/app/teams-settings.d.ts +5 -1
  180. package/dist/teams/app/thread-pane.d.ts +1 -1
  181. package/dist/teams/app-rail.d.ts +15 -3
  182. package/dist/teams/base.d.ts +2 -0
  183. package/dist/teams/calendar-agenda.d.ts +30 -0
  184. package/dist/teams/calendar-view.d.ts +61 -0
  185. package/dist/teams/chat-composer.d.ts +32 -2
  186. package/dist/teams/chat-list.d.ts +32 -2
  187. package/dist/teams/chat-sidebar.d.ts +25 -0
  188. package/dist/teams/index.d.ts +6 -1
  189. package/dist/teams/people-picker.d.ts +37 -0
  190. package/dist/teams.js +2 -2
  191. package/dist/visio/locales/de-commands.d.ts +2 -0
  192. package/dist/visio/locales/de-text.d.ts +2 -0
  193. package/dist/visio/locales/en-commands.d.ts +2 -0
  194. package/dist/visio/locales/en-text.d.ts +2 -0
  195. package/dist/visio/locales/es-commands.d.ts +2 -0
  196. package/dist/visio/locales/es-text.d.ts +2 -0
  197. package/dist/visio/locales/fr-commands.d.ts +2 -0
  198. package/dist/visio/locales/fr-text.d.ts +2 -0
  199. package/dist/visio/locales/zh-CN-commands.d.ts +2 -0
  200. package/dist/visio/locales/zh-CN-text.d.ts +2 -0
  201. package/dist/visio/ribbon-home.d.ts +1 -1
  202. package/dist/visio/ribbon-locale.d.ts +11 -0
  203. package/dist/visio/ribbon-localize.d.ts +9 -0
  204. package/dist/visio/ribbon-tab-cue.d.ts +8 -0
  205. package/dist/visio/shapes-document.d.ts +5 -0
  206. package/dist/visio/shapes-files-ui.d.ts +13 -0
  207. package/dist/visio/shapes-master-list.d.ts +5 -0
  208. package/dist/visio/shapes-sections.d.ts +1 -4
  209. package/dist/visio/stencil-files.d.ts +36 -0
  210. package/dist/visio/styles.d.ts +1 -1
  211. package/dist/visio/viewer-chrome.d.ts +3 -3
  212. package/dist/visio/viewer-element.d.ts +2 -0
  213. package/dist/visio.js +2110 -150
  214. package/dist/xlsx/commands/slicers.d.ts +9 -0
  215. package/dist/xlsx/commands/view.d.ts +3 -0
  216. package/dist/xlsx/dialogs/pivot-field-settings.d.ts +7 -0
  217. package/dist/xlsx/dialogs/pivot-group-dates.d.ts +6 -0
  218. package/dist/xlsx/dialogs/pivot-group.d.ts +3 -0
  219. package/dist/xlsx/dialogs/pivot-rows.d.ts +21 -0
  220. package/dist/xlsx/dialogs/pivot-values.d.ts +1 -1
  221. package/dist/xlsx/dialogs/scenario-manager.d.ts +2 -0
  222. package/dist/xlsx/dialogs/slicers.d.ts +9 -0
  223. package/dist/xlsx/dialogs/styles.d.ts +1 -1
  224. package/dist/xlsx/dialogs/what-if.d.ts +5 -0
  225. package/dist/xlsx/grid/page-break-layer.d.ts +3 -0
  226. package/dist/xlsx/grid/slicer.d.ts +22 -0
  227. package/dist/xlsx/grid/styles.d.ts +1 -1
  228. package/dist/xlsx/grid/timeline.d.ts +12 -0
  229. package/dist/xlsx/modules.d.ts +1 -0
  230. package/dist/xlsx/page-views/index.d.ts +7 -0
  231. package/dist/xlsx/page-views/page-layout-bands.d.ts +17 -0
  232. package/dist/xlsx/page-views/page-layout-css.d.ts +1 -0
  233. package/dist/xlsx/page-views/page-layout-editor.d.ts +21 -0
  234. package/dist/xlsx/page-views/page-layout-rulers.d.ts +3 -0
  235. package/dist/xlsx/page-views/page-layout-view.d.ts +8 -0
  236. package/dist/xlsx/print-pages.d.ts +7 -1
  237. package/dist/xlsx/ribbon/tabs/contextual.d.ts +4 -0
  238. package/dist/xlsx.js +2718 -359
  239. package/package.json +2 -2
  240. package/dist/pptx/render/chart-number-format-pattern.d.ts +0 -35
  241. package/dist/pptx/render/chart-number-format-pattern.d.ts.map +0 -1
  242. package/dist/pptx/render/chart-number-format.d.ts +0 -21
  243. package/dist/pptx/render/chart-number-format.d.ts.map +0 -1
  244. package/dist/pptx/render/chart-waterfall-layout.d.ts +0 -20
  245. package/dist/pptx/render/chart-waterfall-layout.d.ts.map +0 -1
@@ -1,12 +1,12 @@
1
1
  import { THEME_CSS, installOfficeUiTheme } from './chunk-7E3HU7RL.js';
2
- import { defineMenuButton, defineMenuItem, registerControls, defineSwitch } from './chunk-BFPHRWEB.js';
2
+ import { defineMenuButton, defineMenuItem, registerControls, defineSwitch } from './chunk-LAVVSCRJ.js';
3
3
  import { registerIcon, getIcon } from './chunk-M6GF5ED2.js';
4
4
  import { initialsOf, definePresence } from './chunk-KNC2UEUR.js';
5
5
  import { definer, OfficeElement, controlStyles } from './chunk-CWBSXK7P.js';
6
6
  import { nothing, html, LitElement, unsafeCSS, svg } from 'lit';
7
+ import { filterActivity, ACTIVITY_FILTERS, describeActivity, localZone, startOfDay, occurrencesBetween, DAY_MS as DAY_MS$1, dayKey, zonedToUtc, viewDays, zonedParts, shiftAnchor, layoutDay, mentionQueryAt, filterMentionTargets, insertMention, MAX_ATTACHMENTS, mentionsUser, mentionSegments, detectContentKind, contentUrl, readContent, sortFiles, filterFiles, parseServerConfig, STATUS_DURATIONS, STATUS_DURATION_LABELS, PRESENCE_LABELS, MAX_STATUS_MESSAGE_CHARS, isPresenceStatus, PRESENCE_STATUSES, isStatusDuration, IDLE_MINUTE_CHOICES, sanitizeChannelName, MAX_CHANNEL_NAME_CHARS, MAX_CHANNEL_TOPIC_CHARS, MAX_CHAT_MEMBERS, occurrenceAt, MAX_RECURRENCE_COUNT, MINUTE_MS, MAX_MEETING_TITLE, MAX_MEETING_DESCRIPTION, markdownBlocks, createTeamsClient, sanitizeAttachment, markdownInline } from 'ooxml-core/teams';
7
8
  import { repeat } from 'lit/directives/repeat.js';
8
9
  import { live } from 'lit/directives/live.js';
9
- import { MAX_ATTACHMENTS, detectContentKind, contentUrl, readContent, sortFiles, filterFiles, parseServerConfig, STATUS_DURATIONS, STATUS_DURATION_LABELS, PRESENCE_LABELS, MAX_STATUS_MESSAGE_CHARS, isPresenceStatus, PRESENCE_STATUSES, isStatusDuration, IDLE_MINUTE_CHOICES, sanitizeChannelName, MAX_CHANNEL_NAME_CHARS, MAX_CHANNEL_TOPIC_CHARS, markdownBlocks, createTeamsClient, sanitizeAttachment, markdownInline } from 'ooxml-core/teams';
10
10
  import { keyed } from 'lit/directives/keyed.js';
11
11
 
12
12
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/reset.css?raw
@@ -42,6 +42,17 @@ function formatSize(bytes) {
42
42
  if (bytes < 1048576) return `${Math.round(bytes / 1024)} KB`;
43
43
  return `${(bytes / 1048576).toFixed(1)} MB`;
44
44
  }
45
+ var timeOnly = (ts) => new Date(ts).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
46
+ function formatWhen(start, end) {
47
+ const day = new Date(start).toLocaleDateString([], {
48
+ weekday: "short",
49
+ month: "short",
50
+ day: "numeric"
51
+ });
52
+ const sameDay = new Date(start).toDateString() === new Date(end).toDateString();
53
+ const tail = sameDay ? timeOnly(end) : `${new Date(end).toLocaleDateString([], { month: "short", day: "numeric" })}, ${timeOnly(end)}`;
54
+ return `${day}, ${timeOnly(start)} - ${tail}`;
55
+ }
45
56
  var GLYPHS = {
46
57
  back: "M12.5 4 6.5 10l6 6",
47
58
  pin: "M7 3h6l-1 5 3 3H5l3-3ZM10 11v6",
@@ -62,7 +73,9 @@ var GLYPHS = {
62
73
  screenShare: "M3 4h14v9H3ZM7 17h6M10 13v4M10 10V6M8 8l2-2 2 2",
63
74
  hand: "M7 10V4.5a1 1 0 0 1 2 0V9M9 9V3.5a1 1 0 0 1 2 0V9M11 9V4.5a1 1 0 0 1 2 0V10M13 8.5a1 1 0 0 1 2 0V12a5 5 0 0 1-5 5h-.5A4.5 4.5 0 0 1 5 13.5L4 10.5a1 1 0 0 1 2-.5l1 2",
64
75
  phoneOff: "M3 11c4-3 10-3 14 0l-1.5 2.5-3-1V10a9 9 0 0 0-5 0v2.5l-3 1Z",
65
- chat: "M3 4h14v9H8l-5 4z"
76
+ chat: "M3 4h14v9H8l-5 4z",
77
+ bell: "M5 14h10l-1.5-2V8.5a3.5 3.5 0 0 0-7 0V12ZM8.5 16.5a1.5 1.5 0 0 0 3 0",
78
+ at: "M13 10a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM13 10v1.2a1.8 1.8 0 0 0 3.6 0V10a6.6 6.6 0 1 0-2.6 5.2"
66
79
  };
67
80
  for (const [name, d] of Object.entries(GLYPHS)) registerIcon(name, d);
68
81
  function icon(name) {
@@ -73,54 +86,232 @@ function icon(name) {
73
86
  </svg>`;
74
87
  }
75
88
 
89
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/activity-feed.css?raw
90
+ var activity_feed_default = ":host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n}\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--office-space-2);\n padding: var(--office-space-3) var(--office-space-3) var(--office-space-1);\n}\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n font-weight: var(--office-font-weight-bold);\n}\n.read-all {\n padding: var(--office-space-1) var(--office-space-2);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n color: var(--office-teams-brand);\n font-size: var(--office-font-size-sm);\n}\n.read-all:hover:not(:disabled) {\n background: var(--office-selected);\n}\n.read-all:disabled {\n color: var(--office-teams-text-subtle);\n cursor: default;\n}\n.filters {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-1);\n padding: var(--office-space-1) var(--office-space-3) var(--office-space-2);\n}\n.filters button {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-1);\n padding: var(--office-space-1) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-full);\n background: transparent;\n font-size: var(--office-font-size-sm);\n}\n.filters button:hover {\n background: var(--office-selected);\n}\n.filters button[aria-pressed='true'] {\n border-color: var(--office-teams-brand);\n background: var(--office-teams-brand-subtle);\n color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\n.count {\n min-width: 1.4em;\n padding: 0 var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: var(--office-font-size-3xs);\n line-height: 1.6;\n text-align: center;\n}\nul {\n flex: 1;\n min-height: 0;\n margin: 0;\n padding: 0 var(--office-space-2) var(--office-space-2);\n overflow-y: auto;\n list-style: none;\n}\nli {\n position: relative;\n}\n.item {\n display: flex;\n align-items: flex-start;\n gap: var(--office-space-3);\n width: 100%;\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n color: inherit;\n text-align: start;\n}\n.item:hover {\n background: var(--office-selected);\n}\n.item[aria-current='true'] {\n background: var(--office-teams-brand-subtle);\n}\n.face {\n position: relative;\n flex: none;\n}\n.kind {\n position: absolute;\n inset-block-end: calc(-1 * var(--office-space-1));\n inset-inline-end: calc(-1 * var(--office-space-1));\n display: grid;\n place-items: center;\n width: 1.5em;\n height: 1.5em;\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\n.kind svg {\n width: 0.9em;\n height: 0.9em;\n}\n.text {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n}\n.line {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\nli[data-unread='true'] .line {\n font-weight: var(--office-font-weight-bold);\n}\n.preview {\n overflow: hidden;\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.when {\n display: flex;\n flex: none;\n flex-direction: column;\n align-items: flex-end;\n gap: var(--office-space-1);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.dot {\n width: 0.7em;\n height: 0.7em;\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n}\n.toggle {\n position: absolute;\n inset-block-end: var(--office-space-1);\n inset-inline-end: var(--office-space-2);\n display: none;\n place-items: center;\n width: 2em;\n height: 2em;\n border: 0;\n border-radius: var(--office-radius);\n background: var(--office-popover);\n color: var(--office-teams-text-subtle);\n box-shadow: var(--office-shadow-teams);\n}\nli:hover .toggle,\nli:focus-within .toggle {\n display: grid;\n}\n.empty {\n margin: 0;\n padding: var(--office-space-4);\n color: var(--office-teams-text-subtle);\n text-align: center;\n}\n@media (pointer: coarse) {\n .filters button,\n .read-all {\n min-height: var(--office-touch-target, 44px);\n }\n .toggle {\n display: grid;\n width: var(--office-touch-target, 44px);\n height: var(--office-touch-target, 44px);\n }\n}\n";
91
+
92
+ // src/teams/activity-feed.ts
93
+ var FILTER_LABEL = {
94
+ all: "All",
95
+ unread: "Unread",
96
+ mentions: "Mentions",
97
+ replies: "Replies",
98
+ reactions: "Reactions"
99
+ };
100
+ var KIND_ICON = {
101
+ mention: "at",
102
+ reply: "reply",
103
+ reaction: "smile",
104
+ pin: "pin",
105
+ meeting: "calendar"
106
+ };
107
+ var EMPTY = {
108
+ all: "No activity yet. Mentions, replies, reactions and pinned messages show up here.",
109
+ unread: "You are all caught up.",
110
+ mentions: "Nobody has mentioned you yet.",
111
+ replies: "No replies to your messages or followed threads yet.",
112
+ reactions: "No reactions to your messages yet."
113
+ };
114
+ function activityTime(ts, now = Date.now()) {
115
+ const day = dayLabel(ts, now);
116
+ if (day === "Today") return formatTime(ts);
117
+ if (day === "Yesterday") return day;
118
+ return new Date(ts).toLocaleDateString([], { month: "short", day: "numeric" });
119
+ }
120
+ var OfficeUiActivityFeed = class extends TeamsElement {
121
+ static styles = withStyles(activity_feed_default);
122
+ static properties = {
123
+ items: { attribute: false },
124
+ filter: { type: String, reflect: true },
125
+ selectedId: { type: String, attribute: "selected-id" }
126
+ };
127
+ constructor() {
128
+ super();
129
+ this.items = [];
130
+ this.filter = "all";
131
+ this.selectedId = "";
132
+ }
133
+ get unread() {
134
+ return this.items.filter((item) => !item.read).length;
135
+ }
136
+ render() {
137
+ const shown = filterActivity(this.items, this.filter);
138
+ const unread = this.unread;
139
+ return html`
140
+ <header>
141
+ <h2>Activity</h2>
142
+ <button
143
+ type="button"
144
+ class="read-all"
145
+ ?disabled=${unread === 0}
146
+ @click=${() => this.fire("office-activity-read-all", {})}
147
+ >
148
+ Mark all as read
149
+ </button>
150
+ </header>
151
+ <div class="filters" role="group" aria-label="Filter activity">
152
+ ${ACTIVITY_FILTERS.map(
153
+ (value) => html`<button
154
+ type="button"
155
+ aria-pressed=${String(this.filter === value)}
156
+ @click=${() => {
157
+ this.filter = value;
158
+ this.fire("office-activity-filter", { filter: value });
159
+ }}
160
+ >
161
+ ${FILTER_LABEL[value]}${value === "unread" && unread ? html` <span class="count">${unread}</span>` : nothing}
162
+ </button>`
163
+ )}
164
+ </div>
165
+ ${shown.length ? html`<ul aria-label="Activity feed">
166
+ ${shown.map((item) => this.row(item))}
167
+ </ul>` : html`<p class="empty" role="status">${EMPTY[this.filter]}</p>`}
168
+ `;
169
+ }
170
+ row(item) {
171
+ const line = describeActivity(item);
172
+ return html`<li data-unread=${String(!item.read)} data-kind=${item.kind}>
173
+ <button
174
+ type="button"
175
+ class="item"
176
+ aria-current=${this.selectedId === item.id ? "true" : "false"}
177
+ aria-label=${`${item.read ? "" : "Unread. "}${line}. ${item.preview}`}
178
+ @click=${() => this.fire("office-activity-open", { id: item.id })}
179
+ >
180
+ <span class="face">
181
+ <office-ui-avatar
182
+ size="md"
183
+ name=${item.actorName}
184
+ seed=${item.actorId}
185
+ ></office-ui-avatar>
186
+ <span class="kind" aria-hidden="true">${icon(KIND_ICON[item.kind])}</span>
187
+ </span>
188
+ <span class="text">
189
+ <span class="line">${line}</span>
190
+ <span class="preview">${item.preview}</span>
191
+ </span>
192
+ <span class="when">
193
+ <time datetime=${new Date(item.ts).toISOString()}>${activityTime(item.ts)}</time>
194
+ ${item.read ? nothing : html`<span class="dot" aria-hidden="true"></span>`}
195
+ </span>
196
+ </button>
197
+ <button
198
+ type="button"
199
+ class="toggle"
200
+ title=${item.read ? "Mark as unread" : "Mark as read"}
201
+ aria-label=${item.read ? "Mark as unread" : "Mark as read"}
202
+ @click=${() => this.fire("office-activity-read", { id: item.id, read: !item.read })}
203
+ >
204
+ ${icon(item.read ? "bell" : "check")}
205
+ </button>
206
+ </li>`;
207
+ }
208
+ };
209
+ var defineActivityFeed = definer("office-ui-activity-feed", () => OfficeUiActivityFeed);
210
+
76
211
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app-rail.css?raw
77
- var app_rail_default = ":host {\n display: block;\n width: 5em;\n background: var(--office-teams-rail);\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\nul {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--office-space-1);\n margin: 0;\n padding: var(--office-space-2) 0;\n list-style: none;\n}\nbutton {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--office-space-0);\n width: 4.4em;\n padding: var(--office-space-2) 0;\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n color: var(--office-teams-text-subtle);\n}\nbutton:hover {\n background: var(--office-selected);\n}\nbutton[aria-current='page'] {\n color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\nbutton[aria-current='page']::before {\n content: '';\n position: absolute;\n inset-inline-start: calc(-1 * var(--office-space-1));\n inset-block: 20%;\n width: var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n}\n.glyph {\n position: relative;\n display: grid;\n place-items: center;\n font-size: 1.3em;\n}\n.label {\n font-size: var(--office-font-size-2xs);\n}\n:host([hide-labels]) .label {\n display: none;\n}\n.badge {\n position: absolute;\n inset-block-start: -0.4em;\n inset-inline-end: -0.9em;\n min-width: 1.5em;\n padding: 0 var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: var(--office-font-size-3xs);\n line-height: 1.6;\n text-align: center;\n}\n\n.short {\n display: none;\n}\n\n/* Compact shell: the rail becomes a bottom navigation bar with short labels. */\n@media (max-width: 56em) {\n :host {\n width: auto;\n padding-block-end: env(safe-area-inset-bottom, 0);\n padding-inline: env(safe-area-inset-left, 0) env(safe-area-inset-right, 0);\n border-inline-end: 0;\n border-block-start: var(--office-border-width) solid var(--office-teams-divider);\n }\n ul {\n flex-direction: row;\n justify-content: space-around;\n align-items: stretch;\n gap: 0;\n padding: var(--office-space-1) var(--office-space-1);\n }\n li {\n flex: 1 1 0;\n min-width: 0;\n }\n button {\n width: 100%;\n min-height: var(--office-touch-target, 44px);\n padding: var(--office-space-1) 0;\n justify-content: center;\n }\n button[aria-current='page']::before {\n inset-inline: 25%;\n inset-block: 0 auto;\n width: auto;\n height: var(--office-space-1);\n }\n .label.has-short {\n display: none;\n }\n .short {\n display: inline;\n font-size: var(--office-font-size-2xs);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n }\n}\n@media (max-width: 56em) and (max-height: 30em) {\n .label,\n .short {\n display: none;\n }\n button {\n min-height: 2.4em;\n }\n}\n";
212
+ var app_rail_default = ":host {\n display: block;\n width: 5em;\n background: var(--office-teams-rail);\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\nul {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--office-space-1);\n margin: 0;\n padding: var(--office-space-2) 0;\n list-style: none;\n}\nbutton {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--office-space-0);\n width: 4.4em;\n padding: var(--office-space-2) 0;\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n color: var(--office-teams-text-subtle);\n}\nbutton:hover {\n background: var(--office-selected);\n}\nbutton[aria-current='page'] {\n color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\nbutton[aria-current='page']::before {\n content: '';\n position: absolute;\n inset-inline-start: calc(-1 * var(--office-space-1));\n inset-block: 20%;\n width: var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n}\n.glyph {\n position: relative;\n display: grid;\n place-items: center;\n font-size: 1.3em;\n}\n.label {\n font-size: var(--office-font-size-2xs);\n}\n:host([hide-labels]) .label {\n display: none;\n}\n.badge {\n position: absolute;\n inset-block-start: -0.4em;\n inset-inline-end: -0.9em;\n min-width: 1.5em;\n padding: 0 var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: var(--office-font-size-3xs);\n line-height: 1.6;\n text-align: center;\n}\n\n.short {\n display: none;\n}\n\n/* Compact shell: the rail becomes a bottom navigation bar with short labels. */\n@media (max-width: 56em) {\n :host {\n width: auto;\n padding-block-end: env(safe-area-inset-bottom, 0);\n padding-inline: env(safe-area-inset-left, 0) env(safe-area-inset-right, 0);\n border-inline-end: 0;\n border-block-start: var(--office-border-width) solid var(--office-teams-divider);\n }\n ul {\n flex-direction: row;\n justify-content: space-around;\n align-items: stretch;\n gap: 0;\n padding: var(--office-space-1) var(--office-space-1);\n }\n li {\n flex: 1 1 0;\n min-width: 0;\n }\n button {\n width: 100%;\n min-height: var(--office-touch-target, 44px);\n padding: var(--office-space-1) 0;\n justify-content: center;\n }\n button[aria-current='page']::before {\n inset-inline: 25%;\n inset-block: 0 auto;\n width: auto;\n height: var(--office-space-1);\n }\n .label.has-short {\n display: none;\n }\n .short {\n display: inline;\n font-size: var(--office-font-size-2xs);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n }\n}\n@media (max-width: 56em) and (max-height: 30em) {\n .label,\n .short {\n display: none;\n }\n button {\n min-height: 2.4em;\n }\n}\n\n/* The More sheet exists only on the phone bar. */\n.more-item {\n display: none;\n}\n.sheet {\n box-sizing: border-box;\n width: 100%;\n max-width: 100%;\n margin: auto 0 0;\n padding: var(--office-space-3) var(--office-space-3)\n calc(var(--office-space-3) + env(safe-area-inset-bottom, 0));\n border: 0;\n border-start-start-radius: var(--office-radius-lg);\n border-start-end-radius: var(--office-radius-lg);\n background: var(--office-popover);\n color: var(--office-foreground);\n box-shadow: var(--office-shadow-teams);\n}\n.sheet::backdrop {\n background: var(--office-overlay);\n}\n.sheet ul {\n flex-direction: column;\n align-items: stretch;\n padding: 0;\n}\n.sheet li {\n flex: none;\n}\n.sheet button {\n flex-direction: row;\n justify-content: flex-start;\n gap: var(--office-space-3);\n width: 100%;\n min-height: var(--office-touch-target, 44px);\n padding: var(--office-space-2) var(--office-space-3);\n font-size: var(--office-font-size);\n}\n.sheet button.close {\n justify-content: center;\n margin-top: var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n}\n.sheet .label {\n display: inline;\n font-size: var(--office-font-size);\n}\n@media (max-width: 56em) {\n li[data-secondary='true'] {\n display: none;\n }\n .more-item {\n display: block;\n flex: 1 1 0;\n min-width: 0;\n }\n .more .glyph {\n font-size: 1.3em;\n }\n}\n";
78
213
 
79
214
  // src/teams/app-rail.ts
215
+ var total = (n) => n > 99 ? "99+" : String(n);
80
216
  var OfficeUiAppRail = class extends TeamsElement {
81
217
  static styles = withStyles(app_rail_default);
82
218
  static properties = {
83
219
  items: { attribute: false },
84
220
  selected: { type: String },
85
- hideLabels: { type: Boolean, attribute: "hide-labels", reflect: true }
221
+ hideLabels: { type: Boolean, attribute: "hide-labels", reflect: true },
222
+ moreOpen: { state: true }
86
223
  };
87
224
  constructor() {
88
225
  super();
89
226
  this.items = [];
90
227
  this.selected = "";
91
228
  this.hideLabels = false;
229
+ this.moreOpen = false;
92
230
  }
93
231
  connectedCallback() {
94
232
  super.connectedCallback();
95
233
  this.setAttribute("role", "navigation");
96
234
  this.setAttribute("aria-label", "App bar");
97
235
  }
236
+ updated(changed) {
237
+ const dialog = this.renderRoot.querySelector("dialog");
238
+ if (!dialog || !changed.has("moreOpen")) return;
239
+ if (this.moreOpen && !dialog.open) dialog.showModal();
240
+ else if (!this.moreOpen && dialog.open) dialog.close();
241
+ }
242
+ closeMore() {
243
+ this.moreOpen = false;
244
+ void this.updateComplete.then(
245
+ () => this.renderRoot.querySelector(".more")?.focus()
246
+ );
247
+ }
248
+ select(id) {
249
+ this.moreOpen = false;
250
+ this.fire("office-rail-select", { id });
251
+ }
98
252
  render() {
253
+ const secondary = this.items.filter((i) => i.secondary);
254
+ const badge = secondary.reduce((n, i) => n + (i.badge ?? 0), 0);
255
+ const inMore = secondary.some((i) => i.id === this.selected);
99
256
  return html`
100
257
  <ul>
101
258
  ${this.items.map(
102
259
  (item) => html`
103
- <li>
104
- <button
105
- type="button"
106
- aria-label=${item.label}
107
- title=${item.label}
108
- aria-current=${this.selected === item.id ? "page" : "false"}
109
- @click=${() => this.fire("office-rail-select", { id: item.id })}
110
- >
111
- <span class="glyph">
112
- ${icon(item.icon)}
113
- ${item.badge ? html`<span class="badge">${item.badge > 99 ? "99+" : item.badge}</span>` : ""}
114
- </span>
115
- <span class=${item.shortLabel ? "label has-short" : "label"}>${item.label}</span>
116
- ${item.shortLabel ? html`<span class="short" aria-hidden="true">${item.shortLabel}</span>` : nothing}
117
- </button>
118
- </li>
260
+ <li data-secondary=${String(item.secondary === true)}>${this.button(item)}</li>
119
261
  `
120
262
  )}
263
+ ${secondary.length ? html`<li class="more-item">
264
+ <button
265
+ type="button"
266
+ class="more"
267
+ aria-label=${badge ? `More, ${badge} unread` : "More"}
268
+ aria-haspopup="dialog"
269
+ aria-expanded=${String(this.moreOpen)}
270
+ aria-current=${inMore ? "page" : "false"}
271
+ @click=${() => this.moreOpen = true}
272
+ >
273
+ <span class="glyph">
274
+ ${icon("more")}
275
+ ${badge ? html`<span class="badge">${total(badge)}</span>` : nothing}
276
+ </span>
277
+ <span class="short" aria-hidden="true">More</span>
278
+ </button>
279
+ </li>` : nothing}
121
280
  </ul>
281
+ ${secondary.length ? html`<dialog
282
+ class="sheet"
283
+ aria-label="More apps"
284
+ @cancel=${(e) => {
285
+ e.preventDefault();
286
+ this.closeMore();
287
+ }}
288
+ @click=${(e) => {
289
+ if (e.target === e.currentTarget) this.closeMore();
290
+ }}
291
+ >
292
+ <ul>
293
+ ${secondary.map((item) => html`<li>${this.button(item, true)}</li>`)}
294
+ </ul>
295
+ <button type="button" class="close" @click=${() => this.closeMore()}>Close</button>
296
+ </dialog>` : nothing}
122
297
  `;
123
298
  }
299
+ button(item, sheet = false) {
300
+ return html`<button
301
+ type="button"
302
+ aria-label=${item.label}
303
+ title=${item.label}
304
+ aria-current=${this.selected === item.id ? "page" : "false"}
305
+ @click=${() => this.select(item.id)}
306
+ >
307
+ <span class="glyph">
308
+ ${icon(item.icon)}
309
+ ${item.badge ? html`<span class="badge">${total(item.badge)}</span>` : nothing}
310
+ </span>
311
+ <span class=${item.shortLabel && !sheet ? "label has-short" : "label"}>${item.label}</span>
312
+ ${item.shortLabel && !sheet ? html`<span class="short" aria-hidden="true">${item.shortLabel}</span>` : nothing}
313
+ </button>`;
314
+ }
124
315
  };
125
316
  var defineAppRail = definer("office-ui-app-rail", () => OfficeUiAppRail);
126
317
 
@@ -179,10 +370,10 @@ var call_controls_default = ":host {\n display: flex;\n align-items: center;\n j
179
370
 
180
371
  // src/teams/call-controls.ts
181
372
  var formatElapsed = (ms) => {
182
- const total = Math.max(0, Math.floor(ms / 1e3));
373
+ const total2 = Math.max(0, Math.floor(ms / 1e3));
183
374
  const pad = (n) => String(n).padStart(2, "0");
184
- const h = Math.floor(total / 3600);
185
- return `${h ? `${h}:` : ""}${pad(Math.floor(total % 3600 / 60))}:${pad(total % 60)}`;
375
+ const h = Math.floor(total2 / 3600);
376
+ return `${h ? `${h}:` : ""}${pad(Math.floor(total2 % 3600 / 60))}:${pad(total2 % 60)}`;
186
377
  };
187
378
  var OfficeUiCallControls = class extends TeamsElement {
188
379
  static styles = withStyles(call_controls_default);
@@ -266,6 +457,346 @@ var OfficeUiCallControls = class extends TeamsElement {
266
457
  };
267
458
  var defineCallControls = definer("office-ui-call-controls", () => OfficeUiCallControls);
268
459
 
460
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/calendar-agenda.css?raw
461
+ var calendar_agenda_default = ":host {\n display: block;\n overflow: auto;\n}\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-3) var(--office-space-3) var(--office-space-2) var(--office-space-4);\n}\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\nh3 {\n margin: var(--office-space-3) var(--office-space-4) var(--office-space-1);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n font-weight: var(--office-font-weight-bold);\n}\n.new {\n display: grid;\n place-items: center;\n width: 2.2em;\n height: 2.2em;\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n color: var(--office-teams-brand);\n cursor: pointer;\n}\n.new:hover {\n background: var(--office-selected);\n}\n.empty {\n margin: var(--office-space-4);\n color: var(--office-teams-text-subtle);\n}\nul {\n display: grid;\n gap: var(--office-space-1);\n margin: 0;\n padding: 0 var(--office-space-2);\n list-style: none;\n}\nli {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n}\n.row {\n display: grid;\n flex: 1;\n min-width: 0;\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-inline-start: var(--office-border-width-thick) solid var(--office-teams-brand);\n border-radius: var(--office-radius);\n background: var(--office-teams-bubble);\n color: inherit;\n font: inherit;\n text-align: start;\n cursor: pointer;\n}\n.row:hover {\n background: var(--office-selected);\n}\n.row[data-live='true'] {\n border-inline-start-color: var(--office-call-live);\n}\n.time,\n.who {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.title {\n overflow: hidden;\n font-weight: var(--office-font-weight-bold);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.join {\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font: inherit;\n cursor: pointer;\n}\n.join:hover {\n background: var(--office-teams-brand-hover);\n}\n@media (pointer: coarse) {\n .new,\n .row,\n .join {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n";
462
+
463
+ // src/teams/calendar-agenda.ts
464
+ var HORIZON_DAYS = 14;
465
+ var JOIN_WINDOW_MS = 15 * 6e4;
466
+ var OfficeUiCalendarAgenda = class extends TeamsElement {
467
+ static styles = withStyles(calendar_agenda_default);
468
+ static properties = {
469
+ meetings: { attribute: false },
470
+ zone: { type: String },
471
+ now: { type: Number }
472
+ };
473
+ constructor() {
474
+ super();
475
+ this.meetings = [];
476
+ this.zone = localZone();
477
+ this.now = 0;
478
+ }
479
+ render() {
480
+ const now = this.now || Date.now();
481
+ const from = startOfDay(now, this.zone);
482
+ const rows = this.meetings.flatMap(
483
+ (m) => occurrencesBetween(m, from, from + HORIZON_DAYS * DAY_MS$1).map((o) => ({ o, m }))
484
+ ).filter(({ o }) => o.end > now).sort((a, b) => a.o.start - b.o.start);
485
+ const groups = /* @__PURE__ */ new Map();
486
+ for (const row of rows) {
487
+ const key = dayKey(row.o.start, this.zone);
488
+ let group = groups.get(key);
489
+ if (!group) {
490
+ const label = new Intl.DateTimeFormat([], {
491
+ weekday: "long",
492
+ month: "short",
493
+ day: "numeric",
494
+ timeZone: this.zone
495
+ }).format(row.o.start);
496
+ groups.set(
497
+ key,
498
+ group = { label: key === dayKey(now, this.zone) ? `Today, ${label}` : label, rows: [] }
499
+ );
500
+ }
501
+ group.rows.push(row);
502
+ }
503
+ return html`
504
+ <header>
505
+ <h2>Calendar</h2>
506
+ <button
507
+ class="new"
508
+ type="button"
509
+ title="New meeting"
510
+ aria-label="New meeting"
511
+ @click=${() => this.fire("office-agenda-new", {})}
512
+ >
513
+ ${icon("plus")}
514
+ </button>
515
+ </header>
516
+ ${groups.size === 0 ? html`<p class="empty">No upcoming meetings. Schedule one with the plus button.</p>` : [...groups.values()].map(
517
+ (g) => html`<section>
518
+ <h3>${g.label}</h3>
519
+ <ul>
520
+ ${g.rows.map(({ o, m }) => this.row(o, m, now))}
521
+ </ul>
522
+ </section>`
523
+ )}
524
+ `;
525
+ }
526
+ row(o, m, now) {
527
+ const title = o.title ?? m.title;
528
+ const time = `${this.clock(o.start)} - ${this.clock(o.end)}`;
529
+ const joinable = m.live || o.start - now <= JOIN_WINDOW_MS && o.end > now;
530
+ return html`<li>
531
+ <button
532
+ type="button"
533
+ class="row"
534
+ data-live=${String(m.live)}
535
+ aria-label=${`${title}, ${time}`}
536
+ @click=${() => this.fire("office-agenda-open", { meetingId: m.id, start: o.start })}
537
+ >
538
+ <span class="time">${time}</span>
539
+ <span class="title">${title}</span>
540
+ <span class="who">${m.mine ? "You organized" : m.organizerName}</span>
541
+ </button>
542
+ ${joinable ? html`<button
543
+ type="button"
544
+ class="join"
545
+ aria-label=${`Join ${title}`}
546
+ @click=${() => this.fire("office-agenda-join", { meetingId: m.id })}
547
+ >
548
+ ${m.live ? "Join now" : "Join"}
549
+ </button>` : ""}
550
+ </li>`;
551
+ }
552
+ clock(ms) {
553
+ return new Date(ms).toLocaleTimeString([], {
554
+ hour: "numeric",
555
+ minute: "2-digit",
556
+ timeZone: this.zone
557
+ });
558
+ }
559
+ };
560
+ var defineCalendarAgenda = definer(
561
+ "office-ui-calendar-agenda",
562
+ () => OfficeUiCalendarAgenda
563
+ );
564
+
565
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/calendar-view.css?raw
566
+ var calendar_view_default = ":host {\n --_hour: 4em;\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n background: var(--office-background);\n}\n.bar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-2);\n padding: var(--office-space-3) var(--office-space-4);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.range {\n flex: 1 1 12em;\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\nbutton {\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\nbutton:hover {\n background: var(--office-selected);\n}\n.step {\n display: grid;\n place-items: center;\n padding: var(--office-space-2);\n}\n.step.next .modes {\n display: inline-flex;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n overflow: hidden;\n}\n.modes button {\n border: 0;\n border-radius: 0;\n}\n.modes button[aria-pressed='true'] {\n background: var(--office-teams-brand-subtle);\n color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\n.new {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-2);\n border-color: transparent;\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\n.new:hover {\n background: var(--office-teams-brand-hover);\n}\n.step.next svg {\n transform: scaleX(-1);\n}\n\n.scroller {\n flex: 1;\n min-height: 0;\n overflow: auto;\n}\n.grid {\n display: grid;\n grid-template-columns: 4em repeat(var(--cols), minmax(0, 1fr));\n grid-template-rows: auto auto;\n min-width: 0;\n}\n.corner,\n.head {\n position: sticky;\n inset-block-start: 0;\n z-index: 3;\n background: var(--office-background);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.head {\n display: grid;\n justify-items: center;\n padding: var(--office-space-2) 0;\n border-inline-start: var(--office-border-width) solid var(--office-teams-divider);\n}\n.dow {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n}\n.dom {\n display: grid;\n place-items: center;\n min-width: 1.8em;\n height: 1.8em;\n border-radius: var(--office-radius-full);\n font-size: var(--office-font-size-lg);\n}\n.head[data-today='true'] .dom {\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\n.hours {\n display: grid;\n grid-template-rows: repeat(24, var(--_hour));\n}\n.hours span {\n white-space: nowrap;\n padding-inline-end: var(--office-space-2);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n text-align: end;\n transform: translateY(-0.6em);\n}\n.column {\n position: relative;\n height: calc(24 * var(--_hour));\n border-inline-start: var(--office-border-width) solid var(--office-teams-divider);\n background-image: linear-gradient(\n to bottom,\n var(--office-teams-divider) var(--office-border-width),\n transparent var(--office-border-width)\n );\n background-size: 100% var(--_hour);\n}\n.column[data-today='true'] {\n background-color: var(--office-teams-brand-subtle);\n}\n.event {\n position: absolute;\n inset-block-start: calc(var(--top) * 1%);\n inset-inline-start: calc(var(--col) / var(--span) * 100%);\n box-sizing: border-box;\n width: calc(100% / var(--span));\n height: calc(var(--height) * 1%);\n min-height: 1.6em;\n display: grid;\n align-content: start;\n padding: var(--office-space-1) var(--office-space-2);\n overflow: hidden;\n border: var(--office-border-width) solid var(--office-teams-brand);\n border-inline-start-width: var(--office-border-width-thick);\n border-radius: var(--office-radius);\n background: var(--office-popover);\n text-align: start;\n z-index: 1;\n}\n.event[data-mine='true'] {\n background: var(--office-teams-bubble-own);\n}\n.event[data-live='true'] {\n border-color: var(--office-call-live);\n}\n.event-title {\n overflow: hidden;\n font-weight: var(--office-font-weight-bold);\n font-size: var(--office-font-size-sm);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.event[data-short='true'] {\n align-content: center;\n}\n.event[data-short='true'] .event-time {\n display: none;\n}\n.event-time {\n overflow: hidden;\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.now {\n position: absolute;\n inset-block-start: calc(var(--top) * 1%);\n inset-inline: 0;\n z-index: 2;\n height: var(--office-border-width-thick);\n background: var(--office-danger);\n pointer-events: none;\n}\n.now::before {\n content: '';\n position: absolute;\n inset-inline-start: calc(-1 * var(--office-space-1));\n inset-block-start: calc(-1 * var(--office-space-1));\n width: var(--office-space-2);\n height: var(--office-space-2);\n border-radius: var(--office-radius-full);\n background: var(--office-danger);\n}\n@media (max-width: 40em) {\n :host {\n --_hour: 4em;\n }\n .grid {\n grid-template-columns: 3.8em repeat(var(--cols), minmax(0, 1fr));\n }\n .bar button {\n min-height: 2.75em;\n }\n .modes button {\n padding-inline: var(--office-space-2);\n }\n}\n";
567
+
568
+ // src/teams/calendar-view.ts
569
+ var MODES = [
570
+ { id: "workweek", label: "Work week" },
571
+ { id: "week", label: "Week" },
572
+ { id: "day", label: "Day" }
573
+ ];
574
+ var SLOT_MINUTES = 30;
575
+ var FIRST_VISIBLE_HOUR = 8;
576
+ var OfficeUiCalendarView = class extends TeamsElement {
577
+ static styles = withStyles(calendar_view_default);
578
+ static properties = {
579
+ meetings: { attribute: false },
580
+ zone: { type: String },
581
+ now: { type: Number },
582
+ mode: { type: String, reflect: true },
583
+ anchor: { state: true },
584
+ tick: { state: true }
585
+ };
586
+ timer;
587
+ scrolled = false;
588
+ resize;
589
+ constructor() {
590
+ super();
591
+ this.meetings = [];
592
+ this.zone = localZone();
593
+ this.now = 0;
594
+ this.mode = "workweek";
595
+ this.anchor = 0;
596
+ this.tick = 0;
597
+ }
598
+ clock() {
599
+ return this.now || Date.now();
600
+ }
601
+ connectedCallback() {
602
+ super.connectedCallback();
603
+ this.setAttribute("role", "region");
604
+ this.setAttribute("aria-label", "Calendar");
605
+ if (!this.anchor) this.anchor = this.clock();
606
+ if (globalThis.matchMedia?.("(max-width: 40em)").matches && this.mode === "workweek")
607
+ this.mode = "day";
608
+ this.timer = setInterval(() => this.tick = this.clock(), 6e4);
609
+ if (typeof ResizeObserver !== "undefined") {
610
+ this.resize = new ResizeObserver(() => this.scrollToWorkday());
611
+ this.resize.observe(this);
612
+ }
613
+ }
614
+ disconnectedCallback() {
615
+ clearInterval(this.timer);
616
+ this.resize?.disconnect();
617
+ super.disconnectedCallback();
618
+ }
619
+ updated() {
620
+ this.scrollToWorkday();
621
+ }
622
+ /** Start the day at 8:00, once the grid has a size. */
623
+ scrollToWorkday() {
624
+ const scroller = this.renderRoot.querySelector(".scroller");
625
+ if (this.scrolled || !scroller || scroller.clientHeight === 0) return;
626
+ scroller.scrollTop = scroller.scrollHeight / 24 * FIRST_VISIBLE_HOUR;
627
+ this.scrolled = true;
628
+ }
629
+ willUpdate(changed) {
630
+ if (changed.has("now") && this.now && changed.get("now") === 0) this.anchor = this.now;
631
+ }
632
+ setMode(mode) {
633
+ this.mode = mode;
634
+ this.fire("office-calendar-mode", { mode });
635
+ }
636
+ range(days) {
637
+ const first = days[0];
638
+ const last = days[days.length - 1];
639
+ const fmt = (opts, ms) => new Intl.DateTimeFormat([], { ...opts, timeZone: this.zone }).format(ms);
640
+ if (days.length === 1)
641
+ return fmt({ weekday: "long", month: "long", day: "numeric", year: "numeric" }, first.start);
642
+ return new Intl.DateTimeFormat([], {
643
+ month: "long",
644
+ day: "numeric",
645
+ year: "numeric",
646
+ timeZone: this.zone
647
+ }).formatRange(first.start, last.start);
648
+ }
649
+ newAt(day, event) {
650
+ const column = event.currentTarget;
651
+ const box = column.getBoundingClientRect();
652
+ const fraction = box.height ? (event.clientY - box.top) / box.height : 0;
653
+ const minute = Math.floor(Math.max(0, fraction) * 1440 / SLOT_MINUTES) * SLOT_MINUTES;
654
+ const start = zonedToUtc(
655
+ { year: day.year, month: day.month, day: day.day, hour: 0, minute },
656
+ this.zone
657
+ );
658
+ this.fire("office-calendar-new", { start });
659
+ }
660
+ render() {
661
+ const now = this.clock();
662
+ const days = viewDays(this.anchor || now, this.mode, this.zone);
663
+ const todayStart = startOfDay(now, this.zone);
664
+ const nowParts = zonedParts(now, this.zone);
665
+ return html`
666
+ <div class="bar">
667
+ <h2 class="range" aria-live="polite">${this.range(days)}</h2>
668
+ <button type="button" class="today" @click=${() => this.anchor = this.clock()}>
669
+ Today
670
+ </button>
671
+ <button
672
+ type="button"
673
+ class="step"
674
+ aria-label=${this.mode === "day" ? "Previous day" : "Previous week"}
675
+ @click=${() => this.anchor = shiftAnchor(this.anchor || now, this.mode, -1, this.zone)}
676
+ >
677
+ ${icon("back")}
678
+ </button>
679
+ <button
680
+ type="button"
681
+ class="step next"
682
+ aria-label=${this.mode === "day" ? "Next day" : "Next week"}
683
+ @click=${() => this.anchor = shiftAnchor(this.anchor || now, this.mode, 1, this.zone)}
684
+ >
685
+ ${icon("back")}
686
+ </button>
687
+ <div class="modes" role="group" aria-label="Calendar view">
688
+ ${MODES.map(
689
+ (m) => html`<button
690
+ type="button"
691
+ aria-pressed=${String(this.mode === m.id)}
692
+ @click=${() => this.setMode(m.id)}
693
+ >
694
+ ${m.label}
695
+ </button>`
696
+ )}
697
+ </div>
698
+ <button
699
+ type="button"
700
+ class="new"
701
+ @click=${() => this.fire("office-calendar-new", { start: 0 })}
702
+ >
703
+ ${icon("plus")} New meeting
704
+ </button>
705
+ </div>
706
+ <div class="scroller">
707
+ <div class="grid" style=${`--cols:${days.length}`}>
708
+ <div class="corner"></div>
709
+ ${days.map((d) => this.dayHead(d, d.start === todayStart))}
710
+ <div class="hours" aria-hidden="true">
711
+ ${Array.from({ length: 24 }, (_, h) => html`<span>${this.hourLabel(h)}</span>`)}
712
+ </div>
713
+ ${days.map((d) => this.column(d, d.start === todayStart, nowParts.hour * 60 + nowParts.minute))}
714
+ </div>
715
+ </div>
716
+ `;
717
+ }
718
+ hourLabel(hour) {
719
+ if (hour === 0) return "";
720
+ return new Date(Date.UTC(2e3, 0, 1, hour)).toLocaleTimeString([], {
721
+ hour: "numeric",
722
+ timeZone: "UTC"
723
+ });
724
+ }
725
+ dayHead(day, today) {
726
+ const name = new Intl.DateTimeFormat([], { weekday: "short", timeZone: this.zone }).format(
727
+ day.start
728
+ );
729
+ return html`<div class="head" data-today=${String(today)}>
730
+ <span class="dow">${name}</span>
731
+ <span class="dom">${day.day}</span>
732
+ </div>`;
733
+ }
734
+ column(day, today, nowMinute) {
735
+ const occurrences = this.meetings.flatMap(
736
+ (m) => occurrencesBetween(m, day.start, day.end).map((o) => ({ o, m }))
737
+ );
738
+ const placed = layoutDay(
739
+ occurrences.map((x) => x.o),
740
+ day,
741
+ this.zone
742
+ );
743
+ const byKey = new Map(occurrences.map((x) => [x.o.key, x.m]));
744
+ const label = new Intl.DateTimeFormat([], {
745
+ weekday: "long",
746
+ month: "long",
747
+ day: "numeric",
748
+ timeZone: this.zone
749
+ }).format(day.start);
750
+ return html`<div
751
+ class="column"
752
+ role="group"
753
+ aria-label=${label}
754
+ data-today=${String(today)}
755
+ @click=${(e) => {
756
+ if (e.target === e.currentTarget) this.newAt(day, e);
757
+ }}
758
+ >
759
+ ${placed.map((p) => {
760
+ const meeting = byKey.get(p.occurrence.key);
761
+ const title = p.occurrence.title ?? meeting.title;
762
+ const time = `${this.clockText(p.occurrence.start)} - ${this.clockText(p.occurrence.end)}`;
763
+ return html`<button
764
+ type="button"
765
+ class="event"
766
+ data-live=${String(meeting.live)}
767
+ data-mine=${String(meeting.mine)}
768
+ data-short=${String(p.endMinute - p.startMinute <= 30)}
769
+ aria-label=${`${title}, ${time}`}
770
+ style=${`--top:${p.startMinute / 1440 * 100};--height:${(p.endMinute - p.startMinute) / 1440 * 100};--col:${p.column};--span:${p.columns}`}
771
+ @click=${(e) => {
772
+ e.stopPropagation();
773
+ this.fire("office-calendar-open", {
774
+ meetingId: meeting.id,
775
+ start: p.occurrence.start
776
+ });
777
+ }}
778
+ >
779
+ <span class="event-title">${title}</span>
780
+ <span class="event-time">${time}</span>
781
+ </button>`;
782
+ })}
783
+ ${today ? html`<span
784
+ class="now"
785
+ style=${`--top:${nowMinute / 1440 * 100}`}
786
+ aria-hidden="true"
787
+ ></span>` : nothing}
788
+ </div>`;
789
+ }
790
+ clockText(ms) {
791
+ return new Date(ms).toLocaleTimeString([], {
792
+ hour: "numeric",
793
+ minute: "2-digit",
794
+ timeZone: this.zone
795
+ });
796
+ }
797
+ };
798
+ var defineCalendarView = definer("office-ui-calendar-view", () => OfficeUiCalendarView);
799
+
269
800
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/call-grid.css?raw
270
801
  var call_grid_default = ":host {\n display: block;\n min-height: 0;\n background: var(--office-call-background);\n color: var(--office-call-foreground);\n}\n.empty {\n display: grid;\n place-items: center;\n height: 100%;\n margin: 0;\n color: var(--office-call-muted-foreground);\n}\n.grid {\n display: grid;\n gap: var(--office-space-2);\n height: 100%;\n padding: var(--office-space-2);\n grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));\n grid-auto-rows: minmax(0, 1fr);\n}\n.grid[data-spotlight='true'] {\n grid-template-columns: minmax(0, 4fr) minmax(0, 1fr);\n grid-template-rows: repeat(var(--rows, 1), minmax(0, 1fr));\n}\n.grid[data-spotlight='true'] .tile[data-main='true'] {\n grid-row: 1 / span var(--rows, 1);\n}\n\n.tile {\n position: relative;\n display: grid;\n place-items: center;\n min-height: 6em;\n overflow: hidden;\n border-radius: var(--office-radius-lg);\n background: var(--office-call-tile);\n}\nvideo {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n background: var(--office-call-background);\n}\n.tile[data-screen='true'] video {\n object-fit: contain;\n}\n.tile[data-self='true'] video {\n transform: scaleX(-1);\n}\n.tile[data-self='true'][data-screen='true'] video {\n transform: none;\n}\n\n.name {\n position: absolute;\n inset-inline-start: var(--office-space-2);\n inset-block-end: var(--office-space-2);\n display: flex;\n align-items: center;\n gap: var(--office-space-1-5);\n max-width: calc(100% - 2 * var(--office-space-2));\n padding: var(--office-space-0) var(--office-space-2);\n border-radius: var(--office-radius-md);\n background: var(--office-call-scrim);\n font-size: var(--office-font-size-sm);\n}\n.name span:last-child {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.muted {\n display: grid;\n color: var(--office-call-danger);\n}\n.hand {\n position: absolute;\n inset-block-start: var(--office-space-2);\n inset-inline-end: var(--office-space-2);\n font-size: 1.5em;\n}\n.status {\n position: absolute;\n inset-block-start: var(--office-space-2);\n inset-inline-start: var(--office-space-2);\n padding: 0 var(--office-space-2);\n border-radius: var(--office-radius-md);\n background: var(--office-call-scrim);\n font-size: var(--office-font-size-xs);\n}\n";
271
802
 
@@ -399,7 +930,7 @@ var OfficeUiChannelList = class extends TeamsElement {
399
930
  var defineChannelList = definer("office-ui-channel-list", () => OfficeUiChannelList);
400
931
 
401
932
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/chat-composer.css?raw
402
- var chat_composer_default = ":host {\n display: block;\n min-width: 0;\n flex: none;\n padding: 0 var(--office-space-5) var(--office-space-4);\n}\n.typing {\n min-height: 1.4em;\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.box {\n position: relative;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-block-end: var(--office-border-width-thick) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-field-background);\n}\n.box:focus-within {\n border-block-end-color: var(--office-teams-brand);\n}\n.banner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-1) var(--office-space-3);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n background: var(--office-teams-panel);\n font-size: var(--office-font-size-sm);\n}\ntextarea {\n display: block;\n width: 100%;\n min-height: 3.2em;\n max-height: 12em;\n padding: var(--office-space-3);\n border: 0;\n outline: 0;\n resize: none;\n background: transparent;\n color: inherit;\n font: inherit;\n overflow-y: auto;\n line-height: 1.5;\n}\n.bar {\n display: flex;\n align-items: center;\n gap: var(--office-space-1);\n padding: 0 var(--office-space-2) var(--office-space-2);\n}\n.spacer {\n flex: 1;\n}\n.bar button,\n.banner button,\n.pending button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--office-space-1);\n min-width: 2.2em;\n height: 2.2em;\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n color: var(--office-teams-text-subtle);\n}\n.bar button:hover:not(:disabled),\n.banner button:hover {\n background: var(--office-selected);\n color: var(--office-foreground);\n}\n.bar button:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.bar .send:not(:disabled) {\n color: var(--office-teams-brand);\n}\n.pending {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-1);\n margin: 0;\n padding: 0 var(--office-space-3);\n list-style: none;\n}\n.pending button {\n padding: 0 var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n background: var(--office-teams-panel);\n font-size: var(--office-font-size-sm);\n color: var(--office-foreground);\n}\n.emoji {\n position: relative;\n}\n.picker {\n position: absolute;\n inset-block-end: calc(100% + var(--office-space-1));\n inset-inline-start: 0;\n z-index: 2;\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: var(--office-space-0);\n padding: var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\n.picker button {\n font-size: 1.3em;\n}\n\ntextarea:focus-visible {\n outline: none;\n}\n.box:focus-within {\n border-color: var(--office-teams-brand);\n}\n.pending {\n max-height: 7em;\n overflow-y: auto;\n padding-block-end: var(--office-space-2);\n}\n.pending li {\n min-width: 0;\n max-width: 100%;\n}\n.pending button {\n max-width: 100%;\n height: auto;\n min-height: 2.4em;\n}\n.file-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n@media (max-width: 600px) {\n :host {\n padding: 0 var(--office-space-2) var(--office-space-2);\n }\n textarea {\n font-size: var(--office-font-size-touch);\n min-height: 3em;\n }\n}\n@media (pointer: coarse) {\n .bar button,\n .banner button,\n .pending button,\n .picker button {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n";
933
+ var chat_composer_default = ":host {\n display: block;\n min-width: 0;\n flex: none;\n padding: 0 var(--office-space-5) var(--office-space-4);\n}\n.typing {\n min-height: 1.4em;\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.box {\n position: relative;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-block-end: var(--office-border-width-thick) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-field-background);\n}\n.box:focus-within {\n border-block-end-color: var(--office-teams-brand);\n}\n.banner {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-1) var(--office-space-3);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n background: var(--office-teams-panel);\n font-size: var(--office-font-size-sm);\n}\ntextarea {\n display: block;\n width: 100%;\n min-height: 3.2em;\n max-height: 12em;\n padding: var(--office-space-3);\n border: 0;\n outline: 0;\n resize: none;\n background: transparent;\n color: inherit;\n font: inherit;\n overflow-y: auto;\n line-height: 1.5;\n}\n.bar {\n display: flex;\n align-items: center;\n gap: var(--office-space-1);\n padding: 0 var(--office-space-2) var(--office-space-2);\n}\n.spacer {\n flex: 1;\n}\n.bar button,\n.banner button,\n.pending button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--office-space-1);\n min-width: 2.2em;\n height: 2.2em;\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n color: var(--office-teams-text-subtle);\n}\n.bar button:hover:not(:disabled),\n.banner button:hover {\n background: var(--office-selected);\n color: var(--office-foreground);\n}\n.bar button:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.bar .send:not(:disabled) {\n color: var(--office-teams-brand);\n}\n.pending {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-1);\n margin: 0;\n padding: 0 var(--office-space-3);\n list-style: none;\n}\n.pending button {\n padding: 0 var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n background: var(--office-teams-panel);\n font-size: var(--office-font-size-sm);\n color: var(--office-foreground);\n}\n.emoji {\n position: relative;\n}\n.picker {\n position: absolute;\n inset-block-end: calc(100% + var(--office-space-1));\n inset-inline-start: 0;\n z-index: 2;\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: var(--office-space-0);\n padding: var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\n.picker button {\n font-size: 1.3em;\n}\n\ntextarea:focus-visible {\n outline: none;\n}\n.box:focus-within {\n border-color: var(--office-teams-brand);\n}\n.pending {\n max-height: 7em;\n overflow-y: auto;\n padding-block-end: var(--office-space-2);\n}\n.pending li {\n min-width: 0;\n max-width: 100%;\n}\n.pending button {\n max-width: 100%;\n height: auto;\n min-height: 2.4em;\n}\n.file-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n@media (max-width: 600px) {\n :host {\n padding: 0 var(--office-space-2) var(--office-space-2);\n }\n textarea {\n font-size: var(--office-font-size-touch);\n min-height: 3em;\n }\n}\n@media (pointer: coarse) {\n .bar button,\n .banner button,\n .pending button,\n .picker button {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n\n/* The @ picker: a listbox of people above the compose box. */\n.mentions {\n position: absolute;\n inset-block-end: calc(100% + var(--office-space-1));\n inset-inline: 0;\n z-index: 3;\n max-height: 16em;\n margin: 0;\n padding: var(--office-space-1);\n overflow-y: auto;\n list-style: none;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\n.mentions li {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n min-height: 2.6em;\n padding: var(--office-space-1) var(--office-space-3);\n border-radius: var(--office-radius);\n cursor: pointer;\n}\n.mentions li[aria-selected='true'],\n.mentions li:hover {\n background: var(--office-selected);\n}\n.mentions .name {\n font-weight: 600;\n}\n.mentions .hint {\n margin-inline-start: auto;\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.mentions .everyone {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 2em;\n height: 2em;\n border-radius: 50%;\n background: var(--office-teams-brand-subtle);\n color: var(--office-teams-brand);\n font-weight: 700;\n}\n@media (pointer: coarse) {\n .mentions li {\n min-height: var(--office-touch-target, 44px);\n }\n}\n";
403
934
  var EMOJI = [
404
935
  "\u{1F600}",
405
936
  "\u{1F604}",
@@ -434,8 +965,12 @@ var OfficeUiChatComposer = class extends TeamsElement {
434
965
  value: { type: String },
435
966
  files: { state: true },
436
967
  missingFiles: { attribute: false },
437
- emojiOpen: { state: true }
968
+ emojiOpen: { state: true },
969
+ mentionTargets: { attribute: false },
970
+ picker: { state: true },
971
+ pickerIndex: { state: true }
438
972
  };
973
+ inserted = [];
439
974
  lastTyping = 0;
440
975
  constructor() {
441
976
  super();
@@ -448,6 +983,9 @@ var OfficeUiChatComposer = class extends TeamsElement {
448
983
  this.files = [];
449
984
  this.missingFiles = [];
450
985
  this.emojiOpen = false;
986
+ this.mentionTargets = [];
987
+ this.picker = null;
988
+ this.pickerIndex = 0;
451
989
  }
452
990
  focusMessage() {
453
991
  void this.updateComplete.then(
@@ -472,15 +1010,19 @@ var OfficeUiChatComposer = class extends TeamsElement {
472
1010
  }
473
1011
  submit() {
474
1012
  if (!this.canSend) return;
475
- const { value: text, files } = this;
1013
+ const { value: text, files, inserted: mentions } = this;
476
1014
  this.value = "";
477
1015
  this.files = [];
1016
+ this.inserted = [];
478
1017
  this.emojiOpen = false;
479
- this.fire("office-chat-send", { text, files });
1018
+ this.picker = null;
1019
+ const detail = { text, files, ...mentions.length ? { mentions } : {} };
1020
+ this.fire("office-chat-send", detail);
480
1021
  this.focusMessage();
481
1022
  }
482
1023
  onInput(event) {
483
1024
  this.value = event.target.value;
1025
+ this.syncPicker();
484
1026
  this.saveDraft();
485
1027
  const now = Date.now();
486
1028
  if (now - this.lastTyping > 2e3) {
@@ -488,7 +1030,51 @@ var OfficeUiChatComposer = class extends TeamsElement {
488
1030
  this.fire("office-chat-typing", {});
489
1031
  }
490
1032
  }
1033
+ /** Open the picker while the caret sits in an `@query` that somebody matches. */
1034
+ syncPicker() {
1035
+ const area = this.renderRoot.querySelector("textarea");
1036
+ const query = area ? mentionQueryAt(this.value, area.selectionStart) : null;
1037
+ if (!query || filterMentionTargets(this.mentionTargets, query.query).length === 0) {
1038
+ this.picker = null;
1039
+ return;
1040
+ }
1041
+ if (this.picker?.start !== query.start || this.picker.query !== query.query)
1042
+ this.pickerIndex = 0;
1043
+ this.picker = query;
1044
+ }
1045
+ pickerOptions() {
1046
+ return this.picker ? filterMentionTargets(this.mentionTargets, this.picker.query) : [];
1047
+ }
1048
+ chooseMention(target) {
1049
+ const area = this.renderRoot.querySelector("textarea");
1050
+ const picker = this.picker;
1051
+ if (!area || !picker) return;
1052
+ const next = insertMention(this.value, picker, area.selectionStart, target);
1053
+ this.value = next.text;
1054
+ this.inserted = [...this.inserted, target];
1055
+ this.picker = null;
1056
+ this.saveDraft();
1057
+ void this.updateComplete.then(() => {
1058
+ area.focus({ preventScroll: true });
1059
+ area.setSelectionRange(next.caret, next.caret);
1060
+ });
1061
+ }
1062
+ /** Picker keys; true when the key was used. */
1063
+ pickerKey(event) {
1064
+ const options = this.pickerOptions();
1065
+ if (!this.picker || options.length === 0) return false;
1066
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1067
+ const step = event.key === "ArrowDown" ? 1 : -1;
1068
+ this.pickerIndex = (this.pickerIndex + step + options.length) % options.length;
1069
+ } else if (event.key === "Enter" && !event.shiftKey && !event.isComposing || event.key === "Tab") {
1070
+ this.chooseMention(options[Math.min(this.pickerIndex, options.length - 1)]);
1071
+ } else if (event.key === "Escape") this.picker = null;
1072
+ else return false;
1073
+ event.preventDefault();
1074
+ return true;
1075
+ }
491
1076
  onKeydown(event) {
1077
+ if (this.pickerKey(event)) return;
492
1078
  if (event.key === "Escape" && this.emojiOpen) {
493
1079
  this.emojiOpen = false;
494
1080
  this.focusMessage();
@@ -527,6 +1113,34 @@ var OfficeUiChatComposer = class extends TeamsElement {
527
1113
  if (n.length === 0) return "";
528
1114
  return n.length === 1 ? `${n[0]} is typing\u2026` : `${n.join(", ")} are typing\u2026`;
529
1115
  }
1116
+ mentionList() {
1117
+ const options = this.pickerOptions();
1118
+ if (options.length === 0) return nothing;
1119
+ return html`<ul
1120
+ id="mention-list"
1121
+ class="mentions"
1122
+ role="listbox"
1123
+ aria-label="People to mention"
1124
+ @mousedown=${(event) => event.preventDefault()}
1125
+ >
1126
+ ${options.map(
1127
+ (target, index) => html`<li
1128
+ id=${`mention-option-${index}`}
1129
+ role="option"
1130
+ aria-selected=${String(index === this.pickerIndex)}
1131
+ @click=${() => this.chooseMention(target)}
1132
+ >
1133
+ ${target.kind === "user" ? html`<office-ui-avatar
1134
+ size="sm"
1135
+ name=${target.name}
1136
+ seed=${target.id}
1137
+ ></office-ui-avatar>` : html`<span class="everyone" aria-hidden="true">@</span>`}
1138
+ <span class="name">${target.name}</span>
1139
+ ${target.kind === "user" ? nothing : html`<span class="hint">${target.kind === "channel" ? "Notify everyone in this channel" : "Notify the whole team"}</span>`}
1140
+ </li>`
1141
+ )}
1142
+ </ul>`;
1143
+ }
530
1144
  render() {
531
1145
  const banner = this.editing ? "Editing message" : this.replyingTo !== null ? `Replying to ${this.replyingTo}` : "";
532
1146
  return html`
@@ -576,9 +1190,13 @@ var OfficeUiChatComposer = class extends TeamsElement {
576
1190
  ${icon("close")}
577
1191
  </button>
578
1192
  </div>` : nothing}
1193
+ ${this.mentionList()}
579
1194
  <textarea
580
1195
  rows="1"
581
1196
  aria-label="Message"
1197
+ aria-haspopup="listbox"
1198
+ aria-controls=${this.pickerOptions().length ? "mention-list" : nothing}
1199
+ aria-activedescendant=${this.pickerOptions().length ? `mention-option-${this.pickerIndex}` : nothing}
582
1200
  .value=${live(this.value)}
583
1201
  placeholder=${this.placeholder}
584
1202
  ?disabled=${this.disabled}
@@ -590,6 +1208,11 @@ var OfficeUiChatComposer = class extends TeamsElement {
590
1208
  }
591
1209
  }}
592
1210
  @keydown=${this.onKeydown}
1211
+ @keyup=${(event) => {
1212
+ if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) this.syncPicker();
1213
+ }}
1214
+ @click=${this.syncPicker}
1215
+ @blur=${() => this.picker = null}
593
1216
  ></textarea>
594
1217
  ${this.files.length ? html`<ul class="pending">
595
1218
  ${this.files.map(
@@ -672,7 +1295,7 @@ var OfficeUiChatComposer = class extends TeamsElement {
672
1295
  var defineChatComposer = definer("office-ui-chat-composer", () => OfficeUiChatComposer);
673
1296
 
674
1297
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/chat-list.css?raw
675
- var chat_list_default = ":host {\n display: block;\n min-height: 0;\n overflow: auto;\n padding: var(--office-space-2) var(--office-space-5);\n}\n.thread-link {\n align-self: flex-start;\n font: inherit;\n color: var(--office-foreground);\n background: var(--office-background);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n padding: var(--office-space-2);\n cursor: pointer;\n}\n.empty {\n margin: var(--office-space-6) 0;\n color: var(--office-teams-text-subtle);\n text-align: center;\n}\n\n.day {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n margin: var(--office-space-4) 0 var(--office-space-2);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.day::before,\n.day::after {\n content: '';\n flex: 1;\n height: var(--office-border-width);\n background: var(--office-teams-divider);\n}\n\n.msg {\n position: relative;\n display: grid;\n grid-template-columns: 2.3em minmax(0, 1fr);\n column-gap: var(--office-space-3);\n padding: var(--office-space-0) 0;\n}\n.msg[data-grouped='false'] {\n margin-top: var(--office-teams-message-gap, var(--office-space-3));\n}\n.gutter {\n padding-top: var(--office-space-1);\n}\nheader {\n display: flex;\n align-items: baseline;\n gap: var(--office-space-2);\n margin-bottom: var(--office-space-0);\n}\n.author {\n font-weight: var(--office-font-weight-bold);\n}\ntime,\n.edited,\n.file-size {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n\n.bubble {\n display: inline-block;\n max-width: min(100%, 44em);\n padding: var(--office-teams-message-padding, var(--office-space-2) var(--office-space-3));\n border-radius: var(--office-radius-md);\n background: var(--office-teams-bubble);\n line-height: var(--office-line-height);\n overflow-wrap: anywhere;\n}\n.text {\n white-space: pre-wrap;\n}\n.msg[data-own='true'] .bubble {\n background: var(--office-teams-bubble-own);\n}\n.deleted {\n color: var(--office-teams-text-subtle);\n font-style: italic;\n}\nblockquote {\n margin: 0 0 var(--office-space-1);\n padding-inline-start: var(--office-space-2);\n border-inline-start: var(--office-border-width-thick) solid var(--office-teams-brand);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n white-space: normal;\n}\nblockquote strong {\n display: block;\n color: var(--office-foreground);\n}\n\n.files {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-2);\n margin-top: var(--office-space-2);\n white-space: normal;\n}\n.file {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n min-width: 14em;\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n text-align: start;\n}\n.file:hover {\n border-color: var(--office-teams-brand);\n}\n.badge {\n display: grid;\n place-items: center;\n width: 2.2em;\n height: 2.2em;\n border-radius: var(--office-radius);\n background: var(--office-file-other);\n color: var(--office-call-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.badge[data-kind='docx'] {\n background: var(--office-file-docx);\n}\n.badge[data-kind='xlsx'] {\n background: var(--office-file-xlsx);\n}\n.badge[data-kind='pptx'] {\n background: var(--office-file-pptx);\n}\n.badge[data-kind='vsdx'] {\n background: var(--office-file-vsdx);\n}\n.meta {\n display: grid;\n}\n\n.reactions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-1);\n margin-top: var(--office-space-1);\n}\n.pill {\n display: inline-flex;\n gap: var(--office-space-1);\n padding: 0 var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-full);\n background: var(--office-background);\n font-size: var(--office-font-size-sm);\n}\n.pill[aria-pressed='true'] {\n border-color: var(--office-teams-brand);\n background: var(--office-teams-brand-subtle);\n}\n\n.toolbar {\n position: absolute;\n inset-block-start: calc(-1 * var(--office-space-3));\n inset-inline-end: 0;\n display: none;\n gap: var(--office-space-0);\n padding: var(--office-space-0);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\n.msg:hover .toolbar,\n.msg:focus-within .toolbar {\n display: flex;\n}\n.toolbar button {\n display: grid;\n place-items: center;\n min-width: 2em;\n height: 2em;\n border: 0;\n border-radius: var(--office-radius-sm);\n background: transparent;\n}\n.toolbar button:hover {\n background: var(--office-selected);\n}\n\n.msg:focus {\n outline: none;\n}\n.pin-mark {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-1);\n color: var(--office-teams-brand);\n font-size: var(--office-font-size-xs);\n}\n.pin-mark svg {\n width: 1em;\n height: 1em;\n}\n@media (pointer: coarse) {\n .toolbar button {\n min-width: var(--office-touch-target, 44px);\n height: var(--office-touch-target, 44px);\n }\n}\n";
1298
+ var chat_list_default = ":host {\n display: block;\n min-height: 0;\n overflow: auto;\n padding: var(--office-space-2) var(--office-space-5);\n}\n.thread-link {\n align-self: flex-start;\n font: inherit;\n color: var(--office-foreground);\n background: var(--office-background);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n padding: var(--office-space-2);\n cursor: pointer;\n}\n.empty {\n margin: var(--office-space-6) 0;\n color: var(--office-teams-text-subtle);\n text-align: center;\n}\n\n.day {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n margin: var(--office-space-4) 0 var(--office-space-2);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.day::before,\n.day::after {\n content: '';\n flex: 1;\n height: var(--office-border-width);\n background: var(--office-teams-divider);\n}\n\n.msg {\n position: relative;\n display: grid;\n grid-template-columns: 2.3em minmax(0, 1fr);\n column-gap: var(--office-space-3);\n padding: var(--office-space-0) 0;\n}\n.msg[data-grouped='false'] {\n margin-top: var(--office-teams-message-gap, var(--office-space-3));\n}\n.gutter {\n padding-top: var(--office-space-1);\n}\nheader {\n display: flex;\n align-items: baseline;\n gap: var(--office-space-2);\n margin-bottom: var(--office-space-0);\n}\n.author {\n font-weight: var(--office-font-weight-bold);\n}\ntime,\n.edited,\n.file-size {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n\n.bubble {\n display: inline-block;\n max-width: min(100%, 44em);\n padding: var(--office-teams-message-padding, var(--office-space-2) var(--office-space-3));\n border-radius: var(--office-radius-md);\n background: var(--office-teams-bubble);\n line-height: var(--office-line-height);\n overflow-wrap: anywhere;\n}\n.text {\n white-space: pre-wrap;\n}\n.msg[data-own='true'] .bubble {\n background: var(--office-teams-bubble-own);\n}\n.deleted {\n color: var(--office-teams-text-subtle);\n font-style: italic;\n}\nblockquote {\n margin: 0 0 var(--office-space-1);\n padding-inline-start: var(--office-space-2);\n border-inline-start: var(--office-border-width-thick) solid var(--office-teams-brand);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n white-space: normal;\n}\nblockquote strong {\n display: block;\n color: var(--office-foreground);\n}\n\n.files {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-2);\n margin-top: var(--office-space-2);\n white-space: normal;\n}\n.file {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n min-width: 14em;\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n text-align: start;\n}\n.file:hover {\n border-color: var(--office-teams-brand);\n}\n.badge {\n display: grid;\n place-items: center;\n width: 2.2em;\n height: 2.2em;\n border-radius: var(--office-radius);\n background: var(--office-file-other);\n color: var(--office-call-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.badge[data-kind='docx'] {\n background: var(--office-file-docx);\n}\n.badge[data-kind='xlsx'] {\n background: var(--office-file-xlsx);\n}\n.badge[data-kind='pptx'] {\n background: var(--office-file-pptx);\n}\n.badge[data-kind='vsdx'] {\n background: var(--office-file-vsdx);\n}\n.meta {\n display: grid;\n}\n\n.reactions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-1);\n margin-top: var(--office-space-1);\n}\n.pill {\n display: inline-flex;\n gap: var(--office-space-1);\n padding: 0 var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-full);\n background: var(--office-background);\n font-size: var(--office-font-size-sm);\n}\n.pill[aria-pressed='true'] {\n border-color: var(--office-teams-brand);\n background: var(--office-teams-brand-subtle);\n}\n\n.toolbar {\n position: absolute;\n inset-block-start: calc(-1 * var(--office-space-3));\n inset-inline-end: 0;\n display: none;\n gap: var(--office-space-0);\n padding: var(--office-space-0);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\n.msg:hover .toolbar,\n.msg:focus-within .toolbar {\n display: flex;\n}\n.toolbar button {\n display: grid;\n place-items: center;\n min-width: 2em;\n height: 2em;\n border: 0;\n border-radius: var(--office-radius-sm);\n background: transparent;\n}\n.toolbar button:hover {\n background: var(--office-selected);\n}\n\n.msg:focus {\n outline: none;\n}\n.pin-mark {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-1);\n color: var(--office-teams-brand);\n font-size: var(--office-font-size-xs);\n}\n.pin-mark svg {\n width: 1em;\n height: 1em;\n}\n@media (pointer: coarse) {\n .toolbar button {\n min-width: var(--office-touch-target, 44px);\n height: var(--office-touch-target, 44px);\n }\n}\n\n/* @mentions: the name in the brand colour; a mention of you (or of everyone) gets a chip. */\n.mention {\n color: var(--office-teams-brand);\n font-weight: 600;\n}\n.mention[data-self='true'] {\n padding: 0 var(--office-space-1);\n border-radius: var(--office-radius);\n background: var(--office-teams-brand-subtle);\n}\n.msg[data-mentions-you='true'] .bubble {\n box-shadow: inset var(--office-border-width-thick) 0 0 var(--office-teams-brand);\n}\n\n/* A message opened from Activity or the pinned bar flashes once. */\n.msg[data-highlight='true'] .bubble {\n animation: flash 2.4s ease-out;\n}\n@keyframes flash {\n from {\n background: var(--office-teams-brand-subtle);\n outline: var(--office-border-width-thick) solid var(--office-teams-brand);\n }\n to {\n outline: var(--office-border-width-thick) solid transparent;\n }\n}\n\n.system {\n margin: var(--office-space-3) 0;\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n text-align: center;\n}\n.system:focus {\n outline: none;\n}\n\n.meeting-card {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto auto;\n align-items: center;\n gap: var(--office-space-2) var(--office-space-3);\n min-width: min(22em, 100%);\n margin-top: var(--office-space-2);\n padding: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-inline-start: var(--office-border-width-thick) solid var(--office-teams-brand);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n white-space: normal;\n}\n.meeting-card[data-gone='true'] {\n grid-template-columns: auto minmax(0, 1fr);\n color: var(--office-teams-text-subtle);\n}\n.meeting-icon {\n display: grid;\n place-items: center;\n width: 2.2em;\n height: 2.2em;\n border-radius: var(--office-radius);\n background: var(--office-teams-brand-subtle);\n color: var(--office-teams-brand);\n}\n.meeting-meta {\n display: grid;\n min-width: 0;\n}\n.meeting-title {\n font-weight: var(--office-font-weight-bold);\n}\n.meeting-time {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n}\n.meeting-open,\n.meeting-join {\n padding: var(--office-space-1) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n.meeting-join {\n border-color: transparent;\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\n.meeting-join:hover {\n background: var(--office-teams-brand-hover);\n}\n@media (max-width: 40em) {\n .meeting-card {\n grid-template-columns: auto minmax(0, 1fr);\n }\n .meeting-open,\n .meeting-join {\n grid-column: 1 / -1;\n min-height: 2.75em;\n }\n}\n";
676
1299
 
677
1300
  // src/teams/chat-list.ts
678
1301
  var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F389}"];
@@ -690,7 +1313,9 @@ var OfficeUiChatList = class extends TeamsElement {
690
1313
  messages: { attribute: false },
691
1314
  selfId: { type: String, attribute: "self-id" },
692
1315
  replyCounts: { attribute: false },
693
- pinnedIds: { attribute: false }
1316
+ pinnedIds: { attribute: false },
1317
+ highlightId: { type: String, attribute: "highlight-id" },
1318
+ meetings: { attribute: false }
694
1319
  };
695
1320
  stickToBottom = true;
696
1321
  constructor() {
@@ -699,6 +1324,8 @@ var OfficeUiChatList = class extends TeamsElement {
699
1324
  this.selfId = "";
700
1325
  this.replyCounts = {};
701
1326
  this.pinnedIds = [];
1327
+ this.highlightId = "";
1328
+ this.meetings = {};
702
1329
  }
703
1330
  connectedCallback() {
704
1331
  super.connectedCallback();
@@ -718,6 +1345,11 @@ var OfficeUiChatList = class extends TeamsElement {
718
1345
  const rows = [];
719
1346
  let previous;
720
1347
  for (const m of this.messages) {
1348
+ if (m.system && !m.deleted) {
1349
+ rows.push(html`<div class="system" data-message-id=${m.id} tabindex="-1">${m.text}</div>`);
1350
+ previous = void 0;
1351
+ continue;
1352
+ }
721
1353
  if (!previous || dayLabel(previous.ts) !== dayLabel(m.ts))
722
1354
  rows.push(html`<div class="day"><span>${dayLabel(m.ts)}</span></div>`);
723
1355
  const grouped = previous !== void 0 && previous.authorId === m.authorId && m.ts - previous.ts < GROUP_MS && !m.replyTo && dayLabel(previous.ts) === dayLabel(m.ts);
@@ -735,7 +1367,9 @@ var OfficeUiChatList = class extends TeamsElement {
735
1367
  tabindex="-1"
736
1368
  data-message-id=${m.id}
737
1369
  data-own=${String(own)}
1370
+ data-highlight=${String(m.id === this.highlightId)}
738
1371
  data-grouped=${String(grouped)}
1372
+ data-mentions-you=${String(!m.deleted && mentionsUser(m, this.selfId) && !own)}
739
1373
  >
740
1374
  <div class="gutter">
741
1375
  ${grouped ? nothing : html`<office-ui-avatar name=${m.authorName} seed=${m.authorId}></office-ui-avatar>`}
@@ -751,7 +1385,8 @@ var OfficeUiChatList = class extends TeamsElement {
751
1385
  <strong>${parent.authorName}</strong
752
1386
  >${parent.deleted ? "This message was deleted" : parent.text.slice(0, 120)}
753
1387
  </blockquote>` : nothing}
754
- ${m.deleted ? html`<span class="deleted">This message was deleted</span>` : html`<span class="text">${m.text}</span>${m.editedAt ? html` <span class="edited">(edited)</span>` : nothing}`}
1388
+ ${m.deleted ? html`<span class="deleted">This message was deleted</span>` : html`<span class="text">${this.body(m)}</span>${m.editedAt ? html` <span class="edited">(edited)</span>` : nothing}`}
1389
+ ${!m.deleted && m.meetingId ? this.meetingCard(m.meetingId) : nothing}
755
1390
  ${!m.deleted && m.attachments?.length ? this.files(m.attachments) : nothing}
756
1391
  ${!m.deleted && m.tabId ? html`<button class="thread-link" type="button" @click=${() => this.fire("office-chat-open-tab", { tabId: m.tabId })}>Open tab</button>` : nothing}
757
1392
  </div>
@@ -776,6 +1411,47 @@ var OfficeUiChatList = class extends TeamsElement {
776
1411
  </article>
777
1412
  `;
778
1413
  }
1414
+ /** Plain runs and highlighted mentions (the name without its `@`, as in Teams), with no whitespace between them. */
1415
+ body(m) {
1416
+ return mentionSegments(m.text, m.mentions).map(
1417
+ (segment) => segment.mention ? html`<span
1418
+ class="mention"
1419
+ data-self=${String(segment.mention.kind !== "user" || segment.mention.id === this.selfId)}
1420
+ >${segment.text.slice(1)}</span
1421
+ >` : segment.text
1422
+ );
1423
+ }
1424
+ meetingCard(meetingId) {
1425
+ const card = this.meetings[meetingId];
1426
+ if (!card)
1427
+ return html`<div class="meeting-card" data-gone="true">
1428
+ <span class="meeting-icon">${icon("calendar")}</span>
1429
+ <span class="meeting-meta">
1430
+ <span class="meeting-title">This meeting was canceled or is not in your calendar</span>
1431
+ </span>
1432
+ </div>`;
1433
+ return html`<div class="meeting-card">
1434
+ <span class="meeting-icon">${icon("calendar")}</span>
1435
+ <span class="meeting-meta">
1436
+ <span class="meeting-title">${card.title}</span>
1437
+ <span class="meeting-time">${formatWhen(card.start, card.end)}</span>
1438
+ </span>
1439
+ <button
1440
+ type="button"
1441
+ class="meeting-open"
1442
+ @click=${() => this.fire("office-chat-meeting", { meetingId, action: "open" })}
1443
+ >
1444
+ Details
1445
+ </button>
1446
+ <button
1447
+ type="button"
1448
+ class="meeting-join"
1449
+ @click=${() => this.fire("office-chat-meeting", { meetingId, action: "join" })}
1450
+ >
1451
+ ${card.live ? "Join now" : "Join"}
1452
+ </button>
1453
+ </div>`;
1454
+ }
779
1455
  toolbar(m, own) {
780
1456
  return html`
781
1457
  <div class="toolbar" role="toolbar" aria-label="Message actions">
@@ -855,6 +1531,161 @@ var OfficeUiChatList = class extends TeamsElement {
855
1531
  };
856
1532
  var defineChatList = definer("office-ui-chat-list", () => OfficeUiChatList);
857
1533
 
1534
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/chat-sidebar.css?raw
1535
+ var chat_sidebar_default = ":host {\n display: block;\n overflow: auto;\n}\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-3) var(--office-space-3) var(--office-space-2) var(--office-space-4);\n}\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\n.new {\n display: grid;\n place-items: center;\n width: 2.2em;\n height: 2.2em;\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n color: var(--office-teams-brand);\n}\n.new:hover {\n background: var(--office-selected);\n}\nul {\n margin: 0;\n padding: 0 var(--office-space-2);\n list-style: none;\n}\n.empty {\n margin: var(--office-space-4);\n color: var(--office-teams-text-subtle);\n}\n.row {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n width: 100%;\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n color: var(--office-teams-text-subtle);\n text-align: start;\n}\n.row:hover {\n background: var(--office-selected);\n}\n.row[aria-current='true'] {\n background: var(--office-teams-brand-subtle);\n color: var(--office-foreground);\n}\n.row[aria-current='true']::before {\n content: '';\n position: absolute;\n inset-inline-start: 0;\n inset-block: 20%;\n width: var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n}\n.group {\n display: grid;\n place-items: center;\n flex: none;\n width: 2.3em;\n height: 2.3em;\n border-radius: var(--office-radius-full);\n background: var(--office-teams-bubble);\n color: var(--office-teams-brand);\n}\n.text {\n display: grid;\n flex: 1;\n min-width: 0;\n}\n.line {\n display: flex;\n align-items: baseline;\n gap: var(--office-space-2);\n}\n.title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n color: var(--office-foreground);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\ntime {\n font-size: var(--office-font-size-xs);\n}\n.preview {\n overflow: hidden;\n font-size: var(--office-font-size-sm);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.row[data-unread='true'] .title,\n.row[data-unread='true'] .preview {\n color: var(--office-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.live {\n display: grid;\n color: var(--office-call-live);\n}\n.count {\n min-width: 1.6em;\n padding: 0 var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: var(--office-font-size-xs);\n line-height: 1.6;\n text-align: center;\n}\n@media (pointer: coarse) {\n .new,\n .row {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n";
1536
+
1537
+ // src/teams/chat-sidebar.ts
1538
+ function stamp(ts) {
1539
+ if (!ts) return "";
1540
+ return dayLabel(ts) === "Today" ? formatTime(ts) : new Date(ts).toLocaleDateString([], { month: "short", day: "numeric" });
1541
+ }
1542
+ var OfficeUiChatSidebar = class extends TeamsElement {
1543
+ static styles = withStyles(chat_sidebar_default);
1544
+ static properties = {
1545
+ chats: { attribute: false },
1546
+ selectedId: { type: String, attribute: "selected-id" }
1547
+ };
1548
+ constructor() {
1549
+ super();
1550
+ this.chats = [];
1551
+ this.selectedId = "";
1552
+ }
1553
+ render() {
1554
+ return html`
1555
+ <header>
1556
+ <h2>Chat</h2>
1557
+ <button
1558
+ class="new"
1559
+ type="button"
1560
+ title="New chat"
1561
+ aria-label="New chat"
1562
+ @click=${() => this.fire("office-chat-new", {})}
1563
+ >
1564
+ ${icon("plus")}
1565
+ </button>
1566
+ </header>
1567
+ ${this.chats.length === 0 ? html`<p class="empty">No chats yet. Start one with the plus button.</p>` : html`<ul>
1568
+ ${this.chats.map((c) => this.row(c))}
1569
+ </ul>`}
1570
+ `;
1571
+ }
1572
+ row(c) {
1573
+ const other = c.kind === "direct" ? c.members.find((m) => !m.self) : void 0;
1574
+ const preview = c.lastMessage ? c.lastMessage.system ? c.lastMessage.text : c.lastMessage.mine ? `You: ${c.lastMessage.text}` : c.kind === "direct" ? c.lastMessage.text : `${c.lastMessage.authorName}: ${c.lastMessage.text}` : "No messages yet";
1575
+ return html`
1576
+ <li>
1577
+ <button
1578
+ class="row"
1579
+ type="button"
1580
+ aria-current=${c.id === this.selectedId ? "true" : "false"}
1581
+ data-unread=${c.unread ? "true" : "false"}
1582
+ @click=${() => this.fire("office-chat-select", { id: c.id })}
1583
+ >
1584
+ ${other ? html`<office-ui-avatar
1585
+ name=${other.name}
1586
+ seed=${other.id}
1587
+ presence=${other.availability}
1588
+ ></office-ui-avatar>` : html`<span class="group" aria-hidden="true"
1589
+ >${icon(c.meetingId ? "calendar" : "users")}</span
1590
+ >`}
1591
+ <span class="text">
1592
+ <span class="line">
1593
+ <span class="title">${c.title}</span>
1594
+ <time>${stamp(c.ts)}</time>
1595
+ </span>
1596
+ <span class="preview">${preview}</span>
1597
+ </span>
1598
+ ${c.live ? html`<span class="live" title="Call in progress">${icon("video")}</span>` : nothing}
1599
+ ${c.unread ? html`<span class="count" aria-label=${`${c.unread} unread`}
1600
+ >${c.unread > 99 ? "99+" : c.unread}</span
1601
+ >` : nothing}
1602
+ </button>
1603
+ </li>
1604
+ `;
1605
+ }
1606
+ };
1607
+ var defineChatSidebar = definer("office-ui-chat-sidebar", () => OfficeUiChatSidebar);
1608
+
1609
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/people-picker.css?raw
1610
+ var people_picker_default = ":host {\n display: grid;\n gap: var(--office-space-2);\n min-width: 0;\n}\ninput[type='search'] {\n min-width: 0;\n box-sizing: border-box;\n padding: var(--office-space-3);\n font: inherit;\n color: inherit;\n background: var(--office-field-background);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius-md);\n}\n.chips,\n.list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.chips {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-1);\n}\n.chip {\n padding: var(--office-space-1) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand-subtle);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n.list {\n max-height: 14em;\n overflow: auto;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n}\n.list label {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-2) var(--office-space-3);\n cursor: pointer;\n}\n.list label:hover {\n background: var(--office-selected);\n}\n.name {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.none {\n margin: 0;\n color: var(--office-teams-text-subtle);\n}\n@media (pointer: coarse) {\n .list label,\n .chip {\n min-height: var(--office-touch-target, 44px);\n }\n}\n";
1611
+
1612
+ // src/teams/people-picker.ts
1613
+ var OfficeUiPeoplePicker = class extends TeamsElement {
1614
+ static styles = withStyles(people_picker_default);
1615
+ static properties = {
1616
+ contacts: { attribute: false },
1617
+ selected: { attribute: false },
1618
+ exclude: { attribute: false },
1619
+ label: { type: String },
1620
+ query: { state: true }
1621
+ };
1622
+ constructor() {
1623
+ super();
1624
+ this.contacts = [];
1625
+ this.selected = [];
1626
+ this.exclude = [];
1627
+ this.label = "People";
1628
+ this.query = "";
1629
+ }
1630
+ toggle(id) {
1631
+ const next = this.selected.includes(id) ? this.selected.filter((s) => s !== id) : [...this.selected, id];
1632
+ this.fire("office-people-change", { selected: next });
1633
+ }
1634
+ render() {
1635
+ const q = this.query.trim().toLocaleLowerCase();
1636
+ const offered = this.contacts.filter(
1637
+ (c) => !this.exclude.includes(c.id) && (!q || c.name.toLocaleLowerCase().includes(q))
1638
+ );
1639
+ const chosen = this.contacts.filter((c) => this.selected.includes(c.id));
1640
+ return html`
1641
+ ${chosen.length ? html`<ul class="chips" aria-label="Selected people">
1642
+ ${chosen.map(
1643
+ (c) => html`<li>
1644
+ <button
1645
+ type="button"
1646
+ class="chip"
1647
+ aria-label=${`Remove ${c.name}`}
1648
+ @click=${() => this.toggle(c.id)}
1649
+ >
1650
+ ${c.name} <span aria-hidden="true">×</span>
1651
+ </button>
1652
+ </li>`
1653
+ )}
1654
+ </ul>` : nothing}
1655
+ <input
1656
+ type="search"
1657
+ placeholder="Search people"
1658
+ aria-label=${`Search ${this.label.toLocaleLowerCase()}`}
1659
+ .value=${this.query}
1660
+ @input=${(e) => this.query = e.target.value}
1661
+ />
1662
+ ${offered.length === 0 ? html`<p class="none">
1663
+ ${this.contacts.length === 0 ? "Nobody else has joined this workspace yet." : "No one matches that search."}
1664
+ </p>` : html`<ul class="list" role="group" aria-label=${this.label}>
1665
+ ${offered.map(
1666
+ (c) => html`<li>
1667
+ <label>
1668
+ <input
1669
+ type="checkbox"
1670
+ .checked=${this.selected.includes(c.id)}
1671
+ @change=${() => this.toggle(c.id)}
1672
+ />
1673
+ <office-ui-avatar
1674
+ size="sm"
1675
+ name=${c.name}
1676
+ seed=${c.id}
1677
+ presence=${c.availability}
1678
+ ></office-ui-avatar>
1679
+ <span class="name">${c.name}</span>
1680
+ </label>
1681
+ </li>`
1682
+ )}
1683
+ </ul>`}
1684
+ `;
1685
+ }
1686
+ };
1687
+ var definePeoplePicker = definer("office-ui-people-picker", () => OfficeUiPeoplePicker);
1688
+
858
1689
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/prejoin.css?raw
859
1690
  var prejoin_default = ":host {\n display: grid;\n place-items: center;\n padding: var(--office-space-5);\n background: var(--office-teams-panel);\n}\n.card {\n display: grid;\n grid-template-columns: minmax(0, 3fr) minmax(16em, 2fr);\n width: min(60em, 100%);\n overflow: hidden;\n border-radius: var(--office-radius-lg);\n background: var(--office-background);\n box-shadow: var(--office-shadow-teams);\n}\n.stage {\n position: relative;\n display: grid;\n place-content: center;\n justify-items: center;\n gap: var(--office-space-3);\n min-height: 18em;\n background: var(--office-call-background);\n color: var(--office-call-muted-foreground);\n}\n.stage video {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n transform: scaleX(-1);\n}\n.panel {\n display: flex;\n flex-direction: column;\n gap: var(--office-space-3);\n padding: var(--office-space-5);\n}\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\n.for {\n margin: 0;\n color: var(--office-teams-text-subtle);\n}\n.switch {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n cursor: pointer;\n}\n.what {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-2);\n}\n.switch input {\n position: absolute;\n inset: 0;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n.track {\n position: relative;\n width: 2.6em;\n height: 1.4em;\n border-radius: var(--office-radius-full);\n background: var(--office-border);\n transition: background var(--office-duration-fast);\n}\n.track::after {\n content: '';\n position: absolute;\n inset-block-start: 0.2em;\n inset-inline-start: 0.2em;\n width: 1em;\n height: 1em;\n border-radius: 50%;\n background: var(--office-background);\n transition: transform var(--office-duration-fast);\n}\n.switch input:checked ~ .track {\n background: var(--office-teams-brand);\n}\n.switch input:checked ~ .track::after {\n transform: translateX(1.2em);\n}\n.switch:has(input:focus-visible) {\n outline: var(--office-focus-width) solid var(--office-ring);\n}\n.actions {\n display: flex;\n justify-content: flex-end;\n gap: var(--office-space-2);\n margin-top: auto;\n}\n.actions button {\n padding: var(--office-space-2) var(--office-space-5);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n font-weight: var(--office-font-weight-bold);\n}\n.actions .join {\n border-color: transparent;\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\n.actions .join:hover:not(:disabled) {\n background: var(--office-teams-brand-hover);\n}\n.actions button:disabled {\n opacity: 0.6;\n cursor: wait;\n}\n@media (max-width: 767px) {\n .card {\n grid-template-columns: 1fr;\n }\n}\n";
860
1691
 
@@ -937,13 +1768,15 @@ var OfficeUiPrejoin = class extends TeamsElement {
937
1768
  };
938
1769
  var definePrejoin = definer("office-ui-prejoin", () => OfficeUiPrejoin);
939
1770
  var TeamsController = class {
940
- constructor(host, onNotice) {
1771
+ constructor(host, onNotice, onNotification = () => void 0) {
941
1772
  this.host = host;
942
1773
  this.onNotice = onNotice;
1774
+ this.onNotification = onNotification;
943
1775
  host.addController(this);
944
1776
  }
945
1777
  host;
946
1778
  onNotice;
1779
+ onNotification;
947
1780
  client = null;
948
1781
  state = null;
949
1782
  off = [];
@@ -958,7 +1791,8 @@ var TeamsController = class {
958
1791
  this.state = client.getState();
959
1792
  this.host.requestUpdate();
960
1793
  }),
961
- client.on("notice", this.onNotice)
1794
+ client.on("notice", this.onNotice),
1795
+ client.on("notification", this.onNotification)
962
1796
  ];
963
1797
  this.host.requestUpdate();
964
1798
  }
@@ -1954,7 +2788,7 @@ function defineTeamsFilesPanel() {
1954
2788
  }
1955
2789
 
1956
2790
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/teams-settings.css?raw
1957
- var teams_settings_default = ":host {\n font: var(--office-font-size) var(--office-font);\n color: var(--office-foreground);\n}\ndialog {\n box-sizing: border-box;\n width: min(76em, 94vw);\n max-height: 90dvh;\n overflow: hidden;\n padding: 0;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-lg);\n background: var(--office-popover);\n color: inherit;\n box-shadow: var(--office-shadow-teams);\n}\ndialog::backdrop {\n background: var(--office-overlay);\n}\n:host([embedded]) {\n display: block;\n height: 100%;\n min-height: 0;\n}\n:host([embedded]:not([open])) {\n display: none;\n}\n:host([embedded]) dialog {\n position: relative;\n width: 100%;\n height: 100%;\n max-width: none;\n max-height: none;\n margin: 0;\n border: 0;\n border-radius: 0;\n box-shadow: none;\n}\n:host([embedded]) dialog[open] {\n display: flex;\n flex-direction: column;\n}\n:host([embedded]) .settings-layout {\n flex: 1;\n height: auto;\n}\nform {\n display: grid;\n gap: var(--office-space-3);\n padding: var(--office-space-5);\n}\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-4) var(--office-space-5);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n gap: var(--office-space-3);\n}\nheader input {\n margin-inline-start: auto;\n width: min(24em, 50%);\n min-width: 0;\n}\n.settings-results ul {\n list-style: none;\n padding: 0;\n display: grid;\n gap: var(--office-space-3);\n}\n.settings-results button {\n display: grid;\n gap: var(--office-space-2);\n width: 100%;\n text-align: start;\n}\n.settings-results button:hover {\n background: var(--office-selected);\n}\nh1 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\n.settings-layout {\n display: grid;\n grid-template-columns: 19em minmax(0, 1fr);\n height: min(42em, calc(90dvh - 5em));\n min-height: 0;\n}\nnav {\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: var(--office-space-1);\n padding: var(--office-space-3);\n background: var(--office-teams-panel);\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\nnav button {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n min-height: 3em;\n text-align: start;\n border: 0;\n background: transparent;\n}\nnav button:hover {\n background: var(--office-selected);\n}\n.icon {\n width: 1.4em;\n height: 1.4em;\n flex-shrink: 0;\n fill: none;\n stroke: currentColor;\n stroke-width: 1.5;\n stroke-linecap: round;\n stroke-linejoin: round;\n}\nnav button[aria-selected='true'] {\n background: var(--office-teams-brand-subtle);\n color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\n.settings-content {\n min-width: 0;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n}\nsection {\n padding: var(--office-space-5);\n}\nsection h3 {\n margin: var(--office-space-5) 0 var(--office-space-3);\n font-size: var(--office-font-size);\n}\nsection h2 + h3 {\n margin-top: var(--office-space-4);\n}\n[hidden] {\n display: none !important;\n}\n.description {\n color: var(--office-teams-text-subtle);\n line-height: 1.5;\n}\noffice-ui-switch {\n --office-switch-track-on: var(--office-teams-brand);\n}\n.preference {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-block: var(--office-space-3);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n font-weight: normal;\n}\nh2 {\n position: sticky;\n top: 0;\n z-index: 1;\n margin: calc(-1 * var(--office-space-5)) calc(-1 * var(--office-space-5)) var(--office-space-4);\n padding: var(--office-space-5);\n background: var(--office-popover);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n font-size: var(--office-font-size-lg);\n}\nlabel {\n display: grid;\n gap: var(--office-space-1);\n font-weight: var(--office-font-weight-bold);\n}\nsmall {\n color: var(--office-teams-text-subtle);\n font-weight: normal;\n}\ninput,\ntextarea,\nselect {\n padding: var(--office-space-2);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n color: inherit;\n font: inherit;\n}\ntextarea {\n min-height: 5.5em;\n font-family: ui-monospace, monospace;\n font-size: 0.9em;\n}\n.issues {\n margin: 0;\n padding-inline-start: 1.2em;\n color: var(--office-danger);\n font-size: var(--office-font-size-sm);\n}\n.actions {\n display: flex;\n justify-content: flex-end;\n gap: var(--office-space-2);\n}\nbutton {\n padding: var(--office-space-2) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\nbutton.primary {\n border-color: transparent;\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\nbutton.primary:hover {\n background: var(--office-teams-brand-hover);\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n@media (max-width: 48em) {\n .settings-layout {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr);\n min-height: 0;\n }\n nav {\n max-height: 30dvh;\n border-inline-end: 0;\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n }\n}\n";
2791
+ var teams_settings_default = ":host {\n font: var(--office-font-size) var(--office-font);\n color: var(--office-foreground);\n}\ndialog {\n box-sizing: border-box;\n width: min(76em, 94vw);\n max-height: 90dvh;\n overflow: hidden;\n padding: 0;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-lg);\n background: var(--office-popover);\n color: inherit;\n box-shadow: var(--office-shadow-teams);\n}\ndialog::backdrop {\n background: var(--office-overlay);\n}\n:host([embedded]) {\n display: block;\n height: 100%;\n min-height: 0;\n}\n:host([embedded]:not([open])) {\n display: none;\n}\n:host([embedded]) dialog {\n position: relative;\n width: 100%;\n height: 100%;\n max-width: none;\n max-height: none;\n margin: 0;\n border: 0;\n border-radius: 0;\n box-shadow: none;\n}\n:host([embedded]) dialog[open] {\n display: flex;\n flex-direction: column;\n}\n:host([embedded]) .settings-layout {\n flex: 1;\n height: auto;\n}\nform {\n display: grid;\n gap: var(--office-space-3);\n padding: var(--office-space-5);\n}\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-4) var(--office-space-5);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n gap: var(--office-space-3);\n}\nheader input {\n margin-inline-start: auto;\n width: min(24em, 50%);\n min-width: 0;\n}\n.settings-results ul {\n list-style: none;\n padding: 0;\n display: grid;\n gap: var(--office-space-3);\n}\n.settings-results button {\n display: grid;\n gap: var(--office-space-2);\n width: 100%;\n text-align: start;\n}\n.settings-results button:hover {\n background: var(--office-selected);\n}\nh1 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\n.settings-layout {\n display: grid;\n grid-template-columns: 19em minmax(0, 1fr);\n height: min(42em, calc(90dvh - 5em));\n min-height: 0;\n}\nnav {\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: var(--office-space-1);\n padding: var(--office-space-3);\n background: var(--office-teams-panel);\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\nnav button {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n min-height: 3em;\n text-align: start;\n border: 0;\n background: transparent;\n}\nnav button:hover {\n background: var(--office-selected);\n}\n.icon {\n width: 1.4em;\n height: 1.4em;\n flex-shrink: 0;\n fill: none;\n stroke: currentColor;\n stroke-width: 1.5;\n stroke-linecap: round;\n stroke-linejoin: round;\n}\nnav button[aria-selected='true'] {\n background: var(--office-teams-brand-subtle);\n color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\n.settings-content {\n min-width: 0;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n}\nsection {\n padding: var(--office-space-5);\n}\nsection h3 {\n margin: var(--office-space-5) 0 var(--office-space-3);\n font-size: var(--office-font-size);\n}\nsection h2 + h3 {\n margin-top: var(--office-space-4);\n}\n[hidden] {\n display: none !important;\n}\n.description {\n color: var(--office-teams-text-subtle);\n line-height: 1.5;\n}\noffice-ui-switch {\n --office-switch-track-on: var(--office-teams-brand);\n}\n.preference {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-block: var(--office-space-3);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n font-weight: normal;\n}\nh2 {\n position: sticky;\n top: 0;\n z-index: 1;\n margin: calc(-1 * var(--office-space-5)) calc(-1 * var(--office-space-5)) var(--office-space-4);\n padding: var(--office-space-5);\n background: var(--office-popover);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n font-size: var(--office-font-size-lg);\n}\nlabel {\n display: grid;\n gap: var(--office-space-1);\n font-weight: var(--office-font-weight-bold);\n}\nsmall {\n color: var(--office-teams-text-subtle);\n font-weight: normal;\n}\ninput,\ntextarea,\nselect {\n padding: var(--office-space-2);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n color: inherit;\n font: inherit;\n}\ntextarea {\n min-height: 5.5em;\n font-family: ui-monospace, monospace;\n font-size: 0.9em;\n}\n.issues {\n margin: 0;\n padding-inline-start: 1.2em;\n color: var(--office-danger);\n font-size: var(--office-font-size-sm);\n}\n.actions {\n display: flex;\n justify-content: flex-end;\n gap: var(--office-space-2);\n}\nbutton {\n padding: var(--office-space-2) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\nbutton.primary {\n border-color: transparent;\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\nbutton.primary:hover {\n background: var(--office-teams-brand-hover);\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n@media (max-width: 48em) {\n .settings-layout {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr);\n min-height: 0;\n }\n nav {\n max-height: 30dvh;\n border-inline-end: 0;\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n }\n}\n\n.notification-level {\n display: grid;\n gap: var(--office-space-2);\n margin: var(--office-space-3) 0 0;\n padding: 0;\n border: 0;\n}\n.notification-level legend {\n margin-bottom: var(--office-space-2);\n padding: 0;\n font-weight: var(--office-font-weight-bold);\n}\n.notification-choice {\n display: flex;\n align-items: flex-start;\n gap: var(--office-space-3);\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n cursor: pointer;\n font-weight: normal;\n}\n.notification-choice:has(input:checked) {\n border-color: var(--office-teams-brand);\n background: var(--office-teams-brand-subtle);\n}\n.notification-choice input {\n margin-block-start: var(--office-space-1);\n accent-color: var(--office-teams-brand);\n}\n.notification-choice span {\n display: grid;\n gap: var(--office-space-1);\n}\n.notification-choice small {\n color: var(--office-teams-text-subtle);\n}\n";
1958
2792
 
1959
2793
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/settings-previews.css?raw
1960
2794
  var settings_previews_default = ".theme-options,\n.density-options {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-3);\n}\n.theme-options button {\n display: grid;\n gap: var(--office-space-3);\n padding: var(--office-space-2);\n text-align: start;\n min-width: 9em;\n}\n.theme-options button[aria-pressed='true'],\n.density-choice:has(input:checked) {\n border-color: var(--office-teams-brand);\n box-shadow: inset 0 0 0 var(--office-border-width) var(--office-teams-brand);\n}\n.theme-swatch {\n --preview-paper: var(--office-call-foreground);\n --preview-ink: var(--office-call-background);\n display: grid;\n grid-template-columns: 0.7em 2.4em 1fr;\n grid-template-rows: 0.7em 1fr;\n width: 9em;\n height: 5.5em;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius);\n overflow: hidden;\n background: var(--preview-paper);\n}\n.theme-swatch[data-theme='dark'] {\n --preview-paper: var(--office-call-background);\n --preview-ink: var(--office-call-foreground);\n}\n.theme-swatch[data-theme='system'] {\n background: linear-gradient(\n 90deg,\n var(--office-call-foreground) 50%,\n var(--office-call-background) 50%\n );\n}\n.preview-toolbar {\n grid-column: 1 / -1;\n background: var(--office-teams-brand);\n}\n.preview-rail,\n.preview-sidebar {\n background: color-mix(in srgb, var(--preview-ink) 8%, var(--preview-paper));\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.preview-sidebar,\n.preview-conversation {\n display: flex;\n flex-direction: column;\n gap: var(--office-space-2);\n padding: var(--office-space-2);\n}\n.preview-sidebar i,\n.preview-conversation i {\n height: 0.4em;\n border-radius: var(--office-radius);\n background: color-mix(in srgb, var(--preview-ink) 25%, var(--preview-paper));\n}\n.preview-conversation i {\n height: 0.8em;\n width: 80%;\n}\n.preview-conversation i:nth-child(2) {\n align-self: flex-end;\n background: var(--office-teams-brand-subtle);\n}\n.density-options {\n margin: var(--office-space-5) 0 0;\n padding: var(--office-space-5) 0 0;\n border: 0;\n border-top: var(--office-border-width) solid var(--office-teams-divider);\n}\n.density-options legend {\n float: inline-start;\n width: 100%;\n font-weight: var(--office-font-weight-bold);\n margin-bottom: var(--office-space-3);\n}\n.density-choice {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: var(--office-space-3);\n width: 13em;\n padding: var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n cursor: pointer;\n font-weight: normal;\n}\n.density-choice input {\n position: absolute;\n inset-inline-end: var(--office-space-2);\n inset-block-end: var(--office-space-2);\n accent-color: var(--office-teams-brand);\n}\n.density-caption {\n display: grid;\n gap: var(--office-space-1);\n padding-inline-end: var(--office-space-5);\n}\n.density-preview {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: var(--office-space-3);\n height: 7em;\n padding: var(--office-space-3);\n border-radius: var(--office-radius);\n background: var(--office-teams-panel);\n}\n.preview-message {\n display: flex;\n gap: var(--office-space-2);\n align-items: flex-start;\n}\n.preview-message > i {\n width: 1.1em;\n height: 1.1em;\n border-radius: 50%;\n background: var(--office-teams-brand-subtle);\n border: var(--office-border-width) solid var(--office-teams-brand);\n}\n.preview-message > span {\n display: grid;\n gap: var(--office-space-1);\n flex: 1;\n}\n.preview-message b {\n height: 0.3em;\n width: 75%;\n background: var(--office-teams-text-subtle);\n border-radius: var(--office-radius);\n}\n.preview-message b + b {\n width: 100%;\n opacity: 0.4;\n}\n.density-preview[data-density='compact'] {\n gap: var(--office-space-1);\n}\n.density-preview[data-density='compact'] .preview-message > i {\n width: 0.7em;\n height: 0.7em;\n}\n.density-preview[data-density='compact'] .preview-message > span {\n display: flex;\n align-items: center;\n}\n@media (forced-colors: active) {\n .theme-options button[aria-pressed='true'],\n .density-choice:has(input:checked) {\n outline: var(--office-focus-width) solid Highlight;\n }\n}\n";
@@ -2023,6 +2857,53 @@ function densityChoices(density, change) {
2023
2857
  )}
2024
2858
  </fieldset>`;
2025
2859
  }
2860
+ var LEVELS = [
2861
+ ["all", "All activity", "Mentions, replies, reactions and pinned messages"],
2862
+ ["mentions", "Mentions only", "Only when someone mentions you"],
2863
+ ["off", "Off", "Activity still collects in the Activity feed"]
2864
+ ];
2865
+ var PERMISSION_TEXT = {
2866
+ granted: "Desktop notifications are on for this browser. They appear when this tab is in the background.",
2867
+ default: "Desktop notifications appear when this tab is in the background. Your browser asks first.",
2868
+ denied: "Desktop notifications are blocked for this site. Allow them in your browser settings to turn them on.",
2869
+ unsupported: "This browser does not support desktop notifications."
2870
+ };
2871
+ var desktopPermission = () => globalThis.Notification?.permission ?? "unsupported";
2872
+ function notificationChoices(settings, permission, handlers) {
2873
+ return html`<fieldset class="notification-level">
2874
+ <legend>Notify me about</legend>
2875
+ ${LEVELS.map(
2876
+ ([level, label, description]) => html`<label class="notification-choice">
2877
+ <input
2878
+ type="radio"
2879
+ name="notification-level"
2880
+ value=${level}
2881
+ .checked=${settings.level === level}
2882
+ @change=${() => handlers.change({ level })}
2883
+ />
2884
+ <span><strong>${label}</strong><small>${description}</small></span>
2885
+ </label>`
2886
+ )}
2887
+ </fieldset>
2888
+ <h3>Sound</h3>
2889
+ <label class="preference"
2890
+ ><span>Play a sound for new notifications</span
2891
+ ><office-ui-switch
2892
+ aria-label="Notification sound"
2893
+ .checked=${settings.sound}
2894
+ @change=${(event) => handlers.change({ sound: event.target.checked })}
2895
+ ></office-ui-switch
2896
+ ></label>
2897
+ <h3>Desktop notifications</h3>
2898
+ <p class="description" role="status">${PERMISSION_TEXT[permission]}</p>
2899
+ ${permission === "default" ? html`<button type="button" class="primary" @click=${handlers.requestPermission}>
2900
+ Enable desktop notifications
2901
+ </button>` : ""}
2902
+ <p class="description">
2903
+ Do not disturb silences every notification. Email and mobile push notifications are not
2904
+ available.
2905
+ </p>`;
2906
+ }
2026
2907
  var SETTINGS_CATEGORIES = [
2027
2908
  ["general", "General"],
2028
2909
  ["appearance", "Appearance and accessibility"],
@@ -2047,6 +2928,21 @@ var TOPICS = [
2047
2928
  title: "Show app names",
2048
2929
  description: "Display labels beneath app bar icons"
2049
2930
  },
2931
+ {
2932
+ category: "notifications",
2933
+ title: "Activity notifications",
2934
+ description: "Notify for all activity, mentions only or nothing"
2935
+ },
2936
+ {
2937
+ category: "notifications",
2938
+ title: "Sound",
2939
+ description: "Play a sound for new notifications"
2940
+ },
2941
+ {
2942
+ category: "notifications",
2943
+ title: "Desktop notifications",
2944
+ description: "Browser notifications while this tab is in the background, and Do not disturb"
2945
+ },
2050
2946
  {
2051
2947
  category: "notifications",
2052
2948
  title: "Followed threads",
@@ -2100,6 +2996,7 @@ var TeamsSettings = class extends LitElement {
2100
2996
  category: { state: true },
2101
2997
  theme: { attribute: false },
2102
2998
  followSettings: { attribute: false },
2999
+ notificationSettings: { attribute: false },
2103
3000
  userName: { attribute: false },
2104
3001
  fileOpenPreference: { attribute: false },
2105
3002
  chatDensity: { attribute: false },
@@ -2115,6 +3012,7 @@ var TeamsSettings = class extends LitElement {
2115
3012
  this.category = "general";
2116
3013
  this.theme = "system";
2117
3014
  this.followSettings = { started: true, replied: true };
3015
+ this.notificationSettings = { level: "all", sound: true };
2118
3016
  this.userName = "";
2119
3017
  this.fileOpenPreference = "teams";
2120
3018
  this.chatDensity = "comfy";
@@ -2304,12 +3202,22 @@ var TeamsSettings = class extends LitElement {
2304
3202
  ?hidden=${!!this.query.trim() || this.category !== "notifications"}
2305
3203
  >
2306
3204
  <h2>Notifications and activity</h2>
3205
+ <h3>Activity notifications</h3>
3206
+ ${notificationChoices(this.notificationSettings, desktopPermission(), {
3207
+ change: (detail) => this.dispatchEvent(
3208
+ new CustomEvent("teams-settings-notifications", {
3209
+ detail,
3210
+ bubbles: true,
3211
+ composed: true
3212
+ })
3213
+ ),
3214
+ requestPermission: () => void globalThis.Notification?.requestPermission().then(
3215
+ () => this.requestUpdate()
3216
+ )
3217
+ })}
2307
3218
  <h3>Followed threads</h3>
2308
3219
  <p class="description">Choose which conversations appear in Followed threads.</p>
2309
3220
  ${["started", "replied"].map((key) => html`<label class="preference"><span>${key === "started" ? "Threads I start" : "Threads I reply to"}</span><office-ui-switch aria-label=${key === "started" ? "Threads I start" : "Threads I reply to"} .checked=${this.followSettings[key]} @change=${(event) => this.dispatchEvent(new CustomEvent("teams-settings-follow", { detail: { [key]: event.target.checked }, bubbles: true, composed: true }))}></office-ui-switch></label>`)}
2310
- <p class="description">
2311
- Desktop, email and activity notifications are not available yet.
2312
- </p>
2313
3221
  </section>
2314
3222
  <section
2315
3223
  id="panel-files"
@@ -2464,8 +3372,8 @@ function loadConfig() {
2464
3372
  var saveConfig = (config) => safeStorage.setItem("teams:config", JSON.stringify(config));
2465
3373
 
2466
3374
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/teams-app.css?raw
2467
- var teams_app_default = "/* <teams-app>: the shell. Colours, spacing and radii come from the ooxml-ui tokens, so a host\n retheme or dark mode reaches the whole app. The elements inside bring their own styles. */\n:host {\n display: block;\n height: 100%;\n min-height: 28em;\n font: var(--office-font-size) var(--office-font);\n color: var(--office-foreground);\n background: var(--office-background);\n}\n:host([data-office-theme='dark']) {\n --office-background: var(--office-teams-panel);\n --office-surface: var(--office-teams-bubble);\n --office-popover: var(--office-teams-panel);\n}\n:host([data-chat-density='compact']) {\n --office-teams-message-gap: var(--office-space-1);\n --office-teams-message-padding: var(--office-space-1) var(--office-space-2);\n}\n@media (prefers-color-scheme: dark) {\n :host(:not([data-office-theme='light'])) {\n --office-background: var(--office-teams-panel);\n --office-surface: var(--office-teams-bubble);\n --office-popover: var(--office-teams-panel);\n }\n}\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\nbutton {\n font: inherit;\n color: inherit;\n cursor: pointer;\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n\n.shell {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n height: 100%;\n}\n\n/* ---- top bar ---------------------------------------------------------------------------- */\n.topbar {\n display: grid;\n grid-template-columns: 16em minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--office-space-4);\n padding: var(--office-space-2) var(--office-space-4);\n background: var(--office-teams-rail);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n}\n.brand {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n font-size: var(--office-font-size-lg);\n font-weight: var(--office-font-weight-bold);\n}\n.logo {\n display: inline-grid;\n place-items: center;\n width: 1.8em;\n height: 1.8em;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: 0.85em;\n}\n.search {\n position: relative;\n justify-self: center;\n width: min(34em, 100%);\n}\n.search input {\n width: 100%;\n padding: var(--office-space-2) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-full);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n}\n.results {\n position: absolute;\n inset-inline: 0;\n inset-block-start: calc(100% + var(--office-space-1));\n z-index: 5;\n max-height: 22em;\n margin: 0;\n padding: var(--office-space-1);\n overflow: auto;\n list-style: none;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\n.results button {\n display: grid;\n width: 100%;\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n text-align: start;\n}\n.results button:hover {\n background: var(--office-selected);\n}\n.results .in,\n.results .none {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n}\n.results .none {\n padding: var(--office-space-3);\n}\n.snippet {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.me {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n}\n.me office-ui-menu-button::part(caret) {\n display: none;\n}\n.channel-head office-ui-menu-button::part(caret) {\n display: none;\n}\n.channel-head svg {\n width: 1em;\n height: 1em;\n fill: none;\n stroke: currentColor;\n stroke-width: var(--office-icon-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n/* ---- body ------------------------------------------------------------------------------- */\n.body {\n display: grid;\n grid-template-columns: auto 19em minmax(0, 1fr);\n min-height: 0;\n grid-template-rows: minmax(0, 1fr);\n}\n.body > office-ui-app-rail {\n grid-column: 1;\n grid-row: 1;\n}\n.body > .side {\n grid-column: 2;\n grid-row: 1;\n}\n.body > .main {\n grid-column: 3;\n grid-row: 1;\n}\n.body > teams-settings {\n grid-column: 2 / -1;\n grid-row: 1;\n min-width: 0;\n min-height: 0;\n z-index: 1;\n}\n.body > teams-settings:not([open]) {\n display: none;\n}\n.body > [inert] {\n visibility: hidden;\n}\n.side {\n min-height: 0;\n overflow: auto;\n background: var(--office-teams-panel);\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.side h2 {\n margin: 0;\n padding: var(--office-space-3) var(--office-space-4);\n font-size: var(--office-font-size-lg);\n}\n.main {\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n}\n.popout-view {\n display: flex;\n height: 100%;\n min-height: 0;\n}\n.popout-view teams-content-preview {\n flex: 1;\n}\n.main office-ui-chat-list,\n.main office-ui-prejoin {\n flex: 1;\n}\n\nul.plain {\n margin: 0;\n padding: 0 var(--office-space-3);\n list-style: none;\n}\nul.plain li {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n padding: var(--office-space-2) var(--office-space-2);\n}\nul.plain li > :first-child:not(office-ui-avatar) {\n flex: 1;\n}\n.hint {\n padding: 0 var(--office-space-4);\n color: var(--office-teams-text-subtle);\n}\n.join {\n padding: var(--office-space-1) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n}\n.join.live {\n border-color: transparent;\n background: var(--office-call-live);\n color: var(--office-call-foreground);\n}\n\n/* ---- conversation ----------------------------------------------------------------------- */\n.channel-head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-4);\n padding: var(--office-space-2) var(--office-space-5);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n}\n.channel-head > button:not(.meet),\n.thread-head button {\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n}\n.channel-head > button:hover:not(:disabled),\n.thread-head button:hover {\n background: var(--office-selected);\n}\n.channel-head > button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.channel-head h1 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n white-space: nowrap;\n}\n.hash {\n color: var(--office-teams-text-subtle);\n}\n.tabs {\n display: flex;\n gap: var(--office-space-1);\n}\n.tabs button {\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-bottom: var(--office-border-width-thick) solid transparent;\n background: transparent;\n}\n.tabs button[aria-selected='true'] {\n border-bottom-color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\n.topic {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n color: var(--office-teams-text-subtle);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.people {\n display: flex;\n align-items: center;\n gap: var(--office-space-0);\n}\n.more {\n margin-inline-start: var(--office-space-1);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n}\n.meet {\n padding: var(--office-space-2) var(--office-space-4);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.meet:hover:not(:disabled) {\n background: var(--office-teams-brand-hover);\n}\n.meet:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.callbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-2) var(--office-space-5);\n background: var(--office-call-live);\n color: var(--office-call-foreground);\n}\n.callbar button {\n padding: 0 var(--office-space-3);\n border: var(--office-border-width) solid currentColor;\n border-radius: var(--office-radius);\n background: transparent;\n}\n\n/* ---- files ------------------------------------------------------------------------------ */\n/* ---- meeting ---------------------------------------------------------------------------- */\n.meeting {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n height: 100%;\n background: var(--office-call-background);\n}\n.stage {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n min-height: 0;\n}\n.panel {\n display: flex;\n flex-direction: column;\n width: 22em;\n min-height: 0;\n background: var(--office-background);\n border-inline-start: var(--office-border-width) solid var(--office-teams-divider);\n}\n.panel office-ui-chat-list {\n flex: 1;\n padding-inline: var(--office-space-3);\n}\n.panel office-ui-chat-composer {\n padding-inline: var(--office-space-3);\n}\n.people-panel h2 {\n margin: 0;\n padding: var(--office-space-3) var(--office-space-4);\n font-size: var(--office-font-size-lg);\n}\n\n/* ---- welcome and toast ------------------------------------------------------------------ */\n.welcome {\n display: grid;\n place-items: center;\n height: 100%;\n background: var(--office-teams-panel);\n}\n.welcome form {\n display: grid;\n justify-items: center;\n gap: var(--office-space-3);\n width: min(24em, 90%);\n padding: var(--office-space-6);\n border-radius: var(--office-radius-lg);\n background: var(--office-background);\n box-shadow: var(--office-shadow-teams);\n}\n.welcome h1 {\n margin: 0;\n font-size: var(--office-font-size-xl);\n}\n.welcome p {\n margin: 0;\n color: var(--office-teams-text-subtle);\n}\n.welcome input {\n width: 100%;\n padding: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n color: inherit;\n font: inherit;\n}\n.welcome button {\n width: 100%;\n padding: var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.toast {\n position: fixed;\n inset-block-end: var(--office-space-4);\n inset-inline-start: 50%;\n z-index: 10;\n transform: translateX(-50%);\n padding: var(--office-space-2) var(--office-space-4);\n border-radius: var(--office-radius-md);\n background: var(--office-call-background);\n color: var(--office-call-foreground);\n}\n\n.conversation-grid {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n flex: 1;\n min-height: 0;\n}\n.conversation-grid[data-thread='true'] {\n grid-template-columns: minmax(0, 1fr) minmax(18em, 0.8fr);\n}\n.conversation-main,\n.thread-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n}\n.tab-conversation teams-channel-tab {\n flex: 1;\n min-height: 0;\n}\n.thread-pane {\n border-inline-start: var(--office-border-width) solid var(--office-teams-divider);\n}\n.thread-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--office-space-3);\n padding: var(--office-space-3);\n}\n.thread-head h2 {\n margin: 0;\n font-size: 1em;\n}\n.followed-threads {\n overflow: auto;\n padding: var(--office-space-4);\n}\n.message-transfer {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-3);\n background: var(--office-teams-panel);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.message-transfer span {\n flex: 1;\n overflow-wrap: anywhere;\n}\n.message-transfer button {\n font: inherit;\n color: inherit;\n background: var(--office-background);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n padding: var(--office-space-2) var(--office-space-3);\n cursor: pointer;\n}\n.followed-threads ul {\n list-style: none;\n padding: 0;\n}\n.followed-threads fieldset {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-4);\n padding: var(--office-space-3);\n margin-block: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n}\n.followed-threads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-3);\n padding-block: var(--office-space-3);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.followed-threads button {\n font: inherit;\n color: inherit;\n background: var(--office-background);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n padding: var(--office-space-2) var(--office-space-3);\n cursor: pointer;\n}\n.followed-threads button:hover {\n background: var(--office-selected);\n}\n.followed-threads li button:first-child {\n display: grid;\n gap: var(--office-space-2);\n flex: 1;\n text-align: start;\n min-width: 0;\n overflow-wrap: anywhere;\n border-color: transparent;\n}\n.followed-threads li[data-unread='true'] button:first-child span {\n font-weight: var(--office-font-weight-bold);\n}\n@media (max-width: 56em) {\n .conversation-grid[data-thread='true'] {\n grid-template-columns: minmax(0, 1fr);\n }\n .conversation-grid[data-thread='true'] .conversation-main {\n display: none;\n }\n}\n.tabs {\n overflow-x: auto;\n min-width: 0;\n flex: 1 1 16em;\n}\n\n.channel-head > .back {\n display: none;\n}\n\n/* ---- compact shell ---------------------------------------------------------------------- */\n/* Below 56em the app rail is a bottom bar, the channel list is its own full-screen view and the\n conversation (or any other content) fills the width with a back button to the list. */\n@media (max-width: 56em) {\n .brand {\n display: none;\n }\n .topbar {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: var(--office-space-2);\n }\n .search {\n width: 100%;\n }\n .channel-head {\n gap: var(--office-space-2);\n padding-inline: var(--office-space-3);\n }\n .channel-head > .back {\n display: inline-flex;\n border-color: transparent;\n }\n .channel-head .topic,\n .channel-head .people {\n display: none;\n }\n .channel-head .tabs {\n flex-basis: 100%;\n order: 1;\n }\n .body {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: minmax(0, 1fr) auto;\n }\n .body > office-ui-app-rail {\n grid-column: 1;\n grid-row: 2;\n }\n .body > .side,\n .body > .main,\n .body > teams-settings {\n grid-column: 1;\n grid-row: 1;\n }\n .side {\n border-inline-end: 0;\n }\n .shell[data-mobile-view='list'] .main,\n .shell[data-mobile-view='content'] .side {\n display: none;\n }\n}\n/* Phone landscape has little height: keep the chrome to one thin row each. */\n@media (max-width: 56em) and (max-height: 30em) {\n .topbar {\n padding-block: var(--office-space-1);\n }\n .channel-head {\n flex-wrap: nowrap;\n padding-block: var(--office-space-1);\n }\n .channel-head .tabs {\n flex: 1 1 0;\n order: 0;\n }\n .channel-head h1 {\n font-size: var(--office-font-size);\n }\n}\n/* Touch: every control in the shell reaches the minimum hit target. */\n@media (pointer: coarse) {\n .channel-head > button,\n .meet,\n .tabs button,\n .join,\n .thread-head button,\n .callbar button {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n\n/* An application host can own the single suite-level account control. */\n:host([host-profile]) teams-profile-menu {\n display: none;\n}\n";
2468
- function threadPane(state, client, openFile, close) {
3375
+ var teams_app_default = "/* <teams-app>: the shell. Colours, spacing and radii come from the ooxml-ui tokens, so a host\n retheme or dark mode reaches the whole app. The elements inside bring their own styles. */\n:host {\n display: block;\n height: 100%;\n min-height: 28em;\n font: var(--office-font-size) var(--office-font);\n color: var(--office-foreground);\n background: var(--office-background);\n}\n:host([data-office-theme='dark']) {\n --office-background: var(--office-teams-panel);\n --office-surface: var(--office-teams-bubble);\n --office-popover: var(--office-teams-panel);\n}\n:host([data-chat-density='compact']) {\n --office-teams-message-gap: var(--office-space-1);\n --office-teams-message-padding: var(--office-space-1) var(--office-space-2);\n}\n@media (prefers-color-scheme: dark) {\n :host(:not([data-office-theme='light'])) {\n --office-background: var(--office-teams-panel);\n --office-surface: var(--office-teams-bubble);\n --office-popover: var(--office-teams-panel);\n }\n}\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\nbutton {\n font: inherit;\n color: inherit;\n cursor: pointer;\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n\n.shell {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n height: 100%;\n}\n\n/* ---- top bar ---------------------------------------------------------------------------- */\n.topbar {\n display: grid;\n grid-template-columns: 16em minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--office-space-4);\n padding: var(--office-space-2) var(--office-space-4);\n background: var(--office-teams-rail);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n}\n.brand {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n font-size: var(--office-font-size-lg);\n font-weight: var(--office-font-weight-bold);\n}\n.logo {\n display: inline-grid;\n place-items: center;\n width: 1.8em;\n height: 1.8em;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: 0.85em;\n}\n.search {\n position: relative;\n justify-self: center;\n width: min(34em, 100%);\n}\n.search input {\n width: 100%;\n padding: var(--office-space-2) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-full);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n}\n.results {\n position: absolute;\n inset-inline: 0;\n inset-block-start: calc(100% + var(--office-space-1));\n z-index: 5;\n max-height: 22em;\n margin: 0;\n padding: var(--office-space-1);\n overflow: auto;\n list-style: none;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\n.results button {\n display: grid;\n width: 100%;\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n text-align: start;\n}\n.results button:hover {\n background: var(--office-selected);\n}\n.results .in,\n.results .none {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n}\n.results .none {\n padding: var(--office-space-3);\n}\n.snippet {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.me {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n}\n.me office-ui-menu-button::part(caret) {\n display: none;\n}\n.channel-head office-ui-menu-button::part(caret) {\n display: none;\n}\n.channel-head svg {\n width: 1em;\n height: 1em;\n fill: none;\n stroke: currentColor;\n stroke-width: var(--office-icon-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n/* ---- body ------------------------------------------------------------------------------- */\n.body {\n display: grid;\n grid-template-columns: auto 19em minmax(0, 1fr);\n min-height: 0;\n grid-template-rows: minmax(0, 1fr);\n}\n.body > office-ui-app-rail {\n grid-column: 1;\n grid-row: 1;\n}\n.body > .side {\n grid-column: 2;\n grid-row: 1;\n}\n.body > .main {\n grid-column: 3;\n grid-row: 1;\n}\n.body > teams-settings {\n grid-column: 2 / -1;\n grid-row: 1;\n min-width: 0;\n min-height: 0;\n z-index: 1;\n}\n.body > teams-settings:not([open]) {\n display: none;\n}\n.body > [inert] {\n visibility: hidden;\n}\n.side {\n min-height: 0;\n overflow: auto;\n background: var(--office-teams-panel);\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.side h2 {\n margin: 0;\n padding: var(--office-space-3) var(--office-space-4);\n font-size: var(--office-font-size-lg);\n}\n.main {\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n}\n.popout-view {\n display: flex;\n height: 100%;\n min-height: 0;\n}\n.popout-view teams-content-preview {\n flex: 1;\n}\n.main office-ui-chat-list,\n.main office-ui-prejoin {\n flex: 1;\n}\n\nul.plain {\n margin: 0;\n padding: 0 var(--office-space-3);\n list-style: none;\n}\nul.plain li {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n padding: var(--office-space-2) var(--office-space-2);\n}\nul.plain li > :first-child:not(office-ui-avatar) {\n flex: 1;\n}\n.hint {\n padding: 0 var(--office-space-4);\n color: var(--office-teams-text-subtle);\n}\n.join {\n padding: var(--office-space-1) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n}\n.join.live {\n border-color: transparent;\n background: var(--office-call-live);\n color: var(--office-call-foreground);\n}\n\n/* ---- conversation ----------------------------------------------------------------------- */\n.channel-head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-4);\n padding: var(--office-space-2) var(--office-space-5);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n}\n.channel-head > button:not(.meet),\n.thread-head button {\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n}\n.channel-head > button:hover:not(:disabled),\n.thread-head button:hover {\n background: var(--office-selected);\n}\n.channel-head > button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.channel-head h1 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n white-space: nowrap;\n}\n.hash {\n color: var(--office-teams-text-subtle);\n}\n.tabs {\n display: flex;\n gap: var(--office-space-1);\n}\n.tabs button {\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-bottom: var(--office-border-width-thick) solid transparent;\n background: transparent;\n}\n.tabs button[aria-selected='true'] {\n border-bottom-color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\n.topic {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n color: var(--office-teams-text-subtle);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.people {\n display: flex;\n align-items: center;\n gap: var(--office-space-0);\n}\n.more {\n margin-inline-start: var(--office-space-1);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n}\n.meet {\n padding: var(--office-space-2) var(--office-space-4);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.meet:hover:not(:disabled) {\n background: var(--office-teams-brand-hover);\n}\n.meet:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.callbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-2) var(--office-space-5);\n background: var(--office-call-live);\n color: var(--office-call-foreground);\n}\n.callbar button {\n padding: 0 var(--office-space-3);\n border: var(--office-border-width) solid currentColor;\n border-radius: var(--office-radius);\n background: transparent;\n}\n\n/* ---- files ------------------------------------------------------------------------------ */\n/* ---- meeting ---------------------------------------------------------------------------- */\n.meeting {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n height: 100%;\n background: var(--office-call-background);\n}\n.stage {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n min-height: 0;\n}\n.panel {\n display: flex;\n flex-direction: column;\n width: 22em;\n min-height: 0;\n background: var(--office-background);\n border-inline-start: var(--office-border-width) solid var(--office-teams-divider);\n}\n.panel office-ui-chat-list {\n flex: 1;\n padding-inline: var(--office-space-3);\n}\n.panel office-ui-chat-composer {\n padding-inline: var(--office-space-3);\n}\n.people-panel h2 {\n margin: 0;\n padding: var(--office-space-3) var(--office-space-4);\n font-size: var(--office-font-size-lg);\n}\n\n/* ---- welcome and toast ------------------------------------------------------------------ */\n.welcome {\n display: grid;\n place-items: center;\n height: 100%;\n background: var(--office-teams-panel);\n}\n.welcome form {\n display: grid;\n justify-items: center;\n gap: var(--office-space-3);\n width: min(24em, 90%);\n padding: var(--office-space-6);\n border-radius: var(--office-radius-lg);\n background: var(--office-background);\n box-shadow: var(--office-shadow-teams);\n}\n.welcome h1 {\n margin: 0;\n font-size: var(--office-font-size-xl);\n}\n.welcome p {\n margin: 0;\n color: var(--office-teams-text-subtle);\n}\n.welcome input {\n width: 100%;\n padding: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n color: inherit;\n font: inherit;\n}\n.welcome button {\n width: 100%;\n padding: var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.toast {\n position: fixed;\n inset-block-end: var(--office-space-4);\n inset-inline-start: 50%;\n z-index: 10;\n transform: translateX(-50%);\n padding: var(--office-space-2) var(--office-space-4);\n border-radius: var(--office-radius-md);\n background: var(--office-call-background);\n color: var(--office-call-foreground);\n}\n\n.conversation-grid {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n flex: 1;\n min-height: 0;\n}\n.conversation-grid[data-thread='true'] {\n grid-template-columns: minmax(0, 1fr) minmax(18em, 0.8fr);\n}\n.conversation-main,\n.thread-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n}\n.tab-conversation teams-channel-tab {\n flex: 1;\n min-height: 0;\n}\n.thread-pane {\n border-inline-start: var(--office-border-width) solid var(--office-teams-divider);\n}\n.thread-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--office-space-3);\n padding: var(--office-space-3);\n}\n.thread-head h2 {\n margin: 0;\n font-size: 1em;\n}\n.followed-threads {\n overflow: auto;\n padding: var(--office-space-4);\n}\n.message-transfer {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-3);\n background: var(--office-teams-panel);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.message-transfer span {\n flex: 1;\n overflow-wrap: anywhere;\n}\n.message-transfer button {\n font: inherit;\n color: inherit;\n background: var(--office-background);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n padding: var(--office-space-2) var(--office-space-3);\n cursor: pointer;\n}\n.followed-threads ul {\n list-style: none;\n padding: 0;\n}\n.followed-threads fieldset {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-4);\n padding: var(--office-space-3);\n margin-block: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n}\n.followed-threads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-3);\n padding-block: var(--office-space-3);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.followed-threads button {\n font: inherit;\n color: inherit;\n background: var(--office-background);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n padding: var(--office-space-2) var(--office-space-3);\n cursor: pointer;\n}\n.followed-threads button:hover {\n background: var(--office-selected);\n}\n.followed-threads li button:first-child {\n display: grid;\n gap: var(--office-space-2);\n flex: 1;\n text-align: start;\n min-width: 0;\n overflow-wrap: anywhere;\n border-color: transparent;\n}\n.followed-threads li[data-unread='true'] button:first-child span {\n font-weight: var(--office-font-weight-bold);\n}\n@media (max-width: 56em) {\n .conversation-grid[data-thread='true'] {\n grid-template-columns: minmax(0, 1fr);\n }\n .conversation-grid[data-thread='true'] .conversation-main {\n display: none;\n }\n}\n.tabs {\n overflow-x: auto;\n min-width: 0;\n flex: 1 1 16em;\n}\n\n.channel-head > .back {\n display: none;\n}\n\n/* ---- compact shell ---------------------------------------------------------------------- */\n/* Below 56em the app rail is a bottom bar, the channel list is its own full-screen view and the\n conversation (or any other content) fills the width with a back button to the list. */\n@media (max-width: 56em) {\n .brand {\n display: none;\n }\n .topbar {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: var(--office-space-2);\n }\n .search {\n width: 100%;\n }\n .channel-head {\n gap: var(--office-space-2);\n padding-inline: var(--office-space-3);\n }\n .channel-head > .back {\n display: inline-flex;\n border-color: transparent;\n }\n .channel-head .topic,\n .channel-head .people {\n display: none;\n }\n .channel-head .tabs {\n flex-basis: 100%;\n order: 1;\n }\n .body {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: minmax(0, 1fr) auto;\n }\n .body > office-ui-app-rail {\n grid-column: 1;\n grid-row: 2;\n }\n .body > .side,\n .body > .main,\n .body > teams-settings {\n grid-column: 1;\n grid-row: 1;\n }\n .side {\n border-inline-end: 0;\n }\n .shell[data-mobile-view='list'] .main,\n .shell[data-mobile-view='content'] .side {\n display: none;\n }\n}\n/* Phone landscape has little height: keep the chrome to one thin row each. */\n@media (max-width: 56em) and (max-height: 30em) {\n .topbar {\n padding-block: var(--office-space-1);\n }\n .channel-head {\n flex-wrap: nowrap;\n padding-block: var(--office-space-1);\n }\n .channel-head .tabs {\n flex: 1 1 0;\n order: 0;\n }\n .channel-head h1 {\n font-size: var(--office-font-size);\n }\n}\n/* Touch: every control in the shell reaches the minimum hit target. */\n@media (pointer: coarse) {\n .channel-head > button,\n .meet,\n .tabs button,\n .join,\n .thread-head button,\n .callbar button {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n\n/* An application host can own the single suite-level account control. */\n:host([host-profile]) teams-profile-menu {\n display: none;\n}\n\n/* ---- activity ---------------------------------------------------------------------------- */\n.side > office-ui-activity-feed {\n height: 100%;\n}\n/* Toasts for new activity stack at the top right, under the header, so they never cover the\n compose box; on phones they are full-width banners below the header. */\n.activity-toasts {\n position: fixed;\n inset-block-start: 8em;\n inset-inline-end: var(--office-space-4);\n z-index: 10;\n display: flex;\n flex-direction: column;\n gap: var(--office-space-2);\n width: min(28em, calc(100% - var(--office-space-5)));\n pointer-events: none;\n}\n.activity-toast {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-2) var(--office-space-3) var(--office-space-2) var(--office-space-4);\n border-radius: var(--office-radius-md);\n background: var(--office-call-background);\n color: var(--office-call-foreground);\n box-shadow: var(--office-shadow-teams);\n pointer-events: auto;\n}\n@media (max-width: 40em) {\n .activity-toasts {\n inset-block-start: 4.6em;\n inset-inline: var(--office-space-2);\n width: auto;\n }\n}\n.activity-toast .toast-text {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n}\n.activity-toast .toast-text > * {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.activity-toast .toast-preview {\n opacity: 0.8;\n font-size: var(--office-font-size-sm);\n}\n.activity-toast button {\n flex: none;\n padding: var(--office-space-1) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n color: inherit;\n font-weight: var(--office-font-weight-bold);\n}\n.activity-toast button:hover {\n background: var(--office-selected);\n}\n.activity-toast button svg {\n width: 1em;\n height: 1em;\n fill: none;\n stroke: currentColor;\n stroke-width: var(--office-icon-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n@media (pointer: coarse) {\n .activity-toast button {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n\n/* ---- chat and calendar ------------------------------------------------------------------ */\n.calendar-back svg,\n.show-calendar svg,\n.group-mark svg,\n.chat-head .chat-people svg {\n width: 1.25em;\n height: 1.25em;\n flex: none;\n fill: none;\n stroke: currentColor;\n stroke-width: var(--office-icon-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n.chat-head office-ui-avatar {\n flex: none;\n}\n.group-mark {\n display: grid;\n flex: none;\n place-items: center;\n width: 2.3em;\n height: 2.3em;\n border-radius: var(--office-radius-full);\n background: var(--office-teams-bubble);\n color: var(--office-teams-brand);\n}\n.chat-head .chat-people {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-2);\n}\n.empty-main {\n display: grid;\n flex: 1;\n place-content: center;\n justify-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-6);\n color: var(--office-teams-text-subtle);\n text-align: center;\n}\n.empty-main button {\n padding: var(--office-space-2) var(--office-space-4);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font: inherit;\n}\n.main > office-ui-calendar-view {\n flex: 1;\n min-height: 0;\n}\n.calendar-back {\n display: none;\n}\n@media (max-width: 56em) {\n .calendar-back {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n padding: var(--office-space-2) var(--office-space-3);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n }\n .calendar-back button {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-2);\n min-height: var(--office-touch-target, 44px);\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n }\n .chat-head .chat-people {\n min-width: var(--office-touch-target, 44px);\n }\n}\n.show-calendar {\n display: none;\n}\n@media (max-width: 56em) {\n .show-calendar {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--office-space-2);\n width: calc(100% - 2 * var(--office-space-4));\n min-height: var(--office-touch-target, 44px);\n margin: var(--office-space-3) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n }\n}\n";
3376
+ function threadPane(state, client, openFile, close, highlightId = "") {
2469
3377
  const thread = state.thread;
2470
3378
  if (!thread) return nothing;
2471
3379
  return html`<aside class="thread-pane" aria-label="Thread">
@@ -2483,6 +3391,7 @@ function threadPane(state, client, openFile, close) {
2483
3391
  .messages=${[thread.root, ...thread.replies]}
2484
3392
  .pinnedIds=${state.pinned.map((p) => p.message.id)}
2485
3393
  self-id=${state.user.id}
3394
+ .highlightId=${highlightId}
2486
3395
  @office-chat-pin=${(e) => client.pinMessage(e.detail.messageId)}
2487
3396
  @office-chat-unpin=${(e) => client.unpinMessage(e.detail.messageId)}
2488
3397
  @office-chat-react=${(e) => client.toggleReaction(e.detail.messageId, e.detail.emoji)}
@@ -2498,6 +3407,7 @@ function threadPane(state, client, openFile, close) {
2498
3407
  .value=${state.draft.text}
2499
3408
  .files=${state.draft.files}
2500
3409
  .missingFiles=${state.draft.missingFiles}
3410
+ .mentionTargets=${state.mentionTargets}
2501
3411
  @office-chat-draft=${(e) => client.setDraft(e.detail)}
2502
3412
  placeholder="Reply in thread"
2503
3413
  @office-chat-typing=${() => client.notifyTyping()}
@@ -2514,7 +3424,10 @@ function followedThreads(state, client, open, unreadOnly, filter) {
2514
3424
  const threads = state.followedThreads.filter((thread) => !unreadOnly || thread.unread > 0);
2515
3425
  return html`<section class="followed-threads" aria-label="Followed threads">
2516
3426
  <h1>Followed threads</h1>
2517
- <p>Threads you follow on this device. Notifications are not enabled.</p>
3427
+ <p>
3428
+ Threads you follow on this device. Their replies appear in Activity and notify you according
3429
+ to your notification settings.
3430
+ </p>
2518
3431
  <fieldset>
2519
3432
  <legend>Automatically follow</legend>
2520
3433
  <label
@@ -2946,6 +3859,60 @@ function watchActivity(target, onActivity, throttleMs = 1e3, now = Date.now) {
2946
3859
  for (const name of ACTIVITY_EVENTS) target.removeEventListener(name, handler);
2947
3860
  };
2948
3861
  }
3862
+ function deliverNotification(item, settings, env, actions) {
3863
+ let result = "none";
3864
+ if (!env.hidden()) {
3865
+ actions.toast(item);
3866
+ result = "toast";
3867
+ } else if (env.permission() === "granted") {
3868
+ env.show(
3869
+ describeActivity(item),
3870
+ { body: item.preview, tag: item.id },
3871
+ () => actions.open(item)
3872
+ );
3873
+ result = "desktop";
3874
+ }
3875
+ if (result !== "none" && settings.sound) env.beep();
3876
+ return result;
3877
+ }
3878
+ function chime() {
3879
+ try {
3880
+ const Context = globalThis.AudioContext ?? globalThis.webkitAudioContext;
3881
+ if (!Context) return;
3882
+ const audio = new Context();
3883
+ const gain = audio.createGain();
3884
+ gain.gain.setValueAtTime(0.06, audio.currentTime);
3885
+ gain.gain.exponentialRampToValueAtTime(1e-4, audio.currentTime + 0.35);
3886
+ gain.connect(audio.destination);
3887
+ [660, 880].forEach((frequency, index) => {
3888
+ const tone = audio.createOscillator();
3889
+ tone.frequency.value = frequency;
3890
+ tone.connect(gain);
3891
+ tone.start(audio.currentTime + index * 0.12);
3892
+ tone.stop(audio.currentTime + index * 0.12 + 0.12);
3893
+ });
3894
+ globalThis.setTimeout(() => void audio.close(), 700);
3895
+ } catch {
3896
+ }
3897
+ }
3898
+ function browserDeliveryEnv() {
3899
+ return {
3900
+ hidden: () => globalThis.document?.visibilityState === "hidden",
3901
+ permission: () => globalThis.Notification?.permission ?? "unsupported",
3902
+ show(title, options, onClick) {
3903
+ try {
3904
+ const notification = new globalThis.Notification(title, options);
3905
+ notification.onclick = () => {
3906
+ globalThis.focus();
3907
+ notification.close();
3908
+ onClick();
3909
+ };
3910
+ } catch {
3911
+ }
3912
+ },
3913
+ beep: chime
3914
+ };
3915
+ }
2949
3916
 
2950
3917
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/add-tab-dialog.css?raw
2951
3918
  var add_tab_dialog_default = ":host {\n font: var(--office-font-size) var(--office-font);\n color: var(--office-foreground);\n}\ndialog {\n box-sizing: border-box;\n width: min(54em, 94vw);\n max-height: 90vh;\n padding: 0;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-lg);\n color: inherit;\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\ndialog::backdrop {\n background: var(--office-overlay);\n}\nheader,\n.app-heading,\nfooter {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n}\nheader {\n justify-content: space-between;\n padding: var(--office-space-4) var(--office-space-5);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n}\nh1,\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\nsection,\nform {\n padding: var(--office-space-5);\n}\nform {\n display: grid;\n gap: var(--office-space-3);\n}\n.apps {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10em, 1fr));\n gap: var(--office-space-3);\n}\nbutton,\ninput {\n font: inherit;\n color: inherit;\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n padding: var(--office-space-2) var(--office-space-3);\n}\nbutton {\n cursor: pointer;\n}\nbutton:hover:not(:disabled) {\n background: var(--office-selected);\n}\nbutton:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.app {\n display: grid;\n justify-items: start;\n text-align: start;\n gap: var(--office-space-2);\n padding: var(--office-space-4);\n}\n.app-heading h2 {\n flex: 1;\n}\n.badge {\n display: grid;\n place-items: center;\n width: 2.6em;\n height: 2.6em;\n border-radius: var(--office-radius-md);\n color: var(--office-call-foreground);\n background: var(--office-file-other);\n font-weight: var(--office-font-weight-bold);\n}\n.badge[data-kind='docx'] {\n background: var(--office-file-docx);\n}\n.badge[data-kind='xlsx'] {\n background: var(--office-file-xlsx);\n}\n.badge[data-kind='pptx'] {\n background: var(--office-file-pptx);\n}\n.badge[data-kind='vsdx'] {\n background: var(--office-file-vsdx);\n}\nlabel {\n display: grid;\n gap: var(--office-space-2);\n}\ninput {\n min-width: 0;\n}\nsmall,\n.hint,\nsection > p {\n color: var(--office-teams-text-subtle);\n line-height: 1.5;\n}\nsmall {\n display: block;\n}\n.files {\n margin: 0;\n padding: var(--office-space-2);\n max-height: 18em;\n overflow: auto;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius);\n}\n.file {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-3);\n border-radius: var(--office-radius);\n cursor: pointer;\n}\n.file:has(input:checked) {\n background: var(--office-teams-brand-subtle);\n}\n.file strong {\n overflow-wrap: anywhere;\n}\n[role='alert'] {\n color: var(--office-danger);\n}\nfooter {\n justify-content: flex-end;\n}\n.post-choice {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n}\nbutton.primary {\n color: var(--office-teams-brand-foreground);\n border-color: var(--office-teams-brand);\n background: var(--office-teams-brand);\n}\nbutton.primary:hover:not(:disabled) {\n background: var(--office-teams-brand-hover);\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n";
@@ -3297,17 +4264,724 @@ function defineTeamsCreateChannelDialog(registry = globalThis.customElements) {
3297
4264
  registry.define("teams-create-channel-dialog", TeamsCreateChannelDialog);
3298
4265
  }
3299
4266
 
4267
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/form-dialog.css?raw
4268
+ var form_dialog_default = "/* The frame shared by the Chat and Calendar dialogs: a modal <dialog> with a header, a form and a\n footer, in the same look as the create-channel dialog. */\n:host {\n font: var(--office-font-size) var(--office-font);\n color: var(--office-foreground);\n}\ndialog {\n box-sizing: border-box;\n width: min(40em, 94vw);\n max-height: 90dvh;\n padding: 0;\n overflow: auto;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-lg);\n background: var(--office-popover);\n color: inherit;\n box-shadow: var(--office-shadow-teams);\n}\ndialog::backdrop {\n background: var(--office-overlay);\n}\ndialog > header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--office-space-3);\n padding: var(--office-space-4) var(--office-space-5);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\nform,\n.body {\n display: grid;\n gap: var(--office-space-3);\n padding: var(--office-space-5);\n}\nlabel,\n.field {\n display: grid;\n gap: var(--office-space-2);\n font-weight: var(--office-font-weight-bold);\n}\ninput:not([type='checkbox']),\ntextarea,\nselect {\n min-width: 0;\n width: 100%;\n box-sizing: border-box;\n padding: var(--office-space-3);\n font: inherit;\n font-weight: normal;\n color: inherit;\n background: var(--office-field-background);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius-md);\n}\ntextarea {\n min-height: 6em;\n resize: vertical;\n}\np,\nsmall {\n margin: 0;\n color: var(--office-teams-text-subtle);\n line-height: 1.5;\n}\n.issue {\n color: var(--office-danger);\n}\nfooter {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: var(--office-space-2);\n}\nbutton {\n padding: var(--office-space-2) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\nbutton:hover {\n background: var(--office-selected);\n}\nbutton.primary {\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n border-color: transparent;\n}\nbutton.primary:hover {\n background: var(--office-teams-brand-hover);\n}\nbutton.danger {\n color: var(--office-danger);\n}\nbutton:disabled {\n opacity: 0.5;\n cursor: default;\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n@media (max-width: 40em) {\n /* Full screen on a phone: the dialog is a page, not a floating card. */\n dialog {\n width: 100vw;\n max-width: 100vw;\n height: 100dvh;\n max-height: 100dvh;\n border-radius: 0;\n }\n button,\n input:not([type='checkbox']),\n select {\n min-height: 2.75em;\n }\n}\n";
4269
+
4270
+ // src/teams/app/dialog-base.ts
4271
+ var dialogStyles = (own = "") => withStyles(`${form_dialog_default}
4272
+ ${own}`);
4273
+ var TeamsDialog = class extends TeamsElement {
4274
+ static properties = {
4275
+ open: { type: Boolean, reflect: true }
4276
+ };
4277
+ constructor() {
4278
+ super();
4279
+ this.open = false;
4280
+ }
4281
+ updated() {
4282
+ const dialog = this.renderRoot.querySelector("dialog");
4283
+ if (!dialog) return;
4284
+ if (this.open && !dialog.open) dialog.showModal();
4285
+ else if (!this.open && dialog.open) dialog.close();
4286
+ }
4287
+ requestClose() {
4288
+ this.fire("teams-dialog-close", {});
4289
+ }
4290
+ /** The dialog frame with a title and a close button around `body`. */
4291
+ frame(title, body, closeLabel = `Close ${title.toLowerCase()}`) {
4292
+ return html`<dialog
4293
+ aria-labelledby="title"
4294
+ @cancel=${(event) => {
4295
+ event.preventDefault();
4296
+ this.requestClose();
4297
+ }}
4298
+ >
4299
+ <header>
4300
+ <h2 id="title">${title}</h2>
4301
+ <button type="button" aria-label=${closeLabel} @click=${this.requestClose}>×</button>
4302
+ </header>
4303
+ ${body}
4304
+ </dialog>`;
4305
+ }
4306
+ };
4307
+
4308
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/new-chat-dialog.css?raw
4309
+ var new_chat_dialog_default = "office-ui-people-picker {\n font-weight: normal;\n}\n";
4310
+
4311
+ // src/teams/app/new-chat-dialog.ts
4312
+ var TeamsNewChatDialog = class extends TeamsDialog {
4313
+ static styles = dialogStyles(new_chat_dialog_default);
4314
+ static properties = {
4315
+ ...TeamsDialog.properties,
4316
+ contacts: { attribute: false },
4317
+ create: { attribute: false },
4318
+ selected: { state: true },
4319
+ name: { state: true },
4320
+ issue: { state: true }
4321
+ };
4322
+ constructor() {
4323
+ super();
4324
+ this.contacts = [];
4325
+ this.create = void 0;
4326
+ this.selected = [];
4327
+ this.name = "";
4328
+ this.issue = "";
4329
+ }
4330
+ willUpdate(changed) {
4331
+ if (changed.has("open") && this.open) {
4332
+ this.selected = [];
4333
+ this.name = "";
4334
+ this.issue = "";
4335
+ }
4336
+ }
4337
+ submit(event) {
4338
+ event.preventDefault();
4339
+ if (this.selected.length === 0) {
4340
+ this.issue = "Pick at least one person.";
4341
+ return;
4342
+ }
4343
+ if (!this.create?.(this.selected, this.selected.length > 1 ? this.name : ""))
4344
+ this.issue = "The chat could not be started.";
4345
+ }
4346
+ render() {
4347
+ const group = this.selected.length > 1;
4348
+ return this.frame(
4349
+ "New chat",
4350
+ html`<form @submit=${this.submit}>
4351
+ <div class="field">
4352
+ To
4353
+ <office-ui-people-picker
4354
+ label="People to chat with"
4355
+ .contacts=${this.contacts}
4356
+ .selected=${this.selected}
4357
+ @office-people-change=${(e) => {
4358
+ this.selected = e.detail.selected.slice(0, MAX_CHAT_MEMBERS - 1);
4359
+ this.issue = "";
4360
+ }}
4361
+ ></office-ui-people-picker>
4362
+ </div>
4363
+ ${group ? html`<label
4364
+ >Group name (optional)<input
4365
+ maxlength=${MAX_CHANNEL_NAME_CHARS}
4366
+ .value=${this.name}
4367
+ placeholder="Name this group chat"
4368
+ @input=${(e) => this.name = e.target.value}
4369
+ /></label>` : nothing}
4370
+ <small
4371
+ >${group ? "Everyone you pick joins one group chat. Only its members can read it." : "Pick one person for a 1:1 chat, or several for a group chat."}</small
4372
+ >
4373
+ ${this.issue ? html`<p class="issue" role="alert">${this.issue}</p>` : nothing}
4374
+ <footer>
4375
+ <button type="button" @click=${this.requestClose}>Cancel</button>
4376
+ <button type="submit" class="primary" ?disabled=${this.selected.length === 0}>
4377
+ ${group ? "Start group chat" : "Start chat"}
4378
+ </button>
4379
+ </footer>
4380
+ </form>`
4381
+ );
4382
+ }
4383
+ };
4384
+ function defineTeamsNewChatDialog(registry = globalThis.customElements) {
4385
+ if (registry && !registry.get("teams-new-chat-dialog"))
4386
+ registry.define("teams-new-chat-dialog", TeamsNewChatDialog);
4387
+ }
4388
+
4389
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/chat-people-dialog.css?raw
4390
+ var chat_people_dialog_default = "h3 {\n margin: var(--office-space-2) 0 0;\n font-size: var(--office-font-size);\n}\n.rename {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: end;\n gap: var(--office-space-2);\n padding: 0;\n}\n.members {\n display: grid;\n gap: var(--office-space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.members li {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-1) 0;\n}\n.name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\noffice-ui-people-picker {\n font-weight: normal;\n}\n";
4391
+
4392
+ // src/teams/app/chat-people-dialog.ts
4393
+ var TeamsChatPeopleDialog = class extends TeamsDialog {
4394
+ static styles = dialogStyles(chat_people_dialog_default);
4395
+ static properties = {
4396
+ ...TeamsDialog.properties,
4397
+ chat: { attribute: false },
4398
+ contacts: { attribute: false },
4399
+ name: { state: true },
4400
+ adding: { state: true }
4401
+ };
4402
+ constructor() {
4403
+ super();
4404
+ this.chat = null;
4405
+ this.contacts = [];
4406
+ this.name = "";
4407
+ this.adding = [];
4408
+ }
4409
+ willUpdate(changed) {
4410
+ if (changed.has("open") && this.open) {
4411
+ this.name = this.chat?.name ?? "";
4412
+ this.adding = [];
4413
+ }
4414
+ }
4415
+ render() {
4416
+ const chat = this.chat;
4417
+ if (!chat) return this.frame("People", html``);
4418
+ const group = chat.kind === "group";
4419
+ return this.frame(
4420
+ "People",
4421
+ html`<div class="body">
4422
+ ${group ? html`<form
4423
+ class="rename"
4424
+ @submit=${(e) => {
4425
+ e.preventDefault();
4426
+ this.fire("teams-chat-rename", { id: chat.id, name: this.name });
4427
+ }}
4428
+ >
4429
+ <label
4430
+ >Group name<input
4431
+ maxlength=${MAX_CHANNEL_NAME_CHARS}
4432
+ .value=${this.name}
4433
+ placeholder="Name this group chat"
4434
+ @input=${(e) => this.name = e.target.value}
4435
+ /></label>
4436
+ <button type="submit">Rename</button>
4437
+ </form>` : nothing}
4438
+ <h3>In this chat (${chat.members.length})</h3>
4439
+ <ul class="members">
4440
+ ${chat.members.map(
4441
+ (m) => html`<li>
4442
+ <office-ui-avatar
4443
+ size="sm"
4444
+ name=${m.name}
4445
+ seed=${m.id}
4446
+ presence=${m.availability}
4447
+ ></office-ui-avatar>
4448
+ <span class="name">${m.name}${m.self ? " (you)" : ""}</span>
4449
+ ${group && !m.self && chat.members.length > 1 ? html`<button
4450
+ type="button"
4451
+ aria-label=${`Remove ${m.name}`}
4452
+ @click=${() => this.fire("teams-chat-remove", { id: chat.id, userId: m.id })}
4453
+ >
4454
+ Remove
4455
+ </button>` : nothing}
4456
+ </li>`
4457
+ )}
4458
+ </ul>
4459
+ ${group ? html`<h3>Add people</h3>
4460
+ <office-ui-people-picker
4461
+ label="People to add"
4462
+ .contacts=${this.contacts}
4463
+ .exclude=${chat.members.map((m) => m.id)}
4464
+ .selected=${this.adding}
4465
+ @office-people-change=${(e) => this.adding = e.detail.selected}
4466
+ ></office-ui-people-picker>
4467
+ <footer>
4468
+ <button
4469
+ type="button"
4470
+ class="primary"
4471
+ ?disabled=${this.adding.length === 0}
4472
+ @click=${() => {
4473
+ this.fire("teams-chat-add", { id: chat.id, userIds: this.adding });
4474
+ this.adding = [];
4475
+ }}
4476
+ >
4477
+ Add to chat
4478
+ </button>
4479
+ </footer>` : html`<p>
4480
+ People cannot be added to or removed from a 1:1 chat. Start a group chat instead.
4481
+ </p>`}
4482
+ <footer>
4483
+ ${chat.canLeave ? html`<button
4484
+ type="button"
4485
+ class="danger"
4486
+ @click=${() => this.fire("teams-chat-leave", { id: chat.id })}
4487
+ >
4488
+ Leave chat
4489
+ </button>` : nothing}
4490
+ <button type="button" @click=${this.requestClose}>Done</button>
4491
+ </footer>
4492
+ </div>`
4493
+ );
4494
+ }
4495
+ };
4496
+ function defineTeamsChatPeopleDialog(registry = globalThis.customElements) {
4497
+ if (registry && !registry.get("teams-chat-people-dialog"))
4498
+ registry.define("teams-chat-people-dialog", TeamsChatPeopleDialog);
4499
+ }
4500
+
4501
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/meeting-dialog.css?raw
4502
+ var meeting_dialog_default = ".times {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--office-space-3);\n}\noffice-ui-people-picker {\n font-weight: normal;\n}\n@media (max-width: 40em) {\n .times {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n";
4503
+
4504
+ // src/teams/app/meeting-dialog.ts
4505
+ var SLOT = 30 * MINUTE_MS;
4506
+ var NOT_SAVED = "The meeting was not saved. Your details are kept here.";
4507
+ function toLocalInput(ms) {
4508
+ const d = new Date(ms);
4509
+ const p = (n) => String(n).padStart(2, "0");
4510
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
4511
+ }
4512
+ var TeamsMeetingDialog = class extends TeamsDialog {
4513
+ static styles = dialogStyles(meeting_dialog_default);
4514
+ static properties = {
4515
+ ...TeamsDialog.properties,
4516
+ meeting: { attribute: false },
4517
+ start: { type: Number },
4518
+ occurrence: { type: Number },
4519
+ contacts: { attribute: false },
4520
+ channels: { attribute: false },
4521
+ save: { attribute: false },
4522
+ meetingTitle: { state: true },
4523
+ description: { state: true },
4524
+ startText: { state: true },
4525
+ endText: { state: true },
4526
+ repeat: { state: true },
4527
+ endMode: { state: true },
4528
+ untilText: { state: true },
4529
+ countText: { state: true },
4530
+ channelId: { state: true },
4531
+ attendees: { state: true },
4532
+ issue: { state: true }
4533
+ };
4534
+ constructor() {
4535
+ super();
4536
+ this.meeting = null;
4537
+ this.start = 0;
4538
+ this.occurrence = 0;
4539
+ this.contacts = [];
4540
+ this.channels = [];
4541
+ this.save = void 0;
4542
+ this.meetingTitle = "";
4543
+ this.description = "";
4544
+ this.startText = "";
4545
+ this.endText = "";
4546
+ this.repeat = "none";
4547
+ this.endMode = "never";
4548
+ this.untilText = "";
4549
+ this.countText = "10";
4550
+ this.channelId = "";
4551
+ this.attendees = [];
4552
+ this.issue = "";
4553
+ }
4554
+ willUpdate(changed) {
4555
+ if (!changed.has("open") || !this.open) return;
4556
+ const m = this.meeting;
4557
+ const one = m && this.occurrence ? occurrenceAt(m, this.start || m.start) : null;
4558
+ const begin = one?.start ?? m?.start ?? (this.start || Math.ceil(Date.now() / SLOT) * SLOT);
4559
+ this.meetingTitle = one?.title ?? m?.title ?? "";
4560
+ this.description = one?.description ?? m?.description ?? "";
4561
+ this.startText = toLocalInput(begin);
4562
+ this.endText = toLocalInput(one?.end ?? m?.end ?? begin + SLOT);
4563
+ this.repeat = m?.repeat ?? "none";
4564
+ this.endMode = m?.count !== void 0 ? "count" : m?.until !== void 0 ? "date" : "never";
4565
+ this.countText = String(m?.count ?? 10);
4566
+ this.untilText = toLocalInput(m?.until ?? begin + 28 * 864e5).slice(0, 10);
4567
+ this.channelId = m?.channelId ?? "";
4568
+ this.attendees = m?.attendees.map((a) => a.id) ?? [];
4569
+ this.issue = "";
4570
+ }
4571
+ moveStart(value) {
4572
+ const before = new Date(this.startText).getTime();
4573
+ const after = new Date(value).getTime();
4574
+ const end = new Date(this.endText).getTime();
4575
+ this.startText = value;
4576
+ if (Number.isFinite(before) && Number.isFinite(after) && Number.isFinite(end))
4577
+ this.endText = toLocalInput(end + (after - before));
4578
+ this.issue = "";
4579
+ }
4580
+ validCount() {
4581
+ const n = Number(this.countText);
4582
+ return Number.isInteger(n) && n >= 1 && n <= MAX_RECURRENCE_COUNT;
4583
+ }
4584
+ untilTime() {
4585
+ return (/* @__PURE__ */ new Date(`${this.untilText}T23:59:59.999`)).getTime();
4586
+ }
4587
+ validUntil(start) {
4588
+ const until = this.untilTime();
4589
+ return Number.isFinite(until) && until >= start;
4590
+ }
4591
+ /** The end of the repeat to store: a count, the end of a day, or none (null clears an old end). */
4592
+ recurrenceEnd() {
4593
+ if (this.repeat === "none" || this.endMode === "never") return { count: null, until: null };
4594
+ return this.endMode === "count" ? { count: Number(this.countText), until: null } : { count: null, until: this.untilTime() };
4595
+ }
4596
+ submit(event) {
4597
+ event.preventDefault();
4598
+ const start = new Date(this.startText).getTime();
4599
+ const end = new Date(this.endText).getTime();
4600
+ if (!this.meetingTitle.trim()) this.issue = "Add a title.";
4601
+ else if (!Number.isFinite(start) || !Number.isFinite(end)) this.issue = "Pick a start and end.";
4602
+ else if (end <= start) this.issue = "The meeting must end after it starts.";
4603
+ else if (end - start > 24 * 60 * MINUTE_MS) this.issue = "A meeting can last at most 24 hours.";
4604
+ else if (this.occurrence) {
4605
+ const one = { title: this.meetingTitle, description: this.description, start, end };
4606
+ if (!this.save?.(one, this.meeting?.id, this.occurrence)) this.issue = NOT_SAVED;
4607
+ } else if (this.repeat !== "none" && this.endMode === "count" && !this.validCount())
4608
+ this.issue = `Enter a number of events from 1 to ${MAX_RECURRENCE_COUNT}.`;
4609
+ else if (this.repeat !== "none" && this.endMode === "date" && !this.validUntil(start))
4610
+ this.issue = "The series must end on or after its first event.";
4611
+ else if (!this.save?.(
4612
+ {
4613
+ title: this.meetingTitle,
4614
+ description: this.description,
4615
+ start,
4616
+ end,
4617
+ repeat: this.repeat,
4618
+ ...this.recurrenceEnd(),
4619
+ attendeeIds: this.channelId ? [] : this.attendees,
4620
+ ...this.channelId ? { channelId: this.channelId } : {}
4621
+ },
4622
+ this.meeting?.id
4623
+ ))
4624
+ this.issue = NOT_SAVED;
4625
+ }
4626
+ render() {
4627
+ const editing = this.meeting !== null;
4628
+ const single = this.occurrence !== 0;
4629
+ return this.frame(
4630
+ single ? "Edit this event" : editing ? "Edit meeting" : "New meeting",
4631
+ html`<form @submit=${this.submit}>
4632
+ <label
4633
+ >Title<input
4634
+ maxlength=${MAX_MEETING_TITLE}
4635
+ .value=${this.meetingTitle}
4636
+ placeholder="Add a title"
4637
+ @input=${(e) => {
4638
+ this.meetingTitle = e.target.value;
4639
+ this.issue = "";
4640
+ }}
4641
+ /></label>
4642
+ <div class="times">
4643
+ <label
4644
+ >Start<input
4645
+ type="datetime-local"
4646
+ .value=${this.startText}
4647
+ @change=${(e) => this.moveStart(e.target.value)}
4648
+ /></label>
4649
+ <label
4650
+ >End<input
4651
+ type="datetime-local"
4652
+ .value=${this.endText}
4653
+ @change=${(e) => {
4654
+ this.endText = e.target.value;
4655
+ this.issue = "";
4656
+ }}
4657
+ /></label>
4658
+ </div>
4659
+ ${single ? html`<small>Only this event changes. The rest of the series stays as it is.</small>` : html`
4660
+ <label
4661
+ >Repeat<select
4662
+ .value=${this.repeat}
4663
+ @change=${(e) => this.repeat = e.target.value}
4664
+ >
4665
+ <option value="none">Does not repeat</option>
4666
+ <option value="daily">Every day</option>
4667
+ <option value="weekly">Every week</option>
4668
+ </select></label
4669
+ >
4670
+ ${this.repeat === "none" ? nothing : html`<label
4671
+ >Ends<select
4672
+ .value=${this.endMode}
4673
+ @change=${(e) => this.endMode = e.target.value}
4674
+ >
4675
+ <option value="never">Never</option>
4676
+ <option value="date">On a date</option>
4677
+ <option value="count">After a number of events</option>
4678
+ </select></label
4679
+ >
4680
+ ${this.endMode === "date" ? html`<label
4681
+ >End date<input
4682
+ type="date"
4683
+ .value=${this.untilText}
4684
+ @change=${(e) => {
4685
+ this.untilText = e.target.value;
4686
+ this.issue = "";
4687
+ }}
4688
+ /></label>` : nothing}
4689
+ ${this.endMode === "count" ? html`<label
4690
+ >Number of events<input
4691
+ type="number"
4692
+ min="1"
4693
+ max=${MAX_RECURRENCE_COUNT}
4694
+ .value=${this.countText}
4695
+ @input=${(e) => {
4696
+ this.countText = e.target.value;
4697
+ this.issue = "";
4698
+ }}
4699
+ /></label>` : nothing}`}
4700
+ <label
4701
+ >Channel (optional)<select
4702
+ ?disabled=${editing}
4703
+ .value=${this.channelId}
4704
+ @change=${(e) => this.channelId = e.target.value}
4705
+ >
4706
+ <option value="">No channel: invite people</option>
4707
+ ${this.channels.map((c) => html`<option value=${c.id}># ${c.name}</option>`)}
4708
+ </select></label
4709
+ >
4710
+ ${this.channelId ? html`<small
4711
+ >A channel meeting posts a meeting card in the channel and shows in
4712
+ everyone's calendar.</small
4713
+ >` : html`<div class="field">
4714
+ Attendees
4715
+ <office-ui-people-picker
4716
+ label="Attendees"
4717
+ .contacts=${this.contacts}
4718
+ .selected=${this.attendees}
4719
+ @office-people-change=${(e) => this.attendees = e.detail.selected}
4720
+ ></office-ui-people-picker>
4721
+ </div>`}
4722
+ `}
4723
+ <label
4724
+ >Details (optional)<textarea
4725
+ maxlength=${MAX_MEETING_DESCRIPTION}
4726
+ .value=${this.description}
4727
+ placeholder="Add an agenda or notes"
4728
+ @input=${(e) => this.description = e.target.value}
4729
+ ></textarea>
4730
+ </label>
4731
+ <small
4732
+ >Times are shown in your time zone. The organizer's zone keeps repeats on schedule.</small
4733
+ >
4734
+ ${this.issue ? html`<p class="issue" role="alert">${this.issue}</p>` : nothing}
4735
+ <footer>
4736
+ <button type="button" @click=${this.requestClose}>Cancel</button>
4737
+ <button type="submit" class="primary">${editing ? "Save" : "Send"}</button>
4738
+ </footer>
4739
+ </form>`
4740
+ );
4741
+ }
4742
+ };
4743
+ function defineTeamsMeetingDialog(registry = globalThis.customElements) {
4744
+ if (registry && !registry.get("teams-meeting-dialog"))
4745
+ registry.define("teams-meeting-dialog", TeamsMeetingDialog);
4746
+ }
4747
+
4748
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/meeting-details.css?raw
4749
+ var meeting_details_default = ".when {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n color: var(--office-foreground);\n font-weight: var(--office-font-weight-bold);\n}\nh3 {\n margin: 0 0 var(--office-space-1);\n font-size: var(--office-font-size);\n}\n.people ul {\n margin: 0;\n padding-inline-start: var(--office-space-5);\n}\n.description {\n color: var(--office-foreground);\n white-space: pre-wrap;\n}\n.scope {\n display: grid;\n gap: var(--office-space-3);\n}\n.scope fieldset {\n display: grid;\n gap: var(--office-space-2);\n margin: 0;\n padding: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n}\n.scope label {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n}\nfooter[hidden] {\n display: none;\n}\n";
4750
+
4751
+ // src/teams/app/meeting-details.ts
4752
+ var REPEAT_TEXT = { none: "", daily: "Repeats every day", weekly: "Repeats every week" };
4753
+ var TeamsMeetingDetails = class extends TeamsDialog {
4754
+ static styles = dialogStyles(meeting_details_default);
4755
+ static properties = {
4756
+ ...TeamsDialog.properties,
4757
+ meeting: { attribute: false },
4758
+ occurrenceStart: { type: Number },
4759
+ zone: { type: String },
4760
+ channelName: { type: String },
4761
+ pending: { state: true },
4762
+ scope: { state: true }
4763
+ };
4764
+ constructor() {
4765
+ super();
4766
+ this.meeting = null;
4767
+ this.occurrenceStart = 0;
4768
+ this.zone = "";
4769
+ this.channelName = "";
4770
+ this.pending = null;
4771
+ this.scope = "occurrence";
4772
+ }
4773
+ willUpdate(changed) {
4774
+ if (changed.has("open") || changed.has("meeting") || changed.has("occurrenceStart"))
4775
+ this.pending = null;
4776
+ }
4777
+ emit(name) {
4778
+ if (this.meeting) this.fire(name, { meetingId: this.meeting.id });
4779
+ }
4780
+ recurrenceId() {
4781
+ const m = this.meeting;
4782
+ return m ? occurrenceAt(m, this.occurrenceStart || m.start)?.recurrenceId ?? m.start : 0;
4783
+ }
4784
+ /** Edit or cancel: a repeating meeting asks which events first. */
4785
+ change(action) {
4786
+ if (this.meeting?.repeat === "none") this.finish(action, "series");
4787
+ else {
4788
+ this.pending = action;
4789
+ this.scope = "occurrence";
4790
+ }
4791
+ }
4792
+ finish(action, scope) {
4793
+ if (!this.meeting) return;
4794
+ this.fire(`teams-meeting-${action}`, {
4795
+ meetingId: this.meeting.id,
4796
+ scope,
4797
+ recurrenceId: this.recurrenceId()
4798
+ });
4799
+ this.pending = null;
4800
+ }
4801
+ endText(m) {
4802
+ const date = (ms) => new Intl.DateTimeFormat([], {
4803
+ dateStyle: "medium",
4804
+ ...this.zone ? { timeZone: this.zone } : {}
4805
+ }).format(ms);
4806
+ if (m.count !== void 0) return m.count === 1 ? ", 1 time" : `, ${m.count} times`;
4807
+ return m.until !== void 0 ? `, until ${date(m.until)}` : "";
4808
+ }
4809
+ scopeChoice(action) {
4810
+ const options = [
4811
+ ["occurrence", action === "edit" ? "This event" : "This event only"],
4812
+ ["series", action === "edit" ? "All events in the series" : "All events in the series"]
4813
+ ];
4814
+ return html`<form
4815
+ class="scope"
4816
+ @submit=${(e) => {
4817
+ e.preventDefault();
4818
+ this.finish(action, this.scope);
4819
+ }}
4820
+ >
4821
+ <fieldset>
4822
+ <legend>
4823
+ ${action === "edit" ? "Edit recurring meeting" : "Cancel recurring meeting"}
4824
+ </legend>
4825
+ ${options.map(
4826
+ ([value, label]) => html`<label
4827
+ ><input
4828
+ type="radio"
4829
+ name="scope"
4830
+ .value=${value}
4831
+ .checked=${this.scope === value}
4832
+ @change=${() => this.scope = value}
4833
+ />
4834
+ ${label}</label
4835
+ >`
4836
+ )}
4837
+ </fieldset>
4838
+ <footer>
4839
+ <button type="button" @click=${() => this.pending = null}>Back</button>
4840
+ <button type="submit" class=${action === "cancel" ? "danger" : "primary"}>
4841
+ ${action === "edit" ? "Continue" : "Cancel meeting"}
4842
+ </button>
4843
+ </footer>
4844
+ </form>`;
4845
+ }
4846
+ render() {
4847
+ const m = this.meeting;
4848
+ if (!m) return this.frame("Meeting", html``);
4849
+ const occurrence = occurrenceAt(m, this.occurrenceStart || m.start);
4850
+ const start = occurrence?.start ?? (this.occurrenceStart || m.start);
4851
+ const end = occurrence?.end ?? start + (m.end - m.start);
4852
+ const description = occurrence?.description ?? m.description;
4853
+ const hasChat = Boolean(m.chatId ?? m.channelId);
4854
+ return this.frame(
4855
+ occurrence?.title ?? m.title,
4856
+ html`<div class="body">
4857
+ <p class="when">${icon("calendar")} ${formatWhen(start, end)}</p>
4858
+ ${m.repeat !== "none" ? html`<p>${REPEAT_TEXT[m.repeat]}${this.endText(m)}</p>` : nothing}
4859
+ ${m.zone !== this.zone ? html`<p>Scheduled in the organizer's time zone, ${m.zone.replaceAll("_", " ")}.</p>` : nothing}
4860
+ <p>Organized by ${m.mine ? "you" : m.organizerName}</p>
4861
+ ${m.channelId ? html`<p>
4862
+ Channel meeting in # ${this.channelName || "a channel"}. Everyone in the channel can
4863
+ join.
4864
+ </p>` : html`<div class="people">
4865
+ <h3>Attendees (${m.attendees.length + 1})</h3>
4866
+ <ul>
4867
+ <li>${m.organizerName} (organizer)</li>
4868
+ ${m.attendees.map((a) => html`<li>${a.name}</li>`)}
4869
+ </ul>
4870
+ </div>`}
4871
+ ${description ? html`<p class="description">${description}</p>` : nothing}
4872
+ ${this.pending ? this.scopeChoice(this.pending) : nothing}
4873
+ <footer ?hidden=${this.pending !== null}>
4874
+ <button type="button" class="primary" @click=${() => this.emit("teams-meeting-join")}>
4875
+ ${m.live ? "Join now" : "Join"}
4876
+ </button>
4877
+ ${hasChat ? html`<button type="button" @click=${() => this.emit("teams-meeting-chat")}>
4878
+ Meeting chat
4879
+ </button>` : nothing}
4880
+ <button type="button" @click=${() => this.emit("teams-meeting-ics")}>Export .ics</button>
4881
+ ${m.mine ? html`<button type="button" @click=${() => this.change("edit")}>Edit</button>
4882
+ <button type="button" class="danger" @click=${() => this.change("cancel")}>
4883
+ Cancel meeting
4884
+ </button>` : nothing}
4885
+ </footer>
4886
+ </div>`,
4887
+ "Close meeting details"
4888
+ );
4889
+ }
4890
+ };
4891
+ function defineTeamsMeetingDetails(registry = globalThis.customElements) {
4892
+ if (registry && !registry.get("teams-meeting-details"))
4893
+ registry.define("teams-meeting-details", TeamsMeetingDetails);
4894
+ }
4895
+ function chatHeader(chat, on) {
4896
+ const other = chat.kind === "direct" ? chat.members.find((m) => !m.self) : void 0;
4897
+ const online = chat.members.filter((m) => !m.self && m.availability !== "offline").length;
4898
+ return html`<header class="channel-head chat-head">
4899
+ <button
4900
+ type="button"
4901
+ class="back"
4902
+ aria-label="Back to chats"
4903
+ title="Back to chats"
4904
+ @click=${on.back}
4905
+ >
4906
+ ${icon("back")}
4907
+ </button>
4908
+ ${other ? html`<office-ui-avatar
4909
+ name=${other.name}
4910
+ seed=${other.id}
4911
+ presence=${other.availability}
4912
+ ></office-ui-avatar>` : html`<span class="group-mark" aria-hidden="true"
4913
+ >${icon(chat.meetingId ? "calendar" : "users")}</span
4914
+ >`}
4915
+ <h1>${chat.title}</h1>
4916
+ <span class="topic"
4917
+ >${chat.kind === "direct" ? other?.availability ?? "" : `${chat.members.length} people${online ? `, ${online} online` : ""}`}</span
4918
+ >
4919
+ <div class="people" aria-label="People in this chat">
4920
+ ${chat.members.slice(0, 5).map(
4921
+ (m) => html`<office-ui-avatar
4922
+ size="sm"
4923
+ name=${m.name}
4924
+ seed=${m.id}
4925
+ presence=${m.availability}
4926
+ ></office-ui-avatar>`
4927
+ )}
4928
+ ${chat.members.length > 5 ? html`<span class="more">+${chat.members.length - 5}</span>` : nothing}
4929
+ </div>
4930
+ <button type="button" class="chat-people" @click=${on.people}>${icon("users")} People</button>
4931
+ <button type="button" class="meet" @click=${on.call}>
4932
+ ${chat.live ? "Join call" : "Call"}
4933
+ </button>
4934
+ </header>`;
4935
+ }
4936
+ function meetingCards(meetings, now) {
4937
+ const out = {};
4938
+ for (const m of meetings) {
4939
+ const next = occurrencesBetween(m, now, now + 400 * 864e5)[0];
4940
+ out[m.id] = {
4941
+ title: m.title,
4942
+ start: next?.start ?? m.start,
4943
+ end: next?.end ?? m.end,
4944
+ live: m.live
4945
+ };
4946
+ }
4947
+ return out;
4948
+ }
4949
+
4950
+ // src/teams/app/calendar-app.ts
4951
+ function downloadText(name, text, type = "text/calendar") {
4952
+ const doc = globalThis.document;
4953
+ if (!doc || typeof URL.createObjectURL !== "function") return false;
4954
+ const url = URL.createObjectURL(new Blob([text], { type: `${type};charset=utf-8` }));
4955
+ const link = doc.createElement("a");
4956
+ link.href = url;
4957
+ link.download = name;
4958
+ doc.body.append(link);
4959
+ link.click();
4960
+ link.remove();
4961
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
4962
+ return true;
4963
+ }
4964
+
3300
4965
  // src/teams/app/teams-app.ts
3301
4966
  var LOCAL_THEME = THEME_CSS.replaceAll(
3302
4967
  ':root[data-office-theme="dark"]',
3303
4968
  ':host([data-office-theme="dark"])'
3304
4969
  ).replaceAll(':root:not([data-office-theme="light"])', ':host([data-office-theme="system"])').replaceAll(":root", ":host([data-office-theme])");
3305
4970
  var RAIL = [
4971
+ { id: "activity", label: "Activity", icon: "bell" },
4972
+ { id: "chat", label: "Chat", icon: "message" },
3306
4973
  { id: "teams", label: "Teams", icon: "users" },
3307
- { id: "calls", label: "Calls", icon: "phone" },
3308
- { id: "files", label: "Files", icon: "folder" },
3309
- { id: "followed", label: "Followed threads", shortLabel: "Threads", icon: "chat" },
3310
- { id: "drafts", label: "Drafts", icon: "chat" }
4974
+ { id: "calendar", label: "Calendar", icon: "calendar" },
4975
+ { id: "calls", label: "Calls", icon: "phone", secondary: true },
4976
+ { id: "files", label: "Files", icon: "folder", secondary: true },
4977
+ {
4978
+ id: "followed",
4979
+ label: "Followed threads",
4980
+ shortLabel: "Threads",
4981
+ icon: "chat",
4982
+ secondary: true
4983
+ },
4984
+ { id: "drafts", label: "Drafts", icon: "chat", secondary: true }
3311
4985
  ];
3312
4986
  var TeamsApp = class extends LitElement {
3313
4987
  static styles = [unsafeCSS(LOCAL_THEME), unsafeCSS(teams_app_default)];
@@ -3323,6 +4997,10 @@ var TeamsApp = class extends LitElement {
3323
4997
  preview: { state: true },
3324
4998
  addingTab: { state: true },
3325
4999
  creatingChannel: { state: true },
5000
+ newChat: { state: true },
5001
+ chatPeople: { state: true },
5002
+ meetingForm: { state: true },
5003
+ meetingDetails: { state: true },
3326
5004
  rail: { state: true },
3327
5005
  tab: { state: true },
3328
5006
  panel: { state: true },
@@ -3330,6 +5008,9 @@ var TeamsApp = class extends LitElement {
3330
5008
  settingsOpen: { state: true },
3331
5009
  mobileView: { state: true },
3332
5010
  toast: { state: true },
5011
+ activityToasts: { state: true },
5012
+ activityFilter: { state: true },
5013
+ highlightId: { state: true },
3333
5014
  identity: { state: true },
3334
5015
  followedUnreadOnly: { state: true },
3335
5016
  theme: { state: true },
@@ -3337,10 +5018,18 @@ var TeamsApp = class extends LitElement {
3337
5018
  chatDensity: { state: true },
3338
5019
  showAppNames: { state: true }
3339
5020
  };
3340
- teams = new TeamsController(this, (text) => this.notify(text));
5021
+ teams = new TeamsController(
5022
+ this,
5023
+ (text) => this.notify(text),
5024
+ (item) => this.announce(item)
5025
+ );
3341
5026
  toastTimer;
5027
+ activityToastTimers = /* @__PURE__ */ new Map();
5028
+ highlightTimer;
3342
5029
  stopActivity;
3343
5030
  openRequest = 0;
5031
+ /** The channel last open in the Teams app, restored when you come back from Chat. */
5032
+ lastChannelId = "";
3344
5033
  retainedTab;
3345
5034
  themeStorageKey = "";
3346
5035
  popoutRequested = new URL(globalThis.location?.href ?? "https://workspace.invalid").searchParams.get(
@@ -3363,6 +5052,10 @@ var TeamsApp = class extends LitElement {
3363
5052
  this.retainedTab = void 0;
3364
5053
  this.addingTab = false;
3365
5054
  this.creatingChannel = false;
5055
+ this.newChat = false;
5056
+ this.chatPeople = false;
5057
+ this.meetingForm = null;
5058
+ this.meetingDetails = null;
3366
5059
  this.rail = "teams";
3367
5060
  this.tab = "posts";
3368
5061
  this.panel = "";
@@ -3370,6 +5063,9 @@ var TeamsApp = class extends LitElement {
3370
5063
  this.settingsOpen = false;
3371
5064
  this.mobileView = "conversation";
3372
5065
  this.toast = "";
5066
+ this.activityToasts = [];
5067
+ this.activityFilter = "all";
5068
+ this.highlightId = "";
3373
5069
  this.identity = null;
3374
5070
  this.followedUnreadOnly = false;
3375
5071
  this.theme = "system";
@@ -3394,6 +5090,10 @@ var TeamsApp = class extends LitElement {
3394
5090
  defineTeamsPinnedBar();
3395
5091
  defineTeamsAddTabDialog();
3396
5092
  defineTeamsCreateChannelDialog();
5093
+ defineTeamsNewChatDialog();
5094
+ defineTeamsChatPeopleDialog();
5095
+ defineTeamsMeetingDialog();
5096
+ defineTeamsMeetingDetails();
3397
5097
  this.stopActivity = watchActivity(this.ownerDocument, () => this.teams.client?.noteActivity());
3398
5098
  super.connectedCallback();
3399
5099
  }
@@ -3423,6 +5123,8 @@ var TeamsApp = class extends LitElement {
3423
5123
  this.tab = "posts";
3424
5124
  this.addingTab = false;
3425
5125
  this.creatingChannel = false;
5126
+ this.newChat = this.chatPeople = false;
5127
+ this.meetingForm = this.meetingDetails = null;
3426
5128
  this.settingsOpen = false;
3427
5129
  this.openRequest++;
3428
5130
  const named = this.userName ? { id: this.userId || loadIdentity()?.id || crypto.randomUUID(), name: this.userName } : this.identity ?? loadIdentity();
@@ -3451,6 +5153,8 @@ var TeamsApp = class extends LitElement {
3451
5153
  updated() {
3452
5154
  this.setAttribute("data-chat-density", this.chatDensity);
3453
5155
  const state = this.teams.state;
5156
+ if (state?.channels.some((c) => c.id === state.selectedChannelId))
5157
+ this.lastChannelId = state.selectedChannelId;
3454
5158
  if (state?.thread && document.visibilityState === "visible" && this.renderRoot.querySelector(".thread-pane"))
3455
5159
  this.teams.client?.markThreadRead(state.selectedChannelId, state.thread.root.id);
3456
5160
  }
@@ -3459,6 +5163,25 @@ var TeamsApp = class extends LitElement {
3459
5163
  clearTimeout(this.toastTimer);
3460
5164
  this.toastTimer = setTimeout(() => this.toast = "", 4e3);
3461
5165
  }
5166
+ /** Deliver a notification the core allowed: a toast in a visible tab, else a desktop one. */
5167
+ announce(item) {
5168
+ const settings = this.teams.client?.getState().notificationSettings;
5169
+ if (!settings) return;
5170
+ deliverNotification(item, settings, browserDeliveryEnv(), {
5171
+ toast: (entry) => {
5172
+ this.activityToasts = [
5173
+ entry,
5174
+ ...this.activityToasts.filter((t) => t.id !== entry.id)
5175
+ ].slice(0, 3);
5176
+ clearTimeout(this.activityToastTimers.get(entry.id));
5177
+ this.activityToastTimers.set(
5178
+ entry.id,
5179
+ setTimeout(() => this.dismissToast(entry.id), 6e3)
5180
+ );
5181
+ },
5182
+ open: (entry) => entry.kind === "meeting" ? this.openMeeting(entry.messageId) : void this.openActivity(entry.id)
5183
+ });
5184
+ }
3462
5185
  // ---- intent -> client actions -----------------------------------------------------------
3463
5186
  async openFile(a, mode) {
3464
5187
  if (!this.canLeaveContent()) return;
@@ -3530,6 +5253,9 @@ var TeamsApp = class extends LitElement {
3530
5253
  this.openRequest++;
3531
5254
  this.preview = null;
3532
5255
  clearTimeout(this.toastTimer);
5256
+ for (const timer of this.activityToastTimers.values()) clearTimeout(timer);
5257
+ this.activityToastTimers.clear();
5258
+ clearTimeout(this.highlightTimer);
3533
5259
  this.stopActivity?.();
3534
5260
  this.stopActivity = void 0;
3535
5261
  super.disconnectedCallback();
@@ -3576,7 +5302,8 @@ var TeamsApp = class extends LitElement {
3576
5302
  void this.teams.client?.openCall(channelId);
3577
5303
  this.mobileView = "conversation";
3578
5304
  this.meeting = true;
3579
- this.rail = "teams";
5305
+ const target = channelId ?? this.teams.state?.selectedChannelId;
5306
+ this.rail = this.teams.state?.chats.some((c) => c.id === target) ? "chat" : "teams";
3580
5307
  }
3581
5308
  selectChannel(id) {
3582
5309
  if (!this.closePreview()) return false;
@@ -3587,7 +5314,7 @@ var TeamsApp = class extends LitElement {
3587
5314
  this.teams.client?.search("");
3588
5315
  this.meeting = false;
3589
5316
  this.tab = "posts";
3590
- this.rail = "teams";
5317
+ this.rail = this.teams.state?.chats.some((c) => c.id === id) ? "chat" : "teams";
3591
5318
  this.mobileView = "conversation";
3592
5319
  return true;
3593
5320
  }
@@ -3606,7 +5333,7 @@ var TeamsApp = class extends LitElement {
3606
5333
  }
3607
5334
  /** Which screen the compact shell shows: the channel or call list, or the content itself. */
3608
5335
  mobileScreen(s) {
3609
- const listed = this.rail === "teams" || this.rail === "calls";
5336
+ const listed = ["teams", "calls", "activity", "chat", "calendar"].includes(this.rail);
3610
5337
  return listed && this.mobileView === "list" && !(this.meeting && s.call) ? "list" : "content";
3611
5338
  }
3612
5339
  // ---- template ---------------------------------------------------------------------------
@@ -3634,11 +5361,12 @@ var TeamsApp = class extends LitElement {
3634
5361
  <div class="body">
3635
5362
  <office-ui-app-rail
3636
5363
  .hideLabels=${!this.showAppNames}
3637
- .items=${RAIL.map((i) => ({ ...i, ...i.id === "calls" && s.channels.some((c) => c.live) ? { badge: s.channels.filter((c) => c.live).length } : {} }))}
5364
+ .items=${RAIL.map((i) => ({ ...i, ...i.id === "calls" && s.channels.some((c) => c.live) ? { badge: s.channels.filter((c) => c.live).length } : {}, ...i.id === "activity" && s.activityUnread ? { badge: s.activityUnread } : {}, ...i.id === "chat" && s.chats.some((c) => c.unread) ? { badge: s.chats.reduce((n, c) => n + c.unread, 0) } : {} }))}
3638
5365
  selected=${this.rail}
3639
5366
  @office-rail-select=${(e) => {
3640
5367
  if (!this.closePreview()) return;
3641
5368
  this.settingsOpen = false;
5369
+ if (e.detail.id === "teams") this.leaveChatSelection();
3642
5370
  this.rail = e.detail.id;
3643
5371
  this.meeting = false;
3644
5372
  this.mobileView = "list";
@@ -3679,6 +5407,8 @@ var TeamsApp = class extends LitElement {
3679
5407
  );
3680
5408
  }}
3681
5409
  .followSettings=${s.threadFollowSettings}
5410
+ .notificationSettings=${s.notificationSettings}
5411
+ @teams-settings-notifications=${(event) => this.teams.client?.setNotificationSettings(event.detail)}
3682
5412
  .userName=${s.user.name}
3683
5413
  @teams-settings-theme=${(event) => {
3684
5414
  if (!["system", "light", "dark"].includes(event.detail.theme)) return;
@@ -3709,7 +5439,8 @@ var TeamsApp = class extends LitElement {
3709
5439
  @teams-create-channel-close=${() => this.creatingChannel = false}
3710
5440
  ></teams-create-channel-dialog>
3711
5441
  ${this.toast ? html`<div class="toast" role="status">${this.toast}</div>` : nothing}
3712
-
5442
+ ${this.activityToasts.length ? html`<div class="activity-toasts">${this.activityToasts.map((t) => this.activityToastView(t))}</div>` : nothing}
5443
+ ${this.chatDialogs(s)} ${this.calendarDialogs(s)}
3713
5444
  <teams-add-tab-dialog
3714
5445
  .open=${this.addingTab}
3715
5446
  .client=${this.teams.client}
@@ -3788,7 +5519,10 @@ var TeamsApp = class extends LitElement {
3788
5519
  }}
3789
5520
  >
3790
5521
  <strong>${h.message.authorName}</strong>
3791
- <span class="in">in # ${h.channelName}</span>
5522
+ <span class="in"
5523
+ >in
5524
+ ${s.chats.some((c) => c.id === h.channelId) ? "" : "# "}${h.channelName}</span
5525
+ >
3792
5526
  <span class="snippet">${h.message.text.slice(0, 100)}</span>
3793
5527
  </button>
3794
5528
  </li>`
@@ -3826,7 +5560,64 @@ var TeamsApp = class extends LitElement {
3826
5560
  </header>
3827
5561
  `;
3828
5562
  }
5563
+ dismissToast(id) {
5564
+ clearTimeout(this.activityToastTimers.get(id));
5565
+ this.activityToastTimers.delete(id);
5566
+ this.activityToasts = this.activityToasts.filter((t) => t.id !== id);
5567
+ }
5568
+ activityToastView(item) {
5569
+ return html`<div class="activity-toast" role="status" data-kind=${item.kind}>
5570
+ <span class="toast-text"
5571
+ ><strong>${describeActivity(item)}</strong
5572
+ ><span class="toast-preview">${item.preview}</span></span
5573
+ >
5574
+ <button
5575
+ type="button"
5576
+ @click=${() => {
5577
+ this.dismissToast(item.id);
5578
+ if (item.kind === "meeting") this.openMeeting(item.messageId);
5579
+ else void this.openActivity(item.id);
5580
+ }}
5581
+ >
5582
+ View
5583
+ </button>
5584
+ <button type="button" aria-label="Dismiss" @click=${() => this.dismissToast(item.id)}>
5585
+ ${icon("close")}
5586
+ </button>
5587
+ </div>`;
5588
+ }
3829
5589
  sidebar(s) {
5590
+ if (this.rail === "chat")
5591
+ return html`<office-ui-chat-sidebar
5592
+ .chats=${s.chats}
5593
+ selected-id=${s.selectedChannelId}
5594
+ @office-chat-select=${(e) => this.selectChannel(e.detail.id)}
5595
+ @office-chat-new=${() => this.newChat = true}
5596
+ ></office-ui-chat-sidebar>`;
5597
+ if (this.rail === "calendar")
5598
+ return html`<office-ui-calendar-agenda
5599
+ .meetings=${s.meetings}
5600
+ .zone=${s.viewerZone}
5601
+ @office-agenda-open=${(e) => this.meetingDetails = { meetingId: e.detail.meetingId, start: e.detail.start }}
5602
+ @office-agenda-join=${(e) => void this.joinMeeting(e.detail.meetingId)}
5603
+ @office-agenda-new=${() => this.meetingForm = { meetingId: "", start: 0 }}
5604
+ ></office-ui-calendar-agenda>
5605
+ <button
5606
+ type="button"
5607
+ class="show-calendar"
5608
+ @click=${() => this.mobileView = "conversation"}
5609
+ >
5610
+ ${icon("calendar")} Open calendar
5611
+ </button>`;
5612
+ if (this.rail === "activity")
5613
+ return html`<office-ui-activity-feed
5614
+ .items=${s.activity}
5615
+ .filter=${this.activityFilter}
5616
+ @office-activity-filter=${(e) => this.activityFilter = e.detail.filter}
5617
+ @office-activity-open=${(e) => void this.openActivity(e.detail.id)}
5618
+ @office-activity-read=${(e) => this.teams.client?.markActivityRead(e.detail.id, e.detail.read)}
5619
+ @office-activity-read-all=${() => this.teams.client?.markAllActivityRead()}
5620
+ ></office-ui-activity-feed>`;
3830
5621
  if (this.rail === "calls") {
3831
5622
  const live2 = s.channels.filter((c) => c.live);
3832
5623
  return html`
@@ -3882,13 +5673,18 @@ var TeamsApp = class extends LitElement {
3882
5673
  }
3883
5674
  );
3884
5675
  if (s.call && this.meeting) return this.meetingView(s);
5676
+ if (this.rail === "calendar") return this.calendarMain(s);
5677
+ if (this.rail === "chat" && !s.chats.some((c) => c.id === s.selectedChannelId))
5678
+ return this.chatEmpty();
3885
5679
  return html`
3886
5680
  ${s.call && !this.meeting ? html`<div class="callbar">
3887
- You are in a call in # ${s.call.channelName}
5681
+ You are in a call in ${this.callHeading(s.call)}
3888
5682
  <button
3889
5683
  type="button"
3890
5684
  @click=${() => {
3891
- if (this.selectChannel(s.call.channelId)) this.meeting = true;
5685
+ if (s.call.kind === "meeting") {
5686
+ if (this.closePreview()) [this.rail, this.meeting] = ["calendar", true];
5687
+ } else if (this.selectChannel(s.call.channelId)) this.meeting = true;
3892
5688
  }}
3893
5689
  >
3894
5690
  Return to call
@@ -3899,52 +5695,57 @@ var TeamsApp = class extends LitElement {
3899
5695
  }
3900
5696
  conversation(s, compact = false) {
3901
5697
  const channel = s.channel;
5698
+ const chat = s.chats.find((x) => x.id === s.selectedChannelId);
3902
5699
  const c = this.teams.client;
3903
5700
  const compose = s.editing ?? s.replyingTo;
3904
5701
  const sharedTab = s.tabs.find((t) => t.id === this.tab);
3905
5702
  if (sharedTab) this.retainedTab = sharedTab;
3906
5703
  const activeTab = sharedTab ?? (this.retainedTab?.id === this.tab && this.retainedTab.channelId === s.selectedChannelId ? this.retainedTab : void 0);
3907
5704
  return html`
3908
- ${compact ? nothing : html`<header class="channel-head">
3909
- <button
3910
- type="button"
3911
- class="back"
3912
- aria-label="Back to channels"
3913
- title="Back to channels"
3914
- @click=${() => this.mobileView = "list"}
3915
- >
3916
- ${icon("back")}
3917
- </button>
3918
- <h1><span class="hash">#</span> ${channel?.name ?? "No channel yet"}</h1>
3919
- <nav class="tabs" role="tablist">
3920
- ${["posts", "files"].map(
5705
+ ${compact ? nothing : chat ? chatHeader(chat, {
5706
+ back: () => this.mobileView = "list",
5707
+ call: () => this.startMeeting(),
5708
+ people: () => this.chatPeople = true
5709
+ }) : html`<header class="channel-head">
5710
+ <button
5711
+ type="button"
5712
+ class="back"
5713
+ aria-label="Back to channels"
5714
+ title="Back to channels"
5715
+ @click=${() => this.mobileView = "list"}
5716
+ >
5717
+ ${icon("back")}
5718
+ </button>
5719
+ <h1><span class="hash">#</span> ${channel?.name ?? "No channel yet"}</h1>
5720
+ <nav class="tabs" role="tablist">
5721
+ ${["posts", "files"].map(
3921
5722
  (t) => html`<button
3922
- type="button"
3923
- role="tab"
3924
- aria-selected=${String(this.tab === t)}
3925
- @click=${() => this.selectTab(t)}
3926
- >
3927
- ${t === "posts" ? "Posts" : "Shared"}
3928
- </button>`
5723
+ type="button"
5724
+ role="tab"
5725
+ aria-selected=${String(this.tab === t)}
5726
+ @click=${() => this.selectTab(t)}
5727
+ >
5728
+ ${t === "posts" ? "Posts" : "Shared"}
5729
+ </button>`
3929
5730
  )}
3930
- ${s.tabs.map((t) => html`<button type="button" role="tab" aria-selected=${String(this.tab === t.id)} @click=${() => this.selectTab(t.id)}>${t.name}</button>`)}
3931
- </nav>
3932
- <button
3933
- type="button"
3934
- aria-label="Add tab"
3935
- title="Add tab"
3936
- ?disabled=${!channel}
3937
- @click=${() => this.addingTab = !this.addingTab}
3938
- >
3939
- +
3940
- </button>
3941
- ${sharedTab ? html`<button
3942
- type="button"
3943
- data-tab-conversation
3944
- aria-label="Show tab conversation"
3945
- title="Show tab conversation"
3946
- aria-expanded=${String(s.thread?.root.tabId === sharedTab.id)}
3947
- @click=${async () => {
5731
+ ${s.tabs.map((t) => html`<button type="button" role="tab" aria-selected=${String(this.tab === t.id)} @click=${() => this.selectTab(t.id)}>${t.name}</button>`)}
5732
+ </nav>
5733
+ <button
5734
+ type="button"
5735
+ aria-label="Add tab"
5736
+ title="Add tab"
5737
+ ?disabled=${!channel}
5738
+ @click=${() => this.addingTab = !this.addingTab}
5739
+ >
5740
+ +
5741
+ </button>
5742
+ ${sharedTab ? html`<button
5743
+ type="button"
5744
+ data-tab-conversation
5745
+ aria-label="Show tab conversation"
5746
+ title="Show tab conversation"
5747
+ aria-expanded=${String(s.thread?.root.tabId === sharedTab.id)}
5748
+ @click=${async () => {
3948
5749
  if (s.thread?.root.tabId === sharedTab.id) {
3949
5750
  await this.closeThread();
3950
5751
  return;
@@ -3957,49 +5758,51 @@ var TeamsApp = class extends LitElement {
3957
5758
  await this.updateComplete;
3958
5759
  this.shadowRoot?.querySelector("[data-thread-heading]")?.focus();
3959
5760
  }}
3960
- >
3961
- ${icon("chat")}
3962
- </button>` : nothing}
3963
- ${sharedTab?.createdBy === s.user.id ? html`<office-ui-menu-button
3964
- label="Tab options"
3965
- icon="more"
3966
- icon-only
3967
- @office-command=${(event) => {
5761
+ >
5762
+ ${icon("chat")}
5763
+ </button>` : nothing}
5764
+ ${sharedTab?.createdBy === s.user.id ? html`<office-ui-menu-button
5765
+ label="Tab options"
5766
+ icon="more"
5767
+ icon-only
5768
+ @office-command=${(event) => {
3968
5769
  if (event.detail.command === "rename-tab") {
3969
5770
  const name = globalThis.prompt?.("Tab name", sharedTab.name)?.trim();
3970
5771
  if (name) c?.renameTab(sharedTab.id, name);
3971
5772
  } else if (event.detail.command === "remove-tab") {
3972
5773
  if (!this.canLeaveContent()) return;
3973
- if (globalThis.confirm?.(`Remove ${sharedTab.name} from this channel?`) === true && c?.removeTab(sharedTab.id)) {
5774
+ if (globalThis.confirm?.(
5775
+ `Remove ${sharedTab.name} from this channel?`
5776
+ ) === true && c?.removeTab(sharedTab.id)) {
3974
5777
  this.openRequest++;
3975
5778
  this.preview = null;
3976
5779
  this.tab = "posts";
3977
5780
  }
3978
5781
  }
3979
5782
  }}
3980
- ><office-ui-menu-item
3981
- label="Rename tab"
3982
- command="rename-tab"
3983
- ></office-ui-menu-item
3984
- ><office-ui-menu-item
3985
- label="Remove tab"
3986
- command="remove-tab"
3987
- ></office-ui-menu-item
3988
- ></office-ui-menu-button>` : nothing}
3989
- <span class="topic">${channel?.topic ?? ""}</span>
3990
- <div class="people" aria-label="People online">
3991
- ${s.people.slice(0, 5).map((p) => html`<office-ui-avatar size="sm" name=${p.name} seed=${p.id} color=${p.color} presence=${p.availability}></office-ui-avatar>`)}
3992
- ${s.people.length > 5 ? html`<span class="more">+${s.people.length - 5}</span>` : nothing}
3993
- </div>
3994
- <button
3995
- type="button"
3996
- class="meet"
3997
- ?disabled=${!channel}
3998
- @click=${() => this.startMeeting()}
3999
- >
4000
- ${s.channels.find((x) => x.id === s.selectedChannelId)?.live ? "Join call" : "Meet now"}
4001
- </button>
4002
- </header>`}
5783
+ ><office-ui-menu-item
5784
+ label="Rename tab"
5785
+ command="rename-tab"
5786
+ ></office-ui-menu-item
5787
+ ><office-ui-menu-item
5788
+ label="Remove tab"
5789
+ command="remove-tab"
5790
+ ></office-ui-menu-item
5791
+ ></office-ui-menu-button>` : nothing}
5792
+ <span class="topic">${channel?.topic ?? ""}</span>
5793
+ <div class="people" aria-label="People online">
5794
+ ${s.people.slice(0, 5).map((p) => html`<office-ui-avatar size="sm" name=${p.name} seed=${p.id} color=${p.color} presence=${p.availability}></office-ui-avatar>`)}
5795
+ ${s.people.length > 5 ? html`<span class="more">+${s.people.length - 5}</span>` : nothing}
5796
+ </div>
5797
+ <button
5798
+ type="button"
5799
+ class="meet"
5800
+ ?disabled=${!channel}
5801
+ @click=${() => this.startMeeting()}
5802
+ >
5803
+ ${s.channels.find((x) => x.id === s.selectedChannelId)?.live ? "Join call" : "Meet now"}
5804
+ </button>
5805
+ </header>`}
4003
5806
  ${c ? messageTransfers(s, c) : nothing}
4004
5807
  ${activeTab && !sharedTab && !compact ? html`<p role="status">This tab was removed from the channel. Your open copy remains here until you close it.</p>` : nothing}
4005
5808
  ${activeTab && !compact ? html`<div
@@ -4019,7 +5822,8 @@ var TeamsApp = class extends LitElement {
4019
5822
  s,
4020
5823
  c,
4021
5824
  (attachment) => void this.openFile(attachment),
4022
- () => void this.closeThread()
5825
+ () => void this.closeThread(),
5826
+ this.highlightId
4023
5827
  ) : nothing}
4024
5828
  </div>` : this.tab === "files" && !compact ? this.fileList(s.files, s) : html`<div class="conversation-grid" data-thread=${String(!!s.thread && !compact)}>
4025
5829
  <div class="conversation-main">
@@ -4033,6 +5837,9 @@ var TeamsApp = class extends LitElement {
4033
5837
  .replyCounts=${compact ? {} : s.replyCounts}
4034
5838
  .pinnedIds=${s.pinned.map((p) => p.message.id)}
4035
5839
  self-id=${s.user.id}
5840
+ .highlightId=${this.highlightId}
5841
+ .meetings=${meetingCards(s.meetings, Date.now())}
5842
+ @office-chat-meeting=${(e) => e.detail.action === "join" ? void this.joinMeeting(e.detail.meetingId) : this.openMeeting(e.detail.meetingId)}
4036
5843
  @office-chat-pin=${(e) => c?.pinMessage(e.detail.messageId)}
4037
5844
  @office-chat-unpin=${(e) => c?.unpinMessage(e.detail.messageId)}
4038
5845
  @office-chat-react=${(e) => c?.toggleReaction(e.detail.messageId, e.detail.emoji)}
@@ -4049,9 +5856,10 @@ var TeamsApp = class extends LitElement {
4049
5856
  .value=${s.draft.text}
4050
5857
  .files=${s.draft.files}
4051
5858
  .missingFiles=${s.draft.missingFiles}
5859
+ .mentionTargets=${s.mentionTargets}
4052
5860
  @office-chat-draft=${(e) => c?.setDraft(e.detail)}
4053
5861
  ?disabled=${!channel}
4054
- placeholder=${channel ? `Message # ${channel.name}` : "Create a channel first"}
5862
+ placeholder=${chat ? `Message ${chat.title}` : channel ? `Message # ${channel.name}` : "Create a channel first"}
4055
5863
  @office-chat-typing=${() => c?.notifyTyping()}
4056
5864
  @office-chat-cancel=${() => c?.cancelCompose()}
4057
5865
  @office-chat-send=${(e) => void c?.send(e.detail)}
@@ -4062,7 +5870,8 @@ var TeamsApp = class extends LitElement {
4062
5870
  s,
4063
5871
  c,
4064
5872
  (attachment) => void this.openFile(attachment),
4065
- () => void this.closeThread()
5873
+ () => void this.closeThread(),
5874
+ this.highlightId
4066
5875
  ) : nothing}
4067
5876
  </div>`}
4068
5877
  `;
@@ -4074,17 +5883,41 @@ var TeamsApp = class extends LitElement {
4074
5883
  this.addingTab = false;
4075
5884
  this.creatingChannel = false;
4076
5885
  }
4077
- /** Scroll a pinned message into view, opening its thread when it is a reply. */
5886
+ /** Scroll a message into view and highlight it, opening its thread when it is a reply. */
4078
5887
  async revealMessage(id) {
4079
5888
  const message = this.teams.state?.messages.find((m) => m.id === id);
4080
5889
  if (!message) return;
4081
- if (message.replyTo) return this.openThread(id);
4082
- const list = this.shadowRoot?.querySelector(".conversation-main office-ui-chat-list");
4083
- const row = Array.from(
4084
- list?.shadowRoot?.querySelectorAll("[data-message-id]") ?? []
4085
- ).find((element) => element.dataset.messageId === id);
5890
+ if (message.replyTo) await this.openThread(id);
5891
+ await this.updateComplete;
5892
+ const lists = [...this.renderRoot.querySelectorAll("office-ui-chat-list")];
5893
+ await Promise.all(lists.map((list) => list.updateComplete));
5894
+ const row = lists.flatMap((list) => [
5895
+ ...list.shadowRoot?.querySelectorAll("[data-message-id]") ?? []
5896
+ ]).find((element) => element.dataset.messageId === id);
4086
5897
  row?.scrollIntoView({ block: "center" });
4087
5898
  row?.focus();
5899
+ this.highlightId = id;
5900
+ clearTimeout(this.highlightTimer);
5901
+ this.highlightTimer = setTimeout(() => this.highlightId = "", 2500);
5902
+ }
5903
+ /** Open an Activity item: its channel or thread, scrolled to the message, and mark it read. */
5904
+ async openActivity(id) {
5905
+ const c = this.teams.client;
5906
+ const item = c?.getState().activity.find((entry) => entry.id === id);
5907
+ if (!c || !item || !this.closePreview()) return;
5908
+ c.markActivityRead(id);
5909
+ this.settingsOpen = false;
5910
+ this.addingTab = false;
5911
+ this.creatingChannel = false;
5912
+ this.meeting = false;
5913
+ this.tab = "posts";
5914
+ this.mobileView = "conversation";
5915
+ c.select(item.channelId);
5916
+ c.search("");
5917
+ if (item.rootId === item.messageId) c.closeThread();
5918
+ await new Promise((resolve) => setTimeout(resolve, 0));
5919
+ await this.updateComplete;
5920
+ await this.revealMessage(item.messageId);
4088
5921
  }
4089
5922
  async openThread(id, action) {
4090
5923
  const client = this.teams.client;
@@ -4095,6 +5928,166 @@ var TeamsApp = class extends LitElement {
4095
5928
  await this.updateComplete;
4096
5929
  this.shadowRoot?.querySelector("[data-thread-heading]")?.focus();
4097
5930
  }
5931
+ callHeading(call) {
5932
+ return call.kind === "channel" ? `# ${call.channelName}` : call.channelName;
5933
+ }
5934
+ /** Teams rail: if a chat is selected, go back to the channel you were in. */
5935
+ leaveChatSelection() {
5936
+ const st = this.teams.state;
5937
+ const c = this.teams.client;
5938
+ if (!st || !c || !st.chats.some((x) => x.id === st.selectedChannelId)) return;
5939
+ const target = st.channels.some((x) => x.id === this.lastChannelId) ? this.lastChannelId : st.channels[0]?.id;
5940
+ if (target) c.select(target);
5941
+ }
5942
+ chatEmpty() {
5943
+ return html`<div class="empty-main">
5944
+ <h1>Chat</h1>
5945
+ <p>Pick a chat from the list, or start a new one.</p>
5946
+ <button type="button" @click=${() => this.newChat = true}>New chat</button>
5947
+ </div>`;
5948
+ }
5949
+ commitChat(ids, name) {
5950
+ const c = this.client;
5951
+ if (!c || !this.closePreview()) return false;
5952
+ const id = c.createGroupChat(ids, name);
5953
+ if (!id) return false;
5954
+ this.newChat = false;
5955
+ this.rail = "chat";
5956
+ this.meeting = false;
5957
+ this.tab = "posts";
5958
+ this.mobileView = "conversation";
5959
+ return true;
5960
+ }
5961
+ chatDialogs(s) {
5962
+ const c = this.teams.client;
5963
+ const chat = s.chats.find((x) => x.id === s.selectedChannelId) ?? null;
5964
+ return html`
5965
+ <teams-new-chat-dialog
5966
+ ?open=${this.newChat}
5967
+ .contacts=${s.contacts}
5968
+ .create=${(ids, name) => this.commitChat(ids, name)}
5969
+ @teams-dialog-close=${() => this.newChat = false}
5970
+ ></teams-new-chat-dialog>
5971
+ <teams-chat-people-dialog
5972
+ ?open=${this.chatPeople && chat !== null}
5973
+ .chat=${chat}
5974
+ .contacts=${s.contacts}
5975
+ @teams-chat-rename=${(e) => c?.renameChat(e.detail.id, e.detail.name)}
5976
+ @teams-chat-add=${(e) => c?.addToChat(e.detail.id, e.detail.userIds)}
5977
+ @teams-chat-remove=${(e) => c?.removeFromChat(e.detail.id, e.detail.userId)}
5978
+ @teams-chat-leave=${(e) => {
5979
+ if (globalThis.confirm?.("Leave this chat? You will stop receiving its messages.") === false)
5980
+ return;
5981
+ if (c?.leaveChat(e.detail.id)) {
5982
+ this.chatPeople = false;
5983
+ this.mobileView = "list";
5984
+ }
5985
+ }}
5986
+ @teams-dialog-close=${() => this.chatPeople = false}
5987
+ ></teams-chat-people-dialog>
5988
+ `;
5989
+ }
5990
+ // ---- calendar ---------------------------------------------------------------------------
5991
+ calendarMain(s) {
5992
+ return html`
5993
+ <div class="calendar-back">
5994
+ <button type="button" @click=${() => this.mobileView = "list"}>
5995
+ ${icon("back")} Upcoming meetings
5996
+ </button>
5997
+ </div>
5998
+ <office-ui-calendar-view
5999
+ .meetings=${s.meetings}
6000
+ .zone=${s.viewerZone}
6001
+ @office-calendar-open=${(e) => this.meetingDetails = { meetingId: e.detail.meetingId, start: e.detail.start }}
6002
+ @office-calendar-new=${(e) => this.meetingForm = { meetingId: "", start: e.detail.start }}
6003
+ ></office-ui-calendar-view>
6004
+ `;
6005
+ }
6006
+ /** Open the calendar on a meeting's details, for example from its reminder. */
6007
+ openMeeting(meetingId, start = 0) {
6008
+ if (!this.closePreview()) return;
6009
+ this.settingsOpen = false;
6010
+ this.meeting = false;
6011
+ this.rail = "calendar";
6012
+ this.mobileView = "conversation";
6013
+ this.meetingDetails = { meetingId, start };
6014
+ }
6015
+ async joinMeeting(meetingId) {
6016
+ if (!this.closePreview()) return;
6017
+ this.meetingDetails = null;
6018
+ this.settingsOpen = false;
6019
+ if (await this.teams.client?.joinMeeting(meetingId)) {
6020
+ this.meeting = true;
6021
+ this.rail = "calendar";
6022
+ this.mobileView = "conversation";
6023
+ }
6024
+ }
6025
+ saveMeeting(form, id, occurrence) {
6026
+ const c = this.client;
6027
+ if (!c) return false;
6028
+ const saved = id && occurrence ? c.editOccurrence(id, occurrence, {
6029
+ start: form.start,
6030
+ end: form.end,
6031
+ title: form.title,
6032
+ ...form.description !== void 0 ? { description: form.description } : {}
6033
+ }) : id ? c.updateMeeting(id, form) : c.createMeeting(form);
6034
+ if (!saved) return false;
6035
+ this.meetingForm = null;
6036
+ return true;
6037
+ }
6038
+ calendarDialogs(s) {
6039
+ const c = this.teams.client;
6040
+ const editing = this.meetingForm?.meetingId ? s.meetings.find((m) => m.id === this.meetingForm?.meetingId) ?? null : null;
6041
+ const details = this.meetingDetails ? s.meetings.find((m) => m.id === this.meetingDetails?.meetingId) ?? null : null;
6042
+ return html`
6043
+ <teams-meeting-dialog
6044
+ ?open=${this.meetingForm !== null}
6045
+ .meeting=${editing}
6046
+ .start=${this.meetingForm?.start ?? 0}
6047
+ .occurrence=${this.meetingForm?.occurrence ?? 0}
6048
+ .contacts=${s.contacts}
6049
+ .channels=${s.channels.map((ch) => ({ id: ch.id, name: ch.name }))}
6050
+ .save=${(form, id, occurrence) => this.saveMeeting(form, id, occurrence)}
6051
+ @teams-dialog-close=${() => this.meetingForm = null}
6052
+ ></teams-meeting-dialog>
6053
+ <teams-meeting-details
6054
+ ?open=${details !== null}
6055
+ .meeting=${details}
6056
+ .occurrenceStart=${this.meetingDetails?.start ?? 0}
6057
+ .zone=${s.viewerZone}
6058
+ .channelName=${s.channels.find((ch) => ch.id === details?.channelId)?.name ?? ""}
6059
+ @teams-meeting-join=${(e) => void this.joinMeeting(e.detail.meetingId)}
6060
+ @teams-meeting-chat=${(e) => {
6061
+ const target = s.meetings.find((m) => m.id === e.detail.meetingId);
6062
+ const conversation = target?.chatId ?? target?.channelId;
6063
+ this.meetingDetails = null;
6064
+ if (conversation) this.selectChannel(conversation);
6065
+ }}
6066
+ @teams-meeting-edit=${(e) => {
6067
+ const { meetingId, scope, recurrenceId } = e.detail;
6068
+ const shown = this.meetingDetails?.start ?? 0;
6069
+ this.meetingDetails = null;
6070
+ this.meetingForm = scope === "occurrence" ? { meetingId, start: shown, occurrence: recurrenceId } : { meetingId, start: 0 };
6071
+ }}
6072
+ @teams-meeting-cancel=${(e) => {
6073
+ const { meetingId, scope, recurrenceId } = e.detail;
6074
+ if (scope === "occurrence") {
6075
+ if (c?.cancelOccurrence(meetingId, recurrenceId)) this.meetingDetails = null;
6076
+ return;
6077
+ }
6078
+ const ask = details?.repeat === "none" ? "Cancel this meeting for everyone?" : "Cancel every event in this series?";
6079
+ if (globalThis.confirm?.(ask) === false) return;
6080
+ if (c?.cancelMeeting(meetingId)) this.meetingDetails = null;
6081
+ }}
6082
+ @teams-meeting-ics=${(e) => {
6083
+ const file = c?.meetingIcs(e.detail.meetingId);
6084
+ if (file && !downloadText(file.name, file.text))
6085
+ this.notify("Could not start the download");
6086
+ }}
6087
+ @teams-dialog-close=${() => this.meetingDetails = null}
6088
+ ></teams-meeting-details>
6089
+ `;
6090
+ }
4098
6091
  async resumeDraft(context) {
4099
6092
  if (!this.closePreview() || !this.teams.client?.openDraft(context)) return;
4100
6093
  this.rail = "teams";
@@ -4163,7 +6156,7 @@ var TeamsApp = class extends LitElement {
4163
6156
  if (call.phase === "prejoin")
4164
6157
  return html`
4165
6158
  <office-ui-prejoin
4166
- heading=${`# ${call.channelName}`}
6159
+ heading=${this.callHeading(call)}
4167
6160
  name=${s.user.name}
4168
6161
  .preview=${call.preview}
4169
6162
  ?audio=${call.prejoin.audio}
@@ -4177,7 +6170,7 @@ var TeamsApp = class extends LitElement {
4177
6170
  return html`
4178
6171
  <section class="meeting">
4179
6172
  <office-ui-call-controls
4180
- heading=${`# ${call.channelName}`}
6173
+ heading=${this.callHeading(call)}
4181
6174
  started-at=${call.startedAt ?? 0}
4182
6175
  ?mic=${self?.state.audio}
4183
6176
  ?camera=${self?.state.video}
@@ -4262,22 +6255,32 @@ function createTeams(options) {
4262
6255
  var TEAMS_TAGS = [
4263
6256
  "office-ui-avatar",
4264
6257
  "office-ui-app-rail",
6258
+ "office-ui-activity-feed",
4265
6259
  "office-ui-channel-list",
4266
6260
  "office-ui-chat-list",
4267
6261
  "office-ui-chat-composer",
4268
6262
  "office-ui-prejoin",
4269
6263
  "office-ui-call-grid",
4270
- "office-ui-call-controls"
6264
+ "office-ui-call-controls",
6265
+ "office-ui-chat-sidebar",
6266
+ "office-ui-people-picker",
6267
+ "office-ui-calendar-view",
6268
+ "office-ui-calendar-agenda"
4271
6269
  ];
4272
6270
  function registerTeams() {
4273
6271
  defineAvatar();
4274
6272
  defineAppRail();
6273
+ defineActivityFeed();
4275
6274
  defineChannelList();
4276
6275
  defineChatList();
4277
6276
  defineChatComposer();
4278
6277
  definePrejoin();
4279
6278
  defineCallGrid();
4280
6279
  defineCallControls();
6280
+ defineChatSidebar();
6281
+ definePeoplePicker();
6282
+ defineCalendarView();
6283
+ defineCalendarAgenda();
4281
6284
  }
4282
6285
 
4283
- export { EMOJI, OfficeUiAppRail, OfficeUiAvatar, OfficeUiCallControls, OfficeUiCallGrid, OfficeUiChannelList, OfficeUiChatComposer, OfficeUiChatList, OfficeUiPrejoin, QUICK_REACTIONS, TEAMS_TAGS, TeamsApp, TeamsChannelTab, TeamsContentPreview, TeamsController, TeamsElement, TeamsFilesPanel, TeamsSettings, XLSX_EDITOR_EVENTS, announceFileCommand, applyTeamsProps, blobFor, colorFor, createTeams, dayLabel, defineAppRail, defineAvatar, defineCallControls, defineCallGrid, defineChannelList, defineChatComposer, defineChatList, definePrejoin, defineTeamsApp, defineTeamsChannelTab, defineTeamsContentPreview, defineTeamsFilesPanel, defineTeamsSettings, downloadBytes, emit, emitEvent, formatElapsed, formatSize, formatTime, icon, listenTeamsEvents, loadConfig, loadIdentity, parseIceLines, pickFile, registerTeams, safeStorage, saveConfig, saveExtension, saveIdentity, withExtension, withStyles };
6286
+ export { EMOJI, OfficeUiActivityFeed, OfficeUiAppRail, OfficeUiAvatar, OfficeUiCalendarAgenda, OfficeUiCalendarView, OfficeUiCallControls, OfficeUiCallGrid, OfficeUiChannelList, OfficeUiChatComposer, OfficeUiChatList, OfficeUiChatSidebar, OfficeUiPeoplePicker, OfficeUiPrejoin, QUICK_REACTIONS, TEAMS_TAGS, TeamsApp, TeamsChannelTab, TeamsContentPreview, TeamsController, TeamsElement, TeamsFilesPanel, TeamsSettings, XLSX_EDITOR_EVENTS, activityTime, announceFileCommand, applyTeamsProps, blobFor, colorFor, createTeams, dayLabel, defineActivityFeed, defineAppRail, defineAvatar, defineCalendarAgenda, defineCalendarView, defineCallControls, defineCallGrid, defineChannelList, defineChatComposer, defineChatList, defineChatSidebar, definePeoplePicker, definePrejoin, defineTeamsApp, defineTeamsChannelTab, defineTeamsContentPreview, defineTeamsFilesPanel, defineTeamsSettings, downloadBytes, emit, emitEvent, formatElapsed, formatSize, formatTime, formatWhen, icon, listenTeamsEvents, loadConfig, loadIdentity, parseIceLines, pickFile, registerTeams, safeStorage, saveConfig, saveExtension, saveIdentity, withExtension, withStyles };