@treelocator/runtime 0.5.2 → 0.6.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 (163) hide show
  1. package/.eslintignore +1 -0
  2. package/dist/_generated_styles.d.ts +1 -1
  3. package/dist/_generated_styles.js +20 -0
  4. package/dist/_generated_tree_icon.d.ts +1 -1
  5. package/dist/adapters/HtmlElementTreeNode.d.ts +2 -2
  6. package/dist/adapters/HtmlElementTreeNode.js +4 -6
  7. package/dist/adapters/createTreeNode.js +17 -44
  8. package/dist/adapters/detectFramework.d.ts +8 -0
  9. package/dist/adapters/detectFramework.js +25 -0
  10. package/dist/adapters/detectFramework.test.d.ts +1 -0
  11. package/dist/adapters/detectFramework.test.js +60 -0
  12. package/dist/adapters/jsx/jsxAdapter.js +54 -89
  13. package/dist/adapters/jsx/jsxAdapter.test.d.ts +1 -0
  14. package/dist/adapters/jsx/jsxAdapter.test.js +273 -0
  15. package/dist/adapters/nextjs/parseNextjsDataAttributes.js +1 -1
  16. package/dist/adapters/nextjs/parseNextjsDataAttributes.test.d.ts +1 -0
  17. package/dist/adapters/nextjs/parseNextjsDataAttributes.test.js +158 -0
  18. package/dist/adapters/react/findFiberByHtmlElement.d.ts +1 -1
  19. package/dist/adapters/react/findFiberByHtmlElement.js +1 -1
  20. package/dist/adapters/react/getAllParentsElementsAndRootComponent.js +4 -0
  21. package/dist/adapters/resolveAdapter.d.ts +1 -1
  22. package/dist/adapters/resolveAdapter.js +4 -8
  23. package/dist/adapters/svelte/svelteAdapter.test.d.ts +1 -0
  24. package/dist/adapters/svelte/svelteAdapter.test.js +280 -0
  25. package/dist/adapters/vue/vueAdapter.test.d.ts +1 -0
  26. package/dist/adapters/vue/vueAdapter.test.js +222 -0
  27. package/dist/browserApi.d.ts +148 -0
  28. package/dist/browserApi.js +146 -5
  29. package/dist/browserApi.test.d.ts +1 -0
  30. package/dist/browserApi.test.js +287 -0
  31. package/dist/components/RecordingPillButton.d.ts +11 -0
  32. package/dist/components/RecordingPillButton.js +202 -0
  33. package/dist/components/RecordingResults.d.ts +2 -0
  34. package/dist/components/RecordingResults.js +213 -78
  35. package/dist/components/Runtime.js +161 -554
  36. package/dist/components/SettingsPanel.d.ts +5 -0
  37. package/dist/components/SettingsPanel.js +312 -0
  38. package/dist/consoleCapture.d.ts +9 -0
  39. package/dist/consoleCapture.js +95 -0
  40. package/dist/functions/cssRuleInspector.d.ts +83 -0
  41. package/dist/functions/cssRuleInspector.js +608 -0
  42. package/dist/functions/cssRuleInspector.test.d.ts +1 -0
  43. package/dist/functions/cssRuleInspector.test.js +439 -0
  44. package/dist/functions/deduplicateLabels.test.d.ts +1 -0
  45. package/dist/functions/deduplicateLabels.test.js +178 -0
  46. package/dist/functions/enrichAncestrySourceMaps.js +0 -1
  47. package/dist/functions/extractComputedStyles.d.ts +51 -0
  48. package/dist/functions/extractComputedStyles.js +447 -0
  49. package/dist/functions/extractComputedStyles.test.d.ts +1 -0
  50. package/dist/functions/extractComputedStyles.test.js +549 -0
  51. package/dist/functions/formatAncestryChain.d.ts +8 -0
  52. package/dist/functions/formatAncestryChain.js +21 -1
  53. package/dist/functions/formatAncestryChain.test.js +18 -0
  54. package/dist/functions/getUsableName.test.d.ts +1 -0
  55. package/dist/functions/getUsableName.test.js +219 -0
  56. package/dist/functions/isCombinationModifiersPressed.test.d.ts +1 -0
  57. package/dist/functions/isCombinationModifiersPressed.test.js +192 -0
  58. package/dist/functions/mergeRects.test.js +210 -1
  59. package/dist/functions/namedSnapshots.d.ts +52 -0
  60. package/dist/functions/namedSnapshots.js +161 -0
  61. package/dist/functions/namedSnapshots.test.d.ts +1 -0
  62. package/dist/functions/namedSnapshots.test.js +85 -0
  63. package/dist/functions/normalizeFilePath.test.d.ts +1 -0
  64. package/dist/functions/normalizeFilePath.test.js +66 -0
  65. package/dist/functions/parseDataId.test.d.ts +1 -0
  66. package/dist/functions/parseDataId.test.js +101 -0
  67. package/dist/hooks/getStorage.d.ts +3 -0
  68. package/dist/hooks/getStorage.js +17 -0
  69. package/dist/hooks/useEventListeners.d.ts +15 -0
  70. package/dist/hooks/useEventListeners.js +56 -0
  71. package/dist/hooks/useLocatorStorage.d.ts +18 -0
  72. package/dist/hooks/useLocatorStorage.js +41 -0
  73. package/dist/hooks/useLocatorStorage.test.d.ts +1 -0
  74. package/dist/hooks/useLocatorStorage.test.js +124 -0
  75. package/dist/hooks/useRecordingState.d.ts +43 -0
  76. package/dist/hooks/useRecordingState.js +387 -0
  77. package/dist/hooks/useSettings.d.ts +13 -0
  78. package/dist/hooks/useSettings.js +66 -0
  79. package/dist/index.d.ts +5 -2
  80. package/dist/index.js +4 -2
  81. package/dist/initRuntime.d.ts +3 -1
  82. package/dist/initRuntime.js +4 -1
  83. package/dist/mcpBridge.d.ts +61 -0
  84. package/dist/mcpBridge.js +534 -0
  85. package/dist/mcpBridge.test.d.ts +1 -0
  86. package/dist/mcpBridge.test.js +248 -0
  87. package/dist/output.css +20 -0
  88. package/dist/visualDiff/diff.d.ts +9 -0
  89. package/dist/visualDiff/diff.js +209 -0
  90. package/dist/visualDiff/diff.test.d.ts +1 -0
  91. package/dist/visualDiff/diff.test.js +253 -0
  92. package/dist/visualDiff/settle.d.ts +3 -0
  93. package/dist/visualDiff/settle.js +50 -0
  94. package/dist/visualDiff/settle.test.d.ts +1 -0
  95. package/dist/visualDiff/settle.test.js +65 -0
  96. package/dist/visualDiff/snapshot.d.ts +4 -0
  97. package/dist/visualDiff/snapshot.js +84 -0
  98. package/dist/visualDiff/snapshot.test.d.ts +1 -0
  99. package/dist/visualDiff/snapshot.test.js +245 -0
  100. package/dist/visualDiff/types.d.ts +37 -0
  101. package/dist/visualDiff/types.js +1 -0
  102. package/package.json +2 -2
  103. package/scripts/wrapCSS.js +1 -1
  104. package/scripts/wrapImage.js +1 -1
  105. package/src/_generated_styles.ts +21 -1
  106. package/src/_generated_tree_icon.ts +1 -1
  107. package/src/adapters/HtmlElementTreeNode.ts +10 -7
  108. package/src/adapters/createTreeNode.ts +12 -51
  109. package/src/adapters/detectFramework.test.ts +73 -0
  110. package/src/adapters/detectFramework.ts +28 -0
  111. package/src/adapters/jsx/jsxAdapter.test.ts +240 -0
  112. package/src/adapters/jsx/jsxAdapter.ts +53 -106
  113. package/src/adapters/nextjs/parseNextjsDataAttributes.test.ts +212 -0
  114. package/src/adapters/nextjs/parseNextjsDataAttributes.ts +1 -1
  115. package/src/adapters/react/findDebugSource.ts +5 -6
  116. package/src/adapters/react/findFiberByHtmlElement.ts +3 -3
  117. package/src/adapters/react/getAllParentsElementsAndRootComponent.ts +3 -0
  118. package/src/adapters/react/reactAdapter.ts +1 -2
  119. package/src/adapters/resolveAdapter.ts +4 -14
  120. package/src/adapters/svelte/svelteAdapter.test.ts +334 -0
  121. package/src/adapters/vue/vueAdapter.test.ts +259 -0
  122. package/src/browserApi.test.ts +329 -0
  123. package/src/browserApi.ts +351 -4
  124. package/src/components/RecordingPillButton.tsx +301 -0
  125. package/src/components/RecordingResults.tsx +114 -13
  126. package/src/components/Runtime.tsx +176 -621
  127. package/src/components/SettingsPanel.tsx +339 -0
  128. package/src/consoleCapture.ts +113 -0
  129. package/src/functions/cssRuleInspector.test.ts +517 -0
  130. package/src/functions/cssRuleInspector.ts +708 -0
  131. package/src/functions/deduplicateLabels.test.ts +115 -0
  132. package/src/functions/enrichAncestrySourceMaps.ts +6 -3
  133. package/src/functions/extractComputedStyles.test.ts +681 -0
  134. package/src/functions/extractComputedStyles.ts +768 -0
  135. package/src/functions/formatAncestryChain.test.ts +23 -1
  136. package/src/functions/formatAncestryChain.ts +22 -1
  137. package/src/functions/getUsableName.test.ts +242 -0
  138. package/src/functions/isCombinationModifiersPressed.test.ts +156 -0
  139. package/src/functions/mergeRects.test.ts +111 -1
  140. package/src/functions/namedSnapshots.test.ts +106 -0
  141. package/src/functions/namedSnapshots.ts +232 -0
  142. package/src/functions/normalizeFilePath.test.ts +80 -0
  143. package/src/functions/parseDataId.test.ts +125 -0
  144. package/src/hooks/getStorage.ts +26 -0
  145. package/src/hooks/useEventListeners.ts +97 -0
  146. package/src/hooks/useLocatorStorage.test.ts +127 -0
  147. package/src/hooks/useLocatorStorage.ts +60 -0
  148. package/src/hooks/useRecordingState.ts +516 -0
  149. package/src/hooks/useSettings.ts +83 -0
  150. package/src/index.ts +10 -5
  151. package/src/initRuntime.ts +5 -0
  152. package/src/mcpBridge.test.ts +260 -0
  153. package/src/mcpBridge.ts +677 -0
  154. package/src/visualDiff/diff.test.ts +167 -0
  155. package/src/visualDiff/diff.ts +242 -0
  156. package/src/visualDiff/settle.test.ts +77 -0
  157. package/src/visualDiff/settle.ts +62 -0
  158. package/src/visualDiff/snapshot.test.ts +200 -0
  159. package/src/visualDiff/snapshot.ts +119 -0
  160. package/src/visualDiff/types.ts +40 -0
  161. package/tsconfig.json +3 -1
  162. package/vitest.config.ts +18 -0
  163. package/jest.config.ts +0 -195
