@diplodoc/transform 4.70.3 → 4.71.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 (42) hide show
  1. package/dist/css/_yfm-only.css +4 -4
  2. package/dist/css/_yfm-only.css.map +3 -3
  3. package/dist/css/_yfm-only.min.css +1 -1
  4. package/dist/css/_yfm-only.min.css.map +3 -3
  5. package/dist/css/base.css +1 -0
  6. package/dist/css/base.css.map +3 -3
  7. package/dist/css/base.min.css +1 -1
  8. package/dist/css/base.min.css.map +3 -3
  9. package/dist/css/print.css.map +1 -1
  10. package/dist/css/yfm.css +6 -4
  11. package/dist/css/yfm.css.map +3 -3
  12. package/dist/css/yfm.min.css +1 -1
  13. package/dist/css/yfm.min.css.map +3 -3
  14. package/dist/js/base.js +396 -215
  15. package/dist/js/base.js.map +4 -4
  16. package/dist/js/base.min.js +1 -6
  17. package/dist/js/base.min.js.map +4 -4
  18. package/dist/js/yfm.js +429 -228
  19. package/dist/js/yfm.js.map +4 -4
  20. package/dist/js/yfm.min.js +1 -6
  21. package/dist/js/yfm.min.js.map +4 -4
  22. package/dist/scss/_common.scss +1 -0
  23. package/dist/scss/_modal.scss +1 -0
  24. package/dist/scss/{_inline-code.scss → _tooltip.scss} +1 -1
  25. package/dist/scss/_yfm-only.scss +1 -1
  26. package/lib/plugins/anchors/index.js +1 -1
  27. package/lib/plugins/anchors/index.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/js/anchor.ts +27 -3
  30. package/src/js/{inline-code/constant.ts → constant.ts} +1 -9
  31. package/src/js/inline-code/index.ts +24 -42
  32. package/src/js/tooltip/constant.ts +25 -0
  33. package/src/js/tooltip/index.ts +2 -0
  34. package/src/js/tooltip/tooltip.ts +263 -0
  35. package/src/js/tooltip/types.ts +59 -0
  36. package/src/js/tooltip/utils.ts +247 -0
  37. package/src/scss/_common.scss +1 -0
  38. package/src/scss/_modal.scss +1 -0
  39. package/src/scss/{_inline-code.scss → _tooltip.scss} +1 -1
  40. package/src/scss/_yfm-only.scss +1 -1
  41. package/src/transform/plugins/anchors/index.ts +1 -1
  42. package/src/js/inline-code/utils.ts +0 -217
