@synergy-design-system/metadata 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"custom": {},
|
|
3
|
+
"id": "template:tooltip",
|
|
4
|
+
"kind": "template",
|
|
5
|
+
"layers": {
|
|
6
|
+
"examples": [
|
|
7
|
+
{
|
|
8
|
+
"layer": "examples",
|
|
9
|
+
"path": "layers/examples/template/template:tooltip.md"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"name": "Tooltip",
|
|
14
|
+
"package": "docs",
|
|
15
|
+
"relations": [],
|
|
16
|
+
"since": "1.0.0",
|
|
17
|
+
"sources": [],
|
|
18
|
+
"status": "stable",
|
|
19
|
+
"tags": [
|
|
20
|
+
"template"
|
|
21
|
+
]
|
|
22
|
+
}
|
package/data/index.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"builtAt": "2026-05-
|
|
2
|
+
"builtAt": "2026-05-11T08:29:10.575Z",
|
|
3
3
|
"entities": [
|
|
4
4
|
{
|
|
5
5
|
"corePath": "data/core/asset/asset:sick2018-icons.json",
|
|
@@ -1559,6 +1559,20 @@
|
|
|
1559
1559
|
"template"
|
|
1560
1560
|
]
|
|
1561
1561
|
},
|
|
1562
|
+
{
|
|
1563
|
+
"corePath": "data/core/template/template:tooltip.json",
|
|
1564
|
+
"id": "template:tooltip",
|
|
1565
|
+
"kind": "template",
|
|
1566
|
+
"layers": {
|
|
1567
|
+
"examples": 1
|
|
1568
|
+
},
|
|
1569
|
+
"name": "Tooltip",
|
|
1570
|
+
"search": [
|
|
1571
|
+
"template:tooltip",
|
|
1572
|
+
"Tooltip",
|
|
1573
|
+
"template"
|
|
1574
|
+
]
|
|
1575
|
+
},
|
|
1562
1576
|
{
|
|
1563
1577
|
"corePath": "data/core/token/token:tokens-figma-variables-sick2018-dark-json.json",
|
|
1564
1578
|
"id": "token:tokens-figma-variables-sick2018-dark-json",
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
## Single Instance
|
|
2
|
+
|
|
3
|
+
This example demonstrates the usage of a single instance of the <syn-tooltip> component. Tooltips are shared across multiple elements, allowing for more efficient management and consistent behavior.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<div id="tooltip-single-instance-story">
|
|
7
|
+
<syn-header label="Single Instance Tooltips"></syn-header>
|
|
8
|
+
|
|
9
|
+
<!-- Content Area -->
|
|
10
|
+
<section>
|
|
11
|
+
<!-- Dynamic Toolbar Section -->
|
|
12
|
+
<nav class="toolbar">
|
|
13
|
+
<syn-button-group label="Download and save">
|
|
14
|
+
<syn-button data-tooltip="Save">
|
|
15
|
+
<syn-icon name="save" label="Save"></syn-icon>
|
|
16
|
+
</syn-button>
|
|
17
|
+
<syn-button data-tooltip="Download">
|
|
18
|
+
<syn-icon name="save_alt" label="Download"></syn-icon>
|
|
19
|
+
</syn-button>
|
|
20
|
+
</syn-button-group>
|
|
21
|
+
|
|
22
|
+
<syn-button-group label="Misc">
|
|
23
|
+
<syn-button data-tooltip="Edit">
|
|
24
|
+
<syn-icon name="edit" label="Edit"></syn-icon>
|
|
25
|
+
</syn-button>
|
|
26
|
+
<syn-button data-tooltip="Settings">
|
|
27
|
+
<syn-icon name="settings" label="Settings"></syn-icon>
|
|
28
|
+
</syn-button>
|
|
29
|
+
<syn-button data-tooltip="Preview">
|
|
30
|
+
<syn-icon name="wallpaper" label="Preview"></syn-icon>
|
|
31
|
+
</syn-button>
|
|
32
|
+
</syn-button-group>
|
|
33
|
+
</nav>
|
|
34
|
+
|
|
35
|
+
<article>
|
|
36
|
+
<h2>Single instance tooltips</h2>
|
|
37
|
+
<p>
|
|
38
|
+
By default, you will have to wrap each element that needs a tooltip with
|
|
39
|
+
a separate
|
|
40
|
+
<code data-tooltip="<syn-tooltip>My Content</syn-tooltip>"
|
|
41
|
+
><syn-tooltip></code
|
|
42
|
+
>
|
|
43
|
+
component. If you want to share a single instance of
|
|
44
|
+
<code><syn-tooltip></code> across an application, you can do so by
|
|
45
|
+
using the
|
|
46
|
+
<code
|
|
47
|
+
data-tooltip="anchor is a property on the SynTooltip class. It allows you to specify the element the tooltip should be anchored to."
|
|
48
|
+
>anchor</code
|
|
49
|
+
>
|
|
50
|
+
attribute to manually control which element the tooltip is anchored to.
|
|
51
|
+
This example will show how you can set up a single tooltip instance that
|
|
52
|
+
will work for all elements with a <code>data-tooltip</code> attribute,
|
|
53
|
+
even if those elements are added dynamically after the initial setup.
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<p>
|
|
57
|
+
This approach is useful for cases where you have a large number of
|
|
58
|
+
tooltip triggers or dynamically generated content, as it avoids the
|
|
59
|
+
overhead of multiple tooltip instances and allows for more flexible
|
|
60
|
+
tooltip management. However, it also comes with some caveats, such as
|
|
61
|
+
the need to manage tooltip state and transitions to avoid issues like
|
|
62
|
+
flickering or incorrect positioning when rapidly switching between
|
|
63
|
+
triggers. This example also currently only works on hover.
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
<syn-button
|
|
67
|
+
data-tooltip="Dynamically adds or removes the toolbar custom section"
|
|
68
|
+
id="add-section-button"
|
|
69
|
+
variant="filled"
|
|
70
|
+
>
|
|
71
|
+
Toggle custom section
|
|
72
|
+
</syn-button>
|
|
73
|
+
</article>
|
|
74
|
+
</section>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- Global Tooltip instance. Make sure that it has trigger="manual" set. -->
|
|
78
|
+
<syn-tooltip id="global-tooltip" trigger="manual"></syn-tooltip>
|
|
79
|
+
|
|
80
|
+
<style>
|
|
81
|
+
#tooltip-single-instance-story {
|
|
82
|
+
.toolbar {
|
|
83
|
+
display: flex;
|
|
84
|
+
padding: var(--syn-spacing-medium);
|
|
85
|
+
gap: var(--syn-spacing-medium);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
section {
|
|
89
|
+
background: var(--syn-page-background);
|
|
90
|
+
}
|
|
91
|
+
section [data-tooltip]:not(syn-button) {
|
|
92
|
+
cursor: help;
|
|
93
|
+
text-decoration: underline dotted;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
article {
|
|
97
|
+
padding: var(--syn-spacing-medium);
|
|
98
|
+
h2 {
|
|
99
|
+
margin: 0 0 var(--syn-spacing-medium) 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
104
|
+
|
|
105
|
+
<script type="module">
|
|
106
|
+
/**
|
|
107
|
+
* Installs singleton tooltip behavior for all descendants that use [data-tooltip].
|
|
108
|
+
*
|
|
109
|
+
* Why this utility is async:
|
|
110
|
+
* - It can be called immediately, even before custom elements are fully defined.
|
|
111
|
+
* - It waits for the tooltip component to become ready and only then binds listeners.
|
|
112
|
+
*
|
|
113
|
+
* Why there are multiple listeners:
|
|
114
|
+
* - mouseover (capture): detects entering any current or future [data-tooltip] trigger.
|
|
115
|
+
* - mouseout (capture): detects leaving the current trigger to hide or switch anchor.
|
|
116
|
+
* - syn-after-show / syn-after-hide: serializes transitions to avoid open/close races.
|
|
117
|
+
*
|
|
118
|
+
* @param {HTMLElement} tooltipElement A <syn-tooltip trigger="manual"> instance.
|
|
119
|
+
* @param {ParentNode} root Event delegation root. Defaults to document.
|
|
120
|
+
* @returns {Promise<() => void>} Cleanup function that removes all listeners.
|
|
121
|
+
*/
|
|
122
|
+
const setupTooltip = async (tooltipElement, root = document) => {
|
|
123
|
+
if (!(tooltipElement instanceof HTMLElement)) {
|
|
124
|
+
throw new TypeError("setupTooltip requires a tooltip DOM node.");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const cleanupKey = "__synSingletonTooltipCleanup__";
|
|
128
|
+
if (typeof tooltipElement[cleanupKey] === "function") {
|
|
129
|
+
tooltipElement[cleanupKey]();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await customElements.whenDefined("syn-tooltip");
|
|
133
|
+
await tooltipElement.updateComplete;
|
|
134
|
+
|
|
135
|
+
const delegatedRoot = root ?? document;
|
|
136
|
+
let requestedAnchor = null;
|
|
137
|
+
let transitionInFlight = false;
|
|
138
|
+
|
|
139
|
+
const getTooltipTarget = (node) => {
|
|
140
|
+
if (!(node instanceof Element)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return node.closest("[data-tooltip]");
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const getTooltipContent = (element) =>
|
|
148
|
+
element.getAttribute("data-tooltip")?.trim() ?? "";
|
|
149
|
+
|
|
150
|
+
const isSyncNeeded = () => {
|
|
151
|
+
const currentAnchor =
|
|
152
|
+
tooltipElement.anchor instanceof HTMLElement
|
|
153
|
+
? tooltipElement.anchor
|
|
154
|
+
: null;
|
|
155
|
+
|
|
156
|
+
if (requestedAnchor) {
|
|
157
|
+
const nextContent = getTooltipContent(requestedAnchor);
|
|
158
|
+
if (nextContent === "") {
|
|
159
|
+
return tooltipElement.open;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
!tooltipElement.open ||
|
|
164
|
+
currentAnchor !== requestedAnchor ||
|
|
165
|
+
tooltipElement.content !== nextContent
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return tooltipElement.open;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const syncTooltipState = () => {
|
|
173
|
+
if (transitionInFlight) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const currentAnchor =
|
|
178
|
+
tooltipElement.anchor instanceof HTMLElement
|
|
179
|
+
? tooltipElement.anchor
|
|
180
|
+
: null;
|
|
181
|
+
|
|
182
|
+
if (requestedAnchor) {
|
|
183
|
+
const nextContent = getTooltipContent(requestedAnchor);
|
|
184
|
+
|
|
185
|
+
if (nextContent === "") {
|
|
186
|
+
requestedAnchor = null;
|
|
187
|
+
} else if (tooltipElement.open && currentAnchor !== requestedAnchor) {
|
|
188
|
+
transitionInFlight = true;
|
|
189
|
+
tooltipElement.open = false;
|
|
190
|
+
return;
|
|
191
|
+
} else {
|
|
192
|
+
tooltipElement.content = nextContent;
|
|
193
|
+
tooltipElement.anchor = requestedAnchor;
|
|
194
|
+
|
|
195
|
+
if (!tooltipElement.open) {
|
|
196
|
+
transitionInFlight = true;
|
|
197
|
+
tooltipElement.open = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (tooltipElement.open) {
|
|
205
|
+
transitionInFlight = true;
|
|
206
|
+
tooltipElement.open = false;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const handleAfterShow = () => {
|
|
211
|
+
transitionInFlight = false;
|
|
212
|
+
if (isSyncNeeded()) {
|
|
213
|
+
syncTooltipState();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const handleAfterHide = () => {
|
|
218
|
+
transitionInFlight = false;
|
|
219
|
+
if (isSyncNeeded()) {
|
|
220
|
+
syncTooltipState();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const handleMouseOver = (event) => {
|
|
225
|
+
const target = getTooltipTarget(event.target);
|
|
226
|
+
const relatedTarget = getTooltipTarget(event.relatedTarget);
|
|
227
|
+
|
|
228
|
+
if (!(target instanceof HTMLElement) || target === relatedTarget) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (getTooltipContent(target) === "") {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
requestedAnchor = target;
|
|
237
|
+
syncTooltipState();
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleMouseOut = (event) => {
|
|
241
|
+
const target = getTooltipTarget(event.target);
|
|
242
|
+
const relatedTarget = getTooltipTarget(event.relatedTarget);
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
!(target instanceof HTMLElement) ||
|
|
246
|
+
target === relatedTarget ||
|
|
247
|
+
requestedAnchor !== target
|
|
248
|
+
) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
requestedAnchor =
|
|
253
|
+
relatedTarget instanceof HTMLElement ? relatedTarget : null;
|
|
254
|
+
syncTooltipState();
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
tooltipElement.addEventListener("syn-after-show", handleAfterShow);
|
|
258
|
+
tooltipElement.addEventListener("syn-after-hide", handleAfterHide);
|
|
259
|
+
delegatedRoot.addEventListener("mouseover", handleMouseOver, true);
|
|
260
|
+
delegatedRoot.addEventListener("mouseout", handleMouseOut, true);
|
|
261
|
+
|
|
262
|
+
const cleanup = () => {
|
|
263
|
+
tooltipElement.removeEventListener("syn-after-show", handleAfterShow);
|
|
264
|
+
tooltipElement.removeEventListener("syn-after-hide", handleAfterHide);
|
|
265
|
+
delegatedRoot.removeEventListener("mouseover", handleMouseOver, true);
|
|
266
|
+
delegatedRoot.removeEventListener("mouseout", handleMouseOut, true);
|
|
267
|
+
if (tooltipElement.open) {
|
|
268
|
+
tooltipElement.open = false;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
tooltipElement[cleanupKey] = cleanup;
|
|
273
|
+
return cleanup;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const globalTooltip = document.getElementById("global-tooltip");
|
|
277
|
+
if (globalTooltip instanceof HTMLElement) {
|
|
278
|
+
setupTooltip(globalTooltip).catch((error) => {
|
|
279
|
+
console.error("Failed to initialize singleton tooltip setup.", error);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const addSectionButton = document.getElementById("add-section-button");
|
|
284
|
+
|
|
285
|
+
addSectionButton?.addEventListener("click", () => {
|
|
286
|
+
const root = document.querySelector(
|
|
287
|
+
"#tooltip-single-instance-story .toolbar",
|
|
288
|
+
);
|
|
289
|
+
const hasCustomSection = root?.querySelector(".custom-section");
|
|
290
|
+
|
|
291
|
+
if (root && !hasCustomSection) {
|
|
292
|
+
const newSection = document.createElement("syn-button-group");
|
|
293
|
+
newSection.classList.add("custom-section");
|
|
294
|
+
newSection.setAttribute("label", "Custom Section");
|
|
295
|
+
|
|
296
|
+
const newButton = document.createElement("syn-button");
|
|
297
|
+
newButton.setAttribute(
|
|
298
|
+
"data-tooltip",
|
|
299
|
+
"I am a dynamically added button with a tooltip!",
|
|
300
|
+
);
|
|
301
|
+
newButton.innerHTML = '<syn-icon name="star" label="Star"></syn-icon>';
|
|
302
|
+
|
|
303
|
+
newSection.appendChild(newButton);
|
|
304
|
+
root.appendChild(newSection);
|
|
305
|
+
} else if (hasCustomSection) {
|
|
306
|
+
hasCustomSection.remove();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
</script>
|
|
310
|
+
```
|
package/data/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
"serve-handler": "^6.1.7",
|
|
20
20
|
"typescript": "^5.9.3",
|
|
21
21
|
"zod": "^4.3.6",
|
|
22
|
+
"@synergy-design-system/components": "3.13.4",
|
|
22
23
|
"@synergy-design-system/eslint-config-syn": "^0.1.0",
|
|
23
|
-
"@synergy-design-system/styles": "2.1.0",
|
|
24
24
|
"@synergy-design-system/docs": "0.1.0",
|
|
25
|
-
"@synergy-design-system/
|
|
26
|
-
"@synergy-design-system/
|
|
27
|
-
"@synergy-design-system/
|
|
25
|
+
"@synergy-design-system/fonts": "1.0.6",
|
|
26
|
+
"@synergy-design-system/styles": "2.1.0",
|
|
27
|
+
"@synergy-design-system/tokens": "^3.13.4"
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
"types": "./dist/index.d.ts",
|
|
67
67
|
"type": "module",
|
|
68
|
-
"version": "3.
|
|
68
|
+
"version": "3.7.0",
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build:all": "METADATA_LOG_LEVEL=info pnpm run build:ts && RUN_STORYBOOK_SCRAPER=true pnpm run build:data",
|
|
71
71
|
"build": "METADATA_LOG_LEVEL=info pnpm run build:ts && pnpm run build:data",
|