@astronox/core 0.2.4 → 0.3.1

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 (114) hide show
  1. package/README.md +16 -1
  2. package/package.json +18 -4
  3. package/src/admin/authController.ts +166 -0
  4. package/src/admin/cache.ts +44 -17
  5. package/src/admin/index.ts +3 -0
  6. package/src/admin/preparedDestination.ts +114 -0
  7. package/src/admin/resourceClient.ts +107 -0
  8. package/src/ads/adsense/index.ts +1 -0
  9. package/src/ads/adsense.ts +112 -0
  10. package/src/ads/contracts.ts +11 -0
  11. package/src/ads/index.ts +2 -0
  12. package/src/ads/slot.ts +16 -0
  13. package/src/auth/contracts.ts +1 -4
  14. package/src/auth/firebase/client.ts +19 -6
  15. package/src/auth/firebase/index.ts +1 -0
  16. package/src/client/formSubmission.ts +6 -1
  17. package/src/client/preferences.ts +8 -4
  18. package/src/components/actions/Button/Button.astro +30 -42
  19. package/src/components/actions/CardAction/CardAction.astro +7 -0
  20. package/src/components/actions/CardAction/CardAction.css +31 -10
  21. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +6 -8
  22. package/src/components/actions/MediaTrigger/MediaTrigger.astro +1 -1
  23. package/src/components/actions/ScrollToTop/ScrollToTop.astro +2 -1
  24. package/src/components/actions/ScrollToTop/ScrollToTop.css +2 -6
  25. package/src/components/actions/ThemeToggle/ThemeToggle.astro +3 -3
  26. package/src/components/actions/ThemeToggle/themeToggleController.ts +7 -4
  27. package/src/components/content/CardFooter/CardFooter.css +2 -4
  28. package/src/components/content/CardHeader/CardHeader.astro +1 -4
  29. package/src/components/content/CardLink/CardLink.astro +3 -15
  30. package/src/components/content/CardLink/CardLink.css +4 -15
  31. package/src/components/content/CodeBlock/CodeBlock.astro +7 -6
  32. package/src/components/content/CodeBlock/CodeBlock.css +2 -5
  33. package/src/components/content/CodeBlock/codeBlockController.ts +1 -2
  34. package/src/components/content/MarkdownContent/MarkdownContent.astro +1 -3
  35. package/src/components/content/MarkdownContent/MarkdownContent.css +7 -2
  36. package/src/components/content/MarkdownContent/MarkdownInline.astro +11 -3
  37. package/src/components/content/MarkdownContent/MarkdownNode.astro +28 -33
  38. package/src/components/content/MarkdownContent/MarkdownSymbol.astro +30 -19
  39. package/src/components/content/MarkdownContent/contracts.ts +15 -15
  40. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +27 -83
  41. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +19 -22
  42. package/src/components/feedback/Spinner/Spinner.astro +1 -2
  43. package/src/components/feedback/Tooltip/tooltipController.ts +1 -4
  44. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +1 -4
  45. package/src/components/forms/DateTimeInput/DateTimeInput.astro +2 -1
  46. package/src/components/forms/PreparedFormBootstrap/PreparedFormBootstrap.astro +10 -8
  47. package/src/components/forms/PreparedFormBootstrap/parsePreparedFormState.ts +48 -0
  48. package/src/components/forms/Textarea/Textarea.astro +2 -2
  49. package/src/components/icons/BurnIcon.astro +2 -1
  50. package/src/components/icons/FieldIcon.astro +10 -4
  51. package/src/components/icons/GrassIcon.astro +3 -2
  52. package/src/components/icons/SlowIcon.astro +1 -2
  53. package/src/components/icons/StunIcon.astro +2 -1
  54. package/src/components/icons/WaterIcon.astro +3 -2
  55. package/src/components/index.ts +2 -0
  56. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -2
  57. package/src/components/media/CanvasFrame/canvasFrameController.ts +1 -4
  58. package/src/components/media/EmbedFrame/EmbedFrame.astro +5 -12
  59. package/src/components/media/EmbedFrame/embedFrameController.ts +4 -4
  60. package/src/components/monetization/AdSlot/AdSlot.astro +33 -0
  61. package/src/components/monetization/AdSlot/AdSlot.css +53 -0
  62. package/src/components/navigation/DesktopNavigation.css +1 -2
  63. package/src/components/navigation/Footer/Footer.css +3 -9
  64. package/src/components/navigation/MobileNavigation.astro +10 -2
  65. package/src/components/navigation/MobileNavigation.css +8 -22
  66. package/src/components/navigation/navigationTypes.ts +1 -4
  67. package/src/components/seo/SeoHead.astro +21 -6
  68. package/src/composition/SurfaceGroup/SurfaceGroup.css +4 -14
  69. package/src/config/environment.ts +52 -0
  70. package/src/config/index.ts +3 -0
  71. package/src/config/integrations.ts +9 -0
  72. package/src/config/siteConfig.ts +7 -0
  73. package/src/config/storage.ts +16 -0
  74. package/src/content/admin.ts +354 -0
  75. package/src/content/cache.ts +177 -0
  76. package/src/content/catalog.ts +72 -0
  77. package/src/content/contracts.ts +77 -0
  78. package/src/content/drafts.ts +49 -0
  79. package/src/content/index.ts +7 -0
  80. package/src/content/markdown.ts +146 -0
  81. package/src/content/snapshot.ts +139 -0
  82. package/src/data/firestore/client/contracts.ts +10 -2
  83. package/src/data/index.ts +1 -0
  84. package/src/data/memoizedLoader.ts +13 -0
  85. package/src/display/contracts.ts +1 -5
  86. package/src/i18n/config.ts +2 -8
  87. package/src/i18n/coreMessages.ts +2 -9
  88. package/src/i18n/preference.ts +2 -6
  89. package/src/patterns/Combobox/Combobox.astro +9 -12
  90. package/src/patterns/Combobox/comboboxController.ts +14 -8
  91. package/src/patterns/Fullscreen/FullscreenControl.astro +5 -1
  92. package/src/patterns/Fullscreen/fullscreenController.ts +1 -3
  93. package/src/patterns/MapViewport/geometry.ts +1 -4
  94. package/src/patterns/Modal/Modal.css +1 -4
  95. package/src/patterns/Modal/ModalOpen.astro +5 -1
  96. package/src/patterns/Table/Table.astro +10 -10
  97. package/src/patterns/Table/TableBody.astro +1 -4
  98. package/src/patterns/Table/TableBody.css +2 -4
  99. package/src/patterns/Table/TablePagination.astro +23 -30
  100. package/src/patterns/Table/TableSkeleton.astro +1 -5
  101. package/src/patterns/Table/tableClientController.ts +2 -2
  102. package/src/patterns/Table/tableFilterController.ts +1 -3
  103. package/src/patterns/Table/tableHeaderOverflowController.ts +1 -4
  104. package/src/patterns/Table/tableProgressiveController.ts +62 -60
  105. package/src/patterns/Table/tableScrollController.ts +3 -2
  106. package/src/primitives/text/TextBlock/TextBlock.astro +1 -3
  107. package/src/publication/contracts.ts +11 -0
  108. package/src/publication/http.ts +31 -0
  109. package/src/publication/index.ts +2 -0
  110. package/src/routing/localized.ts +9 -12
  111. package/src/seo/localizedMeta.ts +2 -6
  112. package/src/styles/interaction.css +14 -7
  113. package/src/styles/style.css +11 -20
  114. package/src/theme/contracts.ts +1 -6