@@ -0,0 +1,247 @@
1
+ import type {
2
+ Alignment,
3
+ Coords,
4
+ ElementRect,
5
+ OffsetValues,
6
+ Placement,
7
+ Side,
8
+ SideObject,
9
+ } from './types';
10
+
11
+ import {DEFAULT_SIDE_OBJECT, OPPOSITE_SIDES} from './constant';
12
+
13
+ function isVerticalSide(side: Side): boolean {
14
+ return side === 'top' || side === 'bottom';
15
+ }
16
+
17
+ function createSideObject(value: Partial<SideObject> | number = 0): SideObject {
18
+ if (typeof value === 'number') {
19
+ return {top: value, bottom: value, left: value, right: value};
20
+ }
21
+
22
+ return {...DEFAULT_SIDE_OBJECT, ...value};
23
+ }
24
+
25
+ function parsePlacement(placement: Placement): {side: Side; alignment?: Alignment} {
26
+ const [side, alignment] = placement.split('-') as [Side, Alignment | undefined];
27
+
28
+ return {side, alignment};
29
+ }
30
+
31
+ function getOppositeSide(side: Side): Side {
32
+ return OPPOSITE_SIDES[side];
33
+ }
34
+
35
+ function flipPlacement(placement: Placement): Placement {
36
+ const {side, alignment} = parsePlacement(placement);
37
+ const opposite = getOppositeSide(side);
38
+
39
+ return (alignment ? `${opposite}-${alignment}` : opposite) as Placement;
40
+ }
41
+
42
+ function getOverflow(tooltip: ElementRect, coords: Coords, viewport: ElementRect): SideObject {
43
+ const rect = updateRect(tooltip, {top: coords.y, left: coords.x});
44
+
45
+ return detectOverflow(viewport, rect, 5);
46
+ }
47
+
48
+ function shouldFlip(overflow: SideObject, flippedOverflow: SideObject, side: Side): boolean {
49
+ const opposite = getOppositeSide(side);
50
+
51
+ return overflow[side] > 0 && flippedOverflow[opposite] < overflow[side];
52
+ }
53
+
54
+ export function computePosition(
55
+ reference: ElementRect,
56
+ tooltip: ElementRect,
57
+ viewport: ElementRect,
58
+ placement: Placement,
59
+ offset: OffsetValues,
60
+ isRtl: boolean,
61
+ flip = true,
62
+ ): {coords: Coords; placement: Placement} {
63
+ const coords = computeCoordsFromPlacement(reference, tooltip, offset, placement, isRtl);
64
+
65
+ if (!flip) {
66
+ return {coords, placement};
67
+ }
68
+
69
+ const overflow = getOverflow(tooltip, coords, viewport);
70
+ const {side} = parsePlacement(placement);
71
+
72
+ if (overflow[side] <= 0) {
73
+ return {coords, placement};
74
+ }
75
+
76
+ const flipped = flipPlacement(placement);
77
+ const flippedCoords = computeCoordsFromPlacement(reference, tooltip, offset, flipped, isRtl);
78
+ const flippedOverflow = getOverflow(tooltip, flippedCoords, viewport);
79
+
80
+ if (shouldFlip(overflow, flippedOverflow, side)) {
81
+ return {coords: flippedCoords, placement: flipped};
82
+ }
83
+
84
+ return {coords, placement};
85
+ }
86
+
87
+ export function generateId() {
88
+ const random = Math.random().toString(36).substring(2, 6);
89
+ const now = Date.now().toString(36);
90
+
91
+ return `${random}${now}`;
92
+ }
93
+
94
+ export function createRect(
95
+ params: Pick<ElementRect, 'top' | 'left' | 'width' | 'height'>,
96
+ ): ElementRect {
97
+ return {
98
+ ...params,
99
+ right: params.left + params.width,
100
+ bottom: params.top + params.height,
101
+ };
102
+ }
103
+
104
+ export function updateRect(rect: ElementRect, params: Partial<ElementRect>): ElementRect {
105
+ return createRect({
106
+ top: params.top ?? rect.top,
107
+ left: params.left ?? rect.left,
108
+ width: params.width ?? rect.width,
109
+ height: params.height ?? rect.height,
110
+ });
111
+ }
112
+
113
+ export function getViewportRect(): ElementRect {
114
+ const {documentElement, body} = document;
115
+
116
+ const scrollTop = window.scrollY || documentElement.scrollTop || body.scrollTop;
117
+ const scrollLeft = window.scrollX || documentElement.scrollLeft || body.scrollLeft;
118
+
119
+ const clientTop = documentElement.clientTop || body.clientTop || 0;
120
+ const clientLeft = documentElement.clientLeft || body.clientLeft || 0;
121
+
122
+ return createRect({
123
+ top: Math.round(scrollTop - clientTop),
124
+ left: Math.round(scrollLeft - clientLeft),
125
+ width: document.body.clientWidth,
126
+ height: document.body.clientHeight,
127
+ });
128
+ }
129
+
130
+ export function getElementRect(element: HTMLElement): ElementRect {
131
+ const viewport = getViewportRect();
132
+ const box = element.getBoundingClientRect();
133
+
134
+ return createRect({
135
+ top: Math.round(box.top + viewport.top),
136
+ left: Math.round(box.left + viewport.left),
137
+ width: box.width,
138
+ height: box.height,
139
+ });
140
+ }
141
+
142
+ export function computeAxisOffset(offset: OffsetValues, side: Side, isRtl?: boolean): Coords {
143
+ const {mainAxis = 0, crossAxis = 0} = offset;
144
+
145
+ const isVertical = isVerticalSide(side);
146
+ const mainDirection = side === 'top' || side === 'left' ? -1 : 1;
147
+ const crossDirection = isRtl && isVertical ? -1 : 1;
148
+
149
+ const mainOffset = mainAxis * mainDirection;
150
+ const crossOffset = crossAxis * crossDirection;
151
+
152
+ if (isVertical) {
153
+ return {x: crossOffset, y: mainOffset};
154
+ }
155
+
156
+ return {x: mainOffset, y: crossOffset};
157
+ }
158
+
159
+ export function computeCoordsFromPlacement(
160
+ reference: ElementRect,
161
+ tooltip: ElementRect,
162
+ offset: OffsetValues,
163
+ placement: Placement,
164
+ isRtl: boolean,
165
+ ) {
166
+ const {side, alignment} = parsePlacement(placement);
167
+ const isVertical = isVerticalSide(side);
168
+ const alignmentAxis = isVertical ? 'x' : 'y';
169
+ const alignLength = alignmentAxis === 'y' ? 'height' : 'width';
170
+
171
+ const centerX = reference.left + reference.width / 2 - tooltip.width / 2;
172
+ const centerY = reference.top + reference.height / 2 - tooltip.height / 2;
173
+ const alignmentOffset = reference[alignLength] / 2 - tooltip[alignLength] / 2;
174
+
175
+ const coords: Coords = {x: reference.left, y: reference.top};
176
+
177
+ switch (side) {
178
+ case 'top': {
179
+ coords.x = centerX;
180
+ coords.y = reference.top - tooltip.height;
181
+ break;
182
+ }
183
+
184
+ case 'bottom': {
185
+ coords.x = centerX;
186
+ coords.y = reference.top + reference.height;
187
+ break;
188
+ }
189
+
190
+ case 'right': {
191
+ coords.x = reference.left + reference.width;
192
+ coords.y = centerY;
193
+ break;
194
+ }
195
+
196
+ case 'left': {
197
+ coords.x = reference.left - tooltip.width;
198
+ coords.y = centerY;
199
+ break;
200
+ }
201
+ }
202
+
203
+ switch (alignment) {
204
+ case 'start': {
205
+ coords[alignmentAxis] -= alignmentOffset * (isRtl && isVertical ? -1 : 1);
206
+ break;
207
+ }
208
+
209
+ case 'end': {
210
+ coords[alignmentAxis] += alignmentOffset * (isRtl && isVertical ? -1 : 1);
211
+ break;
212
+ }
213
+ }
214
+
215
+ const axisOffset = computeAxisOffset(offset, side, isRtl);
216
+
217
+ coords.x += axisOffset.x;
218
+ coords.y += axisOffset.y;
219
+
220
+ return coords;
221
+ }
222
+
223
+ export function convertToRelativeToOffsetParentRect(rect: ElementRect, offsetParent: HTMLElement) {
224
+ const offsetRect = getElementRect(offsetParent);
225
+
226
+ return createRect({
227
+ top: rect.top - offsetRect.top + offsetParent.offsetTop,
228
+ left: rect.left - offsetRect.left + offsetParent.offsetLeft,
229
+ width: rect.width,
230
+ height: rect.height,
231
+ });
232
+ }
233
+
234
+ export function detectOverflow(
235
+ boundary: ElementRect,
236
+ element: ElementRect,
237
+ padding: Partial<SideObject> | number = 0,
238
+ ): SideObject {
239
+ const {top, bottom, left, right} = createSideObject(padding);
240
+
241
+ return {
242
+ top: boundary.top - element.top + top,
243
+ bottom: element.bottom - boundary.bottom + bottom,
244
+ left: boundary.left - element.left + left,
245
+ right: element.right - boundary.right + right,
246
+ };
247
+ }
@@ -208,6 +208,7 @@
208
208
  overflow: auto;