@@ -0,0 +1,245 @@
1
+ import { describe, expect, test, beforeEach, afterEach, vi } from "vitest";
2
+ import { takeSnapshot, MAX_SNAPSHOT_ELEMENTS } from "./snapshot";
3
+ function stubRect(el, rect) {
4
+ const full = {
5
+ x: rect.x ?? 0,
6
+ y: rect.y ?? 0,
7
+ width: rect.width ?? 100,
8
+ height: rect.height ?? 100,
9
+ top: rect.y ?? 0,
10
+ left: rect.x ?? 0,
11
+ right: (rect.x ?? 0) + (rect.width ?? 100),
12
+ bottom: (rect.y ?? 0) + (rect.height ?? 100),
13
+ toJSON() {
14
+ return this;
15
+ }
16
+ };
17
+ el.getBoundingClientRect = () => full;
18
+ }
19
+ describe("takeSnapshot", () => {
20
+ beforeEach(() => {
21
+ document.body.innerHTML = "";
22
+ Object.defineProperty(window, "innerWidth", {
23
+ writable: true,
24
+ configurable: true,
25
+ value: 1280
26
+ });
27
+ Object.defineProperty(window, "innerHeight", {
28
+ writable: true,
29
+ configurable: true,
30
+ value: 720
31
+ });
32
+ });
33
+ afterEach(() => {
34
+ document.body.innerHTML = "";
35
+ });
36
+ test("returns ElementSnapshot array with expected fields", () => {
37
+ const div = document.createElement("div");
38
+ div.id = "hello";
39
+ div.className = "foo bar";
40
+ div.textContent = "Hi there";
41
+ document.body.appendChild(div);
42
+ stubRect(div, {
43
+ x: 10,
44
+ y: 20,
45
+ width: 100,
46
+ height: 50
47
+ });
48
+ const snaps = takeSnapshot();
49
+ const found = snaps.find(s => s.id === "hello");
50
+ expect(found).toBeDefined();
51
+ expect(found.tagName).toBe("div");
52
+ expect(found.classes).toEqual(["foo", "bar"]);
53
+ expect(found.x).toBe(10);
54
+ expect(found.y).toBe(20);
55
+ expect(found.width).toBe(100);
56
+ expect(found.height).toBe(50);
57
+ expect(found.text).toBe("Hi there");
58
+ expect(found.visible).toBe(true);
59
+ expect(found.inViewport).toBe(true);
60
+ expect(typeof found.key).toBe("string");
61
+ });
62
+ test("excludes elements inside #locatorjs-wrapper", () => {
63
+ const wrapper = document.createElement("div");
64
+ wrapper.id = "locatorjs-wrapper";
65
+ const child = document.createElement("span");
66
+ child.id = "overlay-child";
67
+ wrapper.appendChild(child);
68
+ document.body.appendChild(wrapper);
69
+ stubRect(child, {
70
+ x: 0,
71
+ y: 0,
72
+ width: 50,
73
+ height: 50
74
+ });
75
+ const snaps = takeSnapshot();
76
+ expect(snaps.find(s => s.id === "overlay-child")).toBeUndefined();
77
+ expect(snaps.find(s => s.id === "locatorjs-wrapper")).toBeUndefined();
78
+ });
79
+ test("elements outside viewport have inViewport: false and are skipped", () => {
80
+ const div = document.createElement("div");
81
+ div.id = "off-screen";
82
+ document.body.appendChild(div);
83
+ stubRect(div, {
84
+ x: 5000,
85
+ y: 5000,
86
+ width: 100,
87
+ height: 100
88
+ });
89
+ const snaps = takeSnapshot();
90
+ expect(snaps.find(s => s.id === "off-screen")).toBeUndefined();
91
+ });
92
+ test("text is trimmed and capped at 80 chars", () => {
93
+ const div = document.createElement("div");
94
+ div.id = "long-text";
95
+ div.textContent = " " + "x".repeat(200) + " ";
96
+ document.body.appendChild(div);
97
+ stubRect(div, {
98
+ x: 0,
99
+ y: 0,
100
+ width: 100,
101
+ height: 100
102
+ });
103
+ const snaps = takeSnapshot();
104
+ const found = snaps.find(s => s.id === "long-text");
105
+ expect(found).toBeDefined();
106
+ expect(found.text.length).toBe(80);
107
+ expect(found.text.startsWith("x")).toBe(true);
108
+ });
109
+ test("display:none elements have visible: false", () => {
110
+ const div = document.createElement("div");
111
+ div.id = "hidden";
112
+ div.style.display = "none";
113
+ document.body.appendChild(div);
114
+ stubRect(div, {
115
+ x: 0,
116
+ y: 0,
117
+ width: 100,
118
+ height: 100
119
+ });
120
+ const snaps = takeSnapshot();
121
+ const found = snaps.find(s => s.id === "hidden");
122
+ expect(found?.visible).toBe(false);
123
+ });
124
+ test("disabled form elements have disabled: true", () => {
125
+ const btn = document.createElement("button");
126
+ btn.id = "btn";
127
+ btn.disabled = true;
128
+ document.body.appendChild(btn);
129
+ stubRect(btn, {
130
+ x: 0,
131
+ y: 0,
132
+ width: 80,
133
+ height: 30
134
+ });
135
+ const snaps = takeSnapshot();
136
+ const found = snaps.find(s => s.id === "btn");
137
+ expect(found?.disabled).toBe(true);
138
+ });
139
+ test("caps at MAX_SNAPSHOT_ELEMENTS", () => {
140
+ vi.spyOn(console, "warn").mockImplementation(() => {});
141
+ for (let i = 0; i < MAX_SNAPSHOT_ELEMENTS + 200; i++) {
142
+ const d = document.createElement("div");
143
+ document.body.appendChild(d);
144
+ stubRect(d, {
145
+ x: 0,
146
+ y: 0,
147
+ width: 10,
148
+ height: 10
149
+ });
150
+ }
151
+ const snaps = takeSnapshot();
152
+ expect(snaps.length).toBeLessThanOrEqual(MAX_SNAPSHOT_ELEMENTS);
153
+ });
154
+ test("text is only captured for leaf elements (no element children)", () => {
155
+ const parent = document.createElement("div");
156
+ parent.id = "parent";
157
+ const child = document.createElement("span");
158
+ child.id = "child";
159
+ child.textContent = "leaf text";
160
+ parent.appendChild(child);
161
+ document.body.appendChild(parent);
162
+ stubRect(parent, {
163
+ x: 0,
164
+ y: 0,
165
+ width: 100,
166
+ height: 100
167
+ });
168
+ stubRect(child, {
169
+ x: 0,
170
+ y: 0,
171
+ width: 50,
172
+ height: 20
173
+ });
174
+ const snaps = takeSnapshot();
175
+ const parentSnap = snaps.find(s => s.id === "parent");
176
+ const childSnap = snaps.find(s => s.id === "child");
177
+ expect(parentSnap?.text).toBeUndefined();
178
+ expect(childSnap?.text).toBe("leaf text");
179
+ });
180
+ test("scoped snapshot only includes root + descendants", () => {
181
+ const outside = document.createElement("div");
182
+ outside.id = "outside";
183
+ document.body.appendChild(outside);
184
+ stubRect(outside, {
185
+ x: 0,
186
+ y: 0,
187
+ width: 100,
188
+ height: 100
189
+ });
190
+ const root = document.createElement("div");
191
+ root.id = "root";
192
+ const child = document.createElement("span");
193
+ child.id = "inside-child";
194
+ root.appendChild(child);
195
+ document.body.appendChild(root);
196
+ stubRect(root, {
197
+ x: 10,
198
+ y: 10,
199
+ width: 100,
200
+ height: 100
201
+ });
202
+ stubRect(child, {
203
+ x: 10,
204
+ y: 10,
205
+ width: 50,
206
+ height: 20
207
+ });
208
+ const snaps = takeSnapshot(root);
209
+ const ids = snaps.map(s => s.id).filter(Boolean);
210
+ expect(ids).toContain("root");
211
+ expect(ids).toContain("inside-child");
212
+ expect(ids).not.toContain("outside");
213
+ });
214
+ test("strips locatorjs-* classes from the classes array", () => {
215
+ const div = document.createElement("div");
216
+ div.id = "with-locator-class";
217
+ div.className = "foo locatorjs-active-pointer bar";
218
+ document.body.appendChild(div);
219
+ stubRect(div, {
220
+ x: 0,
221
+ y: 0,
222
+ width: 50,
223
+ height: 50
224
+ });
225
+ const snaps = takeSnapshot();
226
+ const found = snaps.find(s => s.id === "with-locator-class");
227
+ expect(found?.classes).toEqual(["foo", "bar"]);
228
+ });
229
+ test("assigns stable keys for the same element across snapshots", () => {
230
+ const div = document.createElement("div");
231
+ div.id = "stable";
232
+ document.body.appendChild(div);
233
+ stubRect(div, {
234
+ x: 0,
235
+ y: 0,
236
+ width: 50,
237
+ height: 50
238
+ });
239
+ const a = takeSnapshot();
240
+ const b = takeSnapshot();
241
+ const aKey = a.find(s => s.id === "stable").key;
242
+ const bKey = b.find(s => s.id === "stable").key;
243
+ expect(aKey).toBe(bKey);
244
+ });
245
+ });
@@ -0,0 +1,37 @@
1
+ export interface ElementSnapshot {
2
+ key: string;
3
+ tagName: string;
4
+ id?: string;
5
+ classes: string[];
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ visible: boolean;
11
+ opacity: number;
12
+ inViewport: boolean;
13
+ pointerEvents: string;
14
+ disabled: boolean;
15
+ text?: string;
16
+ }
17
+ export type DeltaChangeType = "+" | "-" | "~" | "→";
18
+ export interface DeltaEntry {
19
+ type: DeltaChangeType;
20
+ key: string;
21
+ label: string;
22
+ before?: ElementSnapshot;
23
+ after?: ElementSnapshot;
24
+ changedFields?: string[];
25
+ }
26
+ export interface DeltaReport {
27
+ elapsedMs: number;
28
+ settle: "clean" | "timeout";
29
+ counts: {
30
+ added: number;
31
+ removed: number;
32
+ changed: number;
33
+ moved: number;
34
+ };
35
+ entries: DeltaEntry[];
36
+ text: string;
37
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treelocator/runtime",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "TreeLocatorJS runtime for component ancestry tracking. Alt+click any element to copy its component tree to clipboard. Exposes window.__treelocator__ API for browser automation (Playwright, Puppeteer, Selenium, Cypress).",
5
5
  "keywords": [
6
6
  "locator",
@@ -47,7 +47,7 @@
47
47
  "dependencies": {
48
48
  "@floating-ui/dom": "^1.6.12",
49
49
  "@locator/shared": "^0.5.0",
50
- "solid-js": "^1.9.2",
50
+ "solid-js": "^1.9.11",
51
51
  "tailwindcss": "^3.4.14"
52
52
  },
53
53
  "devDependencies": {
@@ -13,7 +13,7 @@ async function run() {
13
13
  .replaceAll("\\]", "\\\\]")
14
14
  .replaceAll("\\.", "\\\\.")
15
15
  .replaceAll("\\/", "\\\\/");
16
- const wrapped = `const styles: string = \`${escaped}\`;\nexport default styles;`;
16
+ const wrapped = `const styles = \`${escaped}\`;\nexport default styles;`;
17
17
 
18
18
  await fs.writeFile("./src/_generated_styles.ts", wrapped);
19
19
  console.log("CSS file generated");
@@ -10,7 +10,7 @@ async function run() {
10
10
  const content = await fs.readFile(imagePath);
11
11
  const base64 = content.toString("base64");
12
12
  const dataUrl = `data:image/png;base64,${base64}`;
13
- const wrapped = `const treeIcon: string = "${dataUrl}";\nexport default treeIcon;`;
13
+ const wrapped = `const treeIcon = "${dataUrl}";\nexport default treeIcon;`;
14
14
 
15
15
  await fs.writeFile("./src/_generated_tree_icon.ts", wrapped);
16
16
  console.log("Tree icon file generated");
@@ -1,4 +1,4 @@
1
- const styles: string = `*, ::before, ::after {
1
+ const styles = `*, ::before, ::after {
2
2
  --tw-border-spacing-x: 0;
3
3
  --tw-border-spacing-y: 0;
4
4
  --tw-translate-x: 0;
@@ -815,6 +815,10 @@ input:where([type='file']):focus {
815
815
  visibility: collapse;
816
816
  }
817
817
 
818
+ .static {
819
+ position: static;
820
+ }
821
+
818
822
  .fixed {
819
823
  position: fixed;
820
824
  }
@@ -891,6 +895,10 @@ input:where([type='file']):focus {
891
895
  top: 0.25rem;
892
896
  }
893
897
 
898
+ .isolate {
899
+ isolation: isolate;
900
+ }
901
+
894
902
  .z-10 {
895
903
  z-index: 10;
896
904
  }
@@ -981,6 +989,10 @@ input:where([type='file']):focus {
981
989
  display: contents;
982
990
  }
983
991
 
992
+ .\!hidden {
993
+ display: none !important;
994
+ }
995
+
984
996
  .hidden {
985
997
  display: none;
986
998
  }
@@ -1113,6 +1125,10 @@ input:where([type='file']):focus {
1113
1125
  flex-direction: column;
1114
1126
  }
1115
1127
 
1128
+ .flex-wrap {
1129
+ flex-wrap: wrap;
1130
+ }
1131
+
1116
1132
  .items-center {
1117
1133
  align-items: center;
1118
1134
  }
@@ -1558,6 +1574,10 @@ input:where([type='file']):focus {
1558
1574
  text-transform: lowercase;
1559
1575
  }
1560
1576
 
1577
+ .italic {
1578
+ font-style: italic;
1579
+ }
1580
+
1561
1581
  .text-black {
1562
1582
  --tw-text-opacity: 1;
1563
1583
  color: rgb(0 0 0 / var(--tw-text-opacity, 1));