package/src/data/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './collectionValidation';
2
+ export * from './memoizedLoader';
@@ -0,0 +1,13 @@
1
+ export function createMemoizedLoader<T>(
2
+ load: () => Promise<T>,
3
+ ): () => Promise<T> {
4
+ let pending: Promise<T> | null = null;
5
+
6
+ return () => {
7
+ pending ??= load().catch((reason) => {
8
+ pending = null;
9
+ throw reason;
10
+ });
11
+ return pending;
12
+ };
13
+ }
@@ -175,11 +175,7 @@ export type StageFlowStep = {
175
175
  };
176
176
 
177
177
  export type TableColumnRole =
178
- | 'ordinal'
179
- | 'media'
180
- | 'primary'
181
- | 'value'
182
- | 'collection';
178
+ 'ordinal' | 'media' | 'primary' | 'value' | 'collection';
183
179
  export type TableColumnAlignment = 'start' | 'center' | 'end';
184
180
 
185
181
  export type TableColumn = {
@@ -26,10 +26,7 @@ export type LocaleConfig<
26
26
  };
27
27
 
28
28
  export function createLocaleConfig<
29
- const TDefinitions extends readonly [
30
- LocaleDefinition,
31
- ...LocaleDefinition[],
32
- ],
29
+ const TDefinitions extends readonly [LocaleDefinition, ...LocaleDefinition[]],
33
30
  const TDefaultLocale extends TDefinitions[number]['id'],