209
209
  position: relative;
210
210
  z-index: 1;
211
+ scrollbar-width: thin;
211
212
 
212
213
  box-sizing: border-box;
213
214
  border: 1px solid var(--yfm-color-table-border, var(--yfm-color-table-border-private));
@@ -94,6 +94,7 @@
94
94
 
95
95
  .wide-inner-element {
96
96
  max-width: 700px;
97
+ scrollbar-width: thin;
97
98
  }
98
99
  }
99
100
 
@@ -1,4 +1,4 @@
1
- .inline_code_tooltip {
1
+ .yfm-tooltip {
2
2
  position: absolute;
3
3
  z-index: 100;
4
4
 
@@ -10,4 +10,4 @@
10
10
  @use 'katex';
11
11
  @use 'table';
12
12
  @use 'term';
13
- @use 'inline-code';
13
+ @use 'tooltip';
@@ -46,7 +46,7 @@ function createAnchorLinkTokens(
46
46
  open.attrSet('id', id);
47
47
  }
48
48
  open.attrSet('href', href + '#' + id);
49
- open.attrSet('class', 'yfm-anchor');
49
+ open.attrSet('class', 'yfm-anchor yfm-clipboard-anchor');
50
50
  open.attrSet('aria-hidden', 'true');
51
51
 
52
52
  // SEO: render invisible heading title because link must have text content.
@@ -1,217 +0,0 @@
1
- import type {Lang} from 'src/transform/typings';
2
-
3
- import {getCoords} from '../term/utils';
4
-
5
- import {INLINE_CODE, INLINE_CODE_CLASS, INLINE_CODE_ID, LANG_TOKEN, OPEN_CLASS} from './constant';
6
-
7
- export let timer: ReturnType<typeof setTimeout> | null = null;
8
-
9
- let isListenerNeeded = true;
10
-
11
- export function getTooltipElement(): HTMLElement | null {
12
- return document.getElementById(INLINE_CODE_ID);
13
- }
14
-
15
- function setTooltipAriaAttributes(tooltipElement: HTMLElement, targetElement: HTMLElement): void {
16
- const ariaLive = targetElement.getAttribute('aria-live') || 'polite';
17
- tooltipElement?.setAttribute('aria-live', ariaLive);
18
- tooltipElement?.setAttribute('aria-modal', 'true');
19
- }
20
-
21
- function checkTimerAndClear() {
22
- if (timer) {
23
- clearTimeout(timer);
24
- timer = null;
25
- }
26
- }
27
-
28
- function tooltipParentElement(target: HTMLElement | null) {
29
- if (!target) {
30
- return null;
31
- }
32
-
33
- const closestScrollableParent = target.closest('table') || target.closest('code');
34
-
35
- return closestScrollableParent || target.parentElement;
36
- }
37
-
38
- function tooltipOnResize() {
39
- const openedDefinition = getTooltipElement();
40
-
41
- if (!openedDefinition) {
42
- return;
43
- }
44
- const inlineId = openedDefinition.getAttribute('inline-id') || '';
45
- const targetElement = document.getElementById(inlineId);
46
-
47
- if (!targetElement) {
48
- return;
49
- }
50
-
51
- setTooltipPosition(openedDefinition, targetElement);
52
- }
53
-
54
- export function setTooltipPosition(tooltipElement: HTMLElement, targetElement: HTMLElement): void {
55
- const {
56
- x: inlineX,
57
- y: inlineY,
58
- right: inlineRight,
59
- left: inlineLeft,
60
- width: inlineWidth,
61
- height: inlineHeight,
62
- } = targetElement.getBoundingClientRect();
63
-
64
- const tooltipParent = tooltipParentElement(targetElement);
65
-
66
- if (!tooltipParent) {
67
- return;
68
- }
69
-
70
- const {right: tooltipParentRight, left: tooltipParentLeft} =
71
- tooltipParent.getBoundingClientRect();
72
-
73
- if ((tooltipParentRight < inlineLeft || tooltipParentLeft > inlineRight) && !isListenerNeeded) {
74
- closeTooltip(tooltipElement);
75
- return;
76
- }
77
-
78
- if (isListenerNeeded && tooltipParent) {
79
- tooltipParent.addEventListener('scroll', tooltipOnResize);
80
- isListenerNeeded = false;
81
- }
82
-
83
- const relativeX = Number(tooltipElement.getAttribute('relativeX'));
84
- const relativeY = Number(tooltipElement.getAttribute('relativeY'));
85
-
86
- if (relativeX === inlineX && relativeY === inlineY) {
87
- return;
88
- }
89
-
90
- tooltipElement.setAttribute('relativeX', String(inlineX));
91
- tooltipElement.setAttribute('relativeY', String(inlineY));
92
-
93
- const offsetTop = inlineHeight + 5;
94
- const definitionParent = tooltipElement.parentElement;
95
-
96
- if (!definitionParent) {
97
- return;
98
- }
99
-
100
- const {width: definitionWidth} = tooltipElement.getBoundingClientRect();
101
- const {left: definitionParentLeft} = definitionParent.getBoundingClientRect();
102
-
103
- // If definition not fit document change base alignment
104
- const definitionLeftCoordinate = Number(getCoords(targetElement).left);
105
- const definitionRightCoordinate = definitionWidth + definitionLeftCoordinate;
106
-
107
- const definitionOutOfScreenOnLeft = definitionLeftCoordinate - definitionWidth < 0;
108
- const definitionOutOfScreenOnRight = definitionRightCoordinate > document.body.clientWidth;
109
-
110
- const isAlignSwapped = definitionOutOfScreenOnRight || document.dir === 'rtl';
111
- const fitDefinitionDocument =
112
- isAlignSwapped && !definitionOutOfScreenOnLeft ? definitionWidth - inlineWidth : 0;
113
- const customHeaderTop = getCoords(definitionParent).top - definitionParent.offsetTop;
114
- const offsetRight = 5;
115
- const shiftLeft = definitionOutOfScreenOnRight
116
- ? definitionRightCoordinate - document.body.clientWidth + offsetRight
117
- : 0;
118
- const offsetLeft =
119
- getCoords(targetElement).left -
120
- definitionParentLeft +
121
- definitionParent.offsetLeft -
122
- fitDefinitionDocument;
123
-
124
- const isShiftLeftNeeded = offsetLeft + definitionWidth >= document.body.clientWidth;
125
-
126
- tooltipElement.style.top =
127
- Number(getCoords(targetElement).top + offsetTop - customHeaderTop) + 'px';
128
- tooltipElement.style.left = Number(offsetLeft - (isShiftLeftNeeded ? shiftLeft : 0)) + 'px';
129
- }
130
-
131
- export function getInlineCodeByTooltip(definition: HTMLElement) {
132
- const inlineId = definition.getAttribute('inline-id');
133
-
134
- return inlineId ? document.getElementById(inlineId) : null;
135
- }
136
-
137
- function closeTooltipFn(definition: HTMLElement) {
138
- definition.classList.remove(OPEN_CLASS);
139
- const inline = getInlineCodeByTooltip(definition);
140
- const inlineCodepParent = tooltipParentElement(inline);
141
- const tooltipParent = tooltipParentElement(definition);
142
-
143
- definition.removeAttribute('inline-id');
144
-
145
- if (!inlineCodepParent || !tooltipParent) {
146
- return;
147
- }
148
-
149
- tooltipParent.removeChild(definition);
150
- inlineCodepParent.removeEventListener('scroll', tooltipOnResize);
151
- isListenerNeeded = true;
152
- }
153
-
154
- function createTooltip() {
155
- let tooltip = getTooltipElement();
156
-
157
- if (!tooltip) {
158
- const pageContent = document.querySelector('.dc-doc-page__content') || document.body;
159
- const lang = document.documentElement.lang || 'en';
160
- const tooltipText = LANG_TOKEN[lang as Lang] ?? LANG_TOKEN.en;
161
- const host = document.createElement('div');
162
-
163
- host.innerHTML = `
164
- <div id="${INLINE_CODE_ID}" class="${INLINE_CODE_CLASS}"
165
- role="dialog" aria-live="polite" aria-modal="true">
166
- ${tooltipText}
167
- </div>
168
- `;
169
-
170
- tooltip = host.firstElementChild as HTMLElement;
171
- pageContent.appendChild(tooltip);
172
- }
173
-
174
- return tooltip;
175
- }
176
-
177
- export function openTooltip(target: HTMLElement) {
178
- const tooltip = createTooltip();
179
-
180
- if (!target.matches(INLINE_CODE) || !tooltip) {
181
- return;
182
- }
183
-
184
- tooltip.setAttribute('inline-id', target.getAttribute('id') || '');
185
- setTooltipAriaAttributes(tooltip, target);
186
- setTooltipPosition(tooltip, target);
187
-
188
- // In order not to get rid of the smooth appearance effect, I had to do this
189
- if (tooltip.classList.contains(OPEN_CLASS)) {
190
- tooltip.classList.remove(OPEN_CLASS);
191
- requestAnimationFrame(() => {
192
- tooltip.classList.add(OPEN_CLASS);
193
- });
194
- } else {
195
- tooltip.classList.add(OPEN_CLASS);
196
- }
197
-
198
- return tooltip;
199
- }
200
-
201
- export function closeTooltip(target: HTMLElement) {
202
- checkTimerAndClear();
203
- closeTooltipFn(target);
204
- }
205
-
206
- export function tooltipWorker(target: HTMLElement) {
207
- const definition = openTooltip(target);
208
-
209
- if (!definition) {
210
- return;
211
- }
212
- checkTimerAndClear();
213
- timer = setTimeout(() => {
214
- closeTooltip(definition);
215
- timer = null;
216
- }, 1000);
217
- }