34
31
  >({
35
32
  defaultLocale,
@@ -100,10 +97,7 @@ export function createLocaleConfig<
100
97
  },
101
98
  requireLocalizedLocale(value: string): Locale {
102
99
  const locale = localeByUrlCode.get(value.toLowerCase())?.id;
103
- if (
104
- !locale ||
105
- (!prefixDefaultLocale && locale === defaultLocale)
106
- ) {
100
+ if (!locale || (!prefixDefaultLocale && locale === defaultLocale)) {
107
101
  throw new Error(`Unsupported localized route locale: ${value}`);
108
102
  }
109
103
  return locale;
@@ -1,7 +1,4 @@
1
- import {
2
- resolveLocalizedCatalog,
3
- type MessageShape,
4
- } from './catalog';
1
+ import { resolveLocalizedCatalog, type MessageShape } from './catalog';
5
2
 
6
3
  export const defaultCoreMessages = {
7
4
  actions: {
@@ -29,9 +26,5 @@ export function resolveCoreMessages(
29
26
  translated: unknown = undefined,
30
27
  locale = 'en',
31
28
  ): CoreMessages {
32
- return resolveLocalizedCatalog(
33
- defaultCoreMessages,
34
- translated,
35
- locale,
36
- );
29
+ return resolveLocalizedCatalog(defaultCoreMessages, translated, locale);
37
30
  }
@@ -11,9 +11,7 @@ export function createLocalePreferenceController<TLocale extends string>({
11
11
  resolvePath,
12
12
  storageKey,
13
13
  }: LocalePreferenceControllerOptions<TLocale>) {
14
- return function initLocalePreference(
15
- root: Document = document,
16
- ): () => void {
14
+ return function initLocalePreference(root: Document = document): () => void {
17
15
  const element = root.documentElement;
18
16
  if (element.hasAttribute(initializedAttribute)) return () => undefined;
19
17
  element.setAttribute(initializedAttribute, '');
@@ -24,9 +22,7 @@ export function createLocalePreferenceController<TLocale extends string>({
24
22
 
25
23
  const link = target.closest<HTMLAnchorElement>('a[hreflang]');
26
24
  const languageTag = link?.getAttribute('hreflang');
27
- const locale = languageTag
28
- ? resolveLocale(languageTag)
29
- : undefined;
25
+ const locale = languageTag ? resolveLocale(languageTag) : undefined;
30
26
  if (!locale) return;
31
27
 
32
28
  event.preventDefault();
@@ -244,18 +244,15 @@ const searchId = `${props.id}-search`;
244
244
  data-tone={resolvedTone}
245
245
  data-preference-id={props.preference?.id}
246
246
  data-preference-path={props.preference?.path}
247
- data-preference-control={
248
- props.preference
249
- ? mode === 'number'
250
- ? 'combobox-number'
251
- : 'combobox-options'
252
- : undefined
253
- }
254
- data-preference-number-bounds={
255
- mode === 'number' && (props as NumberProps).preferenceBounds
256
- ? JSON.stringify((props as NumberProps).preferenceBounds)
257
- : undefined
258
- }
247
+ data-preference-control={props.preference
248
+ ? mode === 'number'
249
+ ? 'combobox-number'
250
+ : 'combobox-options'
251
+ : undefined}
252
+ data-preference-number-bounds={mode === 'number' &&
253
+ (props as NumberProps).preferenceBounds
254
+ ? JSON.stringify((props as NumberProps).preferenceBounds)
255
+ : undefined}
259
256
  >
260
257
  <span class="combobox__sizer" aria-hidden="true">
261
258
  {
@@ -105,10 +105,7 @@ function normalizeNumber(value: number, bounds: NumberBounds): number {
105
105
  return Math.min(bounds.max, Math.max(bounds.min, stepped));
106
106
  }
107
107
 
108
- function syncNumberOptionBounds(
109
- root: HTMLElement,
110
- bounds: NumberBounds,
111
- ): void {
108
+ function syncNumberOptionBounds(root: HTMLElement, bounds: NumberBounds): void {
112
109
  for (const option of getOptions(root)) {
113
110
  const value = Number(option.dataset.comboboxOption);
114
111
  option.hidden =
@@ -172,12 +169,18 @@ export function setComboboxOptions(
172
169
  selectedValues: readonly string[] = [],
173
170
  ): void {
174
171
  if (root.dataset.comboboxMode === 'number') return;
175
- const optionsRoot = root.querySelector<HTMLElement>('[data-combobox-options]');
172
+ const optionsRoot = root.querySelector<HTMLElement>(
173
+ '[data-combobox-options]',
174
+ );
176
175
  const empty = root.querySelector<HTMLElement>('[data-combobox-empty]');
177
176
  if (!optionsRoot || !empty) return;
178
177
 
179
- optionsRoot.querySelectorAll('[data-combobox-option]').forEach((node) => node.remove());
180
- root.querySelectorAll('template[data-combobox-tag-template]').forEach((node) => node.remove());
178
+ optionsRoot
179
+ .querySelectorAll('[data-combobox-option]')
180
+ .forEach((node) => node.remove());
181
+ root
182
+ .querySelectorAll('template[data-combobox-tag-template]')
183
+ .forEach((node) => node.remove());
181
184
 
182
185
  options.forEach((option, index) => {
183
186
  const button = document.createElement('button');
@@ -190,7 +193,10 @@ export function setComboboxOptions(
190
193
  button.setAttribute('aria-selected', 'false');
191
194
  if (option.disabled) button.setAttribute('aria-disabled', 'true');
192
195
  button.dataset.comboboxOption = option.value;
193
- button.dataset.comboboxSearchText = [option.label, option.description ?? ''].join(' ');
196
+ button.dataset.comboboxSearchText = [
197
+ option.label,
198
+ option.description ?? '',
199
+ ].join(' ');
194
200
  button.dataset.interactionAccent = 'start';
195
201
 
196
202
  const content = document.createElement('span');
@@ -12,7 +12,11 @@ type FullscreenUnsupportedFallback = {
12
12
  target?: '_blank' | '_self';
13
13
  };
14
14
 
15
- type Props = Omit<HTMLAttributes<'button'>, 'aria-controls' | 'type'> & {
15
+ type Props = Omit<
16
+ HTMLAttributes<'button'>,
17
+ 'aria-controls' | 'disabled' | 'type'
18
+ > & {
19
+ disabled?: boolean;
16
20
  enterLabel: string;
17
21
  exitLabel: string;
18
22
  targetId: string;
@@ -1,9 +1,7 @@
1
1
  const initializedAttribute = 'data-fullscreen-initialized';
2
2
  const noop = (): void => undefined;
3
3
 
4
- export function initFullscreenControl(
5
- control: HTMLButtonElement,
6
- ): () => void {
4
+ export function initFullscreenControl(control: HTMLButtonElement): () => void {
7
5
  if (control.getAttribute(initializedAttribute) === 'true') return noop;
8
6
 
9
7
  const targetId = control.dataset.fullscreenTarget;
@@ -1,7 +1,4 @@
1
- import type {
2
- MapViewportModel,
3
- MapViewportSizing,
4
- } from './contracts';
1
+ import type { MapViewportModel, MapViewportSizing } from './contracts';
5
2
 
6
3
  export type MapViewportSceneGeometry = {
7
4
  aspectRatio: string;
@@ -108,10 +108,7 @@ html:has(.modal[open]) [data-site-viewport] {
108
108
  padding-inline: 0;
109
109
  }
110
110
 
111
- .modal:not(
112
- [data-sizing='adaptive'],
113
- [data-sizing='confirmation']
114
- )
111
+ .modal:not([data-sizing='adaptive'], [data-sizing='confirmation'])
115
112
  .modal__content
116
113
  [data-surface] {
117
114
  border-radius: 0;
@@ -3,7 +3,11 @@ import type { HTMLAttributes } from 'astro/types';
3
3
  import type { Tone } from '../../theme/contracts';
4
4
  import Button from '../../components/actions/Button/Button.astro';
5
5
 
6
- type Props = Omit<HTMLAttributes<'button'>, 'aria-controls' | 'type'> & {
6
+ type Props = Omit<
7
+ HTMLAttributes<'button'>,
8
+ 'aria-controls' | 'disabled' | 'type'
9
+ > & {
10
+ disabled?: boolean;
7
11
  label: string;
8
12
  targetId: string;
9
13
  tone?: Tone;
@@ -114,7 +114,7 @@ const resolvedRows = rows.map((row, index): TableRow => ({
114
114
  ...row,
115
115
  key: row.key ?? `${paginationKey ?? 'table'}-${index + 1}`,
116
116
  }));
117
- const inferredProgressive =
117
+ const inferredProgressive: TableProgressiveModel | undefined =
118
118
  progressive === undefined && resolvedRows.length > batchSize
119
119
  ? {
120
120
  batchSize,
@@ -128,7 +128,7 @@ const inferredProgressive =
128
128
  const progressiveModel =
129
129
  progressive === false
130
130
  ? undefined
131
- : configuredProgressive ?? inferredProgressive;
131
+ : (configuredProgressive ?? inferredProgressive);
132
132
  const hasProgressiveRows = Boolean(
133
133
  progressiveModel && progressiveModel.entries.length > batchSize,
134
134
  );
@@ -171,7 +171,9 @@ if (clientProgressive && !paginationMessages) {
171
171
  throw new Error('Client-progressive tables require localized messages');
172
172
  }
173
173
  if (progressiveModel?.sources && !paginationKey) {
174
- throw new Error('External progressive tables require a stable pagination key');
174
+ throw new Error(
175
+ 'External progressive tables require a stable pagination key',
176
+ );
175
177
  }
176
178
  if (rowKeys.size !== resolvedRows.length) {
177
179
  throw new Error('Table rows require unique keys');
@@ -193,9 +195,9 @@ if (
193
195
  data-table-client-progressive={clientProgressive ? true : undefined}
194
196
  data-table-density={density}
195
197
  data-table-inline-overflow={inlineOverflow}
196
- data-table-index-minimum-rem={
197
- minimumInlineSize ? Number.parseFloat(minimumInlineSize) : undefined
198
- }
198
+ data-table-index-minimum-rem={minimumInlineSize
199
+ ? Number.parseFloat(minimumInlineSize)
200
+ : undefined}
199
201
  data-table-layout={layout}
200
202
  data-table-loading-state={initialLoadingState}
201
203
  data-table-preparation={preparation}
@@ -209,10 +211,8 @@ if (
209
211
  preparesRows ? (
210
212
  <script is:inline>
211
213
  document.documentElement.setAttribute('data-nox-table-enhanced', '');
212
- document.currentScript?.parentElement?.setAttribute(
213
- 'aria-busy',
214
- 'true',
215
- );
214
+ document.currentScript?.parentElement?.setAttribute( 'aria-busy',
215
+ 'true', );
216
216
  </script>
217
217
  ) : null
218
218
  }
@@ -6,10 +6,7 @@ import TableSortIcon from '../../components/icons/TableSortIcon.astro';
6
6
  import TextBlock from '../../primitives/text/TextBlock/TextBlock.astro';
7
7
  import TableCellContent from './TableCell.astro';
8
8
  import TableSkeleton from './TableSkeleton.astro';
9
- import {
10
- getTableColumnAlignment,
11
- type TableLayout,
12
- } from './tableLayout';
9
+ import { getTableColumnAlignment, type TableLayout } from './tableLayout';
13
10
 
14
11
  type Props = HTMLAttributes<'div'> & {
15
12
  caption: string;
@@ -47,8 +47,7 @@
47
47
  min-inline-size: 100%;
48
48
  }
49
49
 
50
- .table[data-table-layout='index'][data-table-index-overflow]
51
- .table-body__cell {
50
+ .table[data-table-layout='index'][data-table-index-overflow] .table-body__cell {
52
51
  overflow: visible;
53
52
  }
54
53
 
@@ -155,8 +154,7 @@
155
154
  display: none;
156
155
  }
157
156
 
158
- .table[data-table-layout='index']
159
- .table-body__row:not([data-table-state]) {
157
+ .table[data-table-layout='index'] .table-body__row:not([data-table-state]) {
160
158
  block-size: var(--table-index-row-block-size);
161
159
  min-block-size: var(--table-index-row-block-size);
162
160
  overflow: hidden;
@@ -37,17 +37,15 @@ const {
37
37
  hidden={progressive || !pagination?.nextHref}
38
38
  >
39
39
  <div class="table-pagination__content">
40
- {
41
- pagination?.nextHref ? (
42
- <a
43
- class="table-pagination__link"
44
- href={pagination.nextHref}
45
- data-table-pagination-link
46
- >
47
- {nextLabel}
48
- </a>
49
- ) : null
50
- }
40
+ {pagination?.nextHref ? (
41
+ <a
42
+ class="table-pagination__link"
43
+ href={pagination.nextHref}
44
+ data-table-pagination-link
45
+ >
46
+ {nextLabel}
47
+ </a>
48
+ ) : null}
51
49
  <span
52
50
  class="table-pagination__status"
53
51
  data-table-pagination-status
@@ -62,25 +60,20 @@ const {
62
60
  />
63
61
  </div>
64
62
  </TableFooter>
65
- {
66
- progressive && pagination?.nextHref ? (
67
- <noscript>
68
- <TableFooter
69
- class:list={['table-pagination', className]}
70
- padding="none"
71
- >
72
- <div class="table-pagination__content">
73
- <a
74
- class="table-pagination__link"
75
- href={pagination.nextHref}
76
- >
77
- {nextLabel}
78
- </a>
79
- </div>
80
- </TableFooter>
81
- </noscript>
82
- ) : null
83
- }
63
+ {progressive && pagination?.nextHref ? (
64
+ <noscript>
65
+ <TableFooter
66
+ class:list={['table-pagination', className]}
67
+ padding="none"
68
+ >
69
+ <div class="table-pagination__content">
70
+ <a class="table-pagination__link" href={pagination.nextHref}>
71
+ {nextLabel}
72
+ </a>
73
+ </div>
74
+ </TableFooter>
75
+ </noscript>
76
+ ) : null}
84
77
  </>
85
78
  ) : null
86
79
  }
@@ -13,11 +13,7 @@ const rowCount = Math.max(1, Math.floor(rows));
13
13
 
14
14
  {
15
15
  Array.from({ length: rowCount }, () => (
16
- <tr
17
- class="table-skeleton__row"
18
- data-table-skeleton-row
19
- aria-hidden="true"
20
- >
16
+ <tr class="table-skeleton__row" data-table-skeleton-row aria-hidden="true">
21
17
  {columns.map((column) => (
22
18
  <td
23
19
  class="table-skeleton__cell"
@@ -189,8 +189,8 @@ export function createClientTablePagination<TItem>({
189
189
  if (!nextCursor) observer?.disconnect();
190
190
  shouldContinue = Boolean(
191
191
  nextCursor &&
192
- sentinel &&
193
- sentinel.getBoundingClientRect().top <= window.innerHeight + 512,
192
+ sentinel &&
193
+ sentinel.getBoundingClientRect().top <= window.innerHeight + 512,
194
194
  );
195
195
  } catch (error) {
196
196
  if (disposed) return;
@@ -50,9 +50,7 @@ function setTokenAttribute(
50
50
  }
51
51
 
52
52
  const tokens = new Set(
53
- (element.getAttribute(attribute) ?? '')
54
- .split(/\s+/)
55
- .filter(Boolean),
53
+ (element.getAttribute(attribute) ?? '').split(/\s+/).filter(Boolean),
56
54
  );
57
55
  if (enabled) tokens.add(normalizedKey);
58
56
  else tokens.delete(normalizedKey);
@@ -27,10 +27,7 @@ function truncateLabel(label: HeaderLabel): void {
27
27
 
28
28
  while (lowerBound < upperBound) {
29
29
  const candidateLength = Math.ceil((lowerBound + upperBound) / 2);
30
- text.textContent = characters
31
- .slice(0, candidateLength)
32
- .join('')
33
- .trimEnd();
30
+ text.textContent = characters.slice(0, candidateLength).join('').trimEnd();
34
31
 
35
32
  if (text.scrollWidth - text.clientWidth <= overflowTolerance) {
36
33
  lowerBound = candidateLength;