@plumeria/inspector 16.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) zss-in-js contributer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @plumeria/inspector
2
+
3
+ A development inspector for visualizing and debugging styled components in the browser.
4
+
5
+ ## Features
6
+
7
+ - **Inlined CSS** — No additional `.css` imports required. The stylesheet is bundled into the JavaScript output.
8
+ - **Zero production overhead** — Renders `null` and skips CSS injection when `process.env.NODE_ENV === 'production'`. Safe to leave in your root layout.
9
+
10
+ ## Documentation(Demo)
11
+
12
+ Read the [documentation](https://plumeria.dev/docs/api-reference/components/inspector) for more details.
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm install -D @plumeria/inspector
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```tsx
22
+ import { Inspector } from '@plumeria/inspector';
23
+
24
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
25
+ return (
26
+ <html lang="en">
27
+ <body>
28
+ {children}
29
+ <Inspector />
30
+ </body>
31
+ </html>
32
+ );
33
+ }
34
+ ```
35
+
36
+ ## Keyboard Shortcuts
37
+
38
+ | Shortcut | Action |
39
+ | --- | --- |
40
+ | `Cmd + I` / `Ctrl + I` | Toggle inspector |
41
+ | `Shift` | Lock / unlock target element |
@@ -0,0 +1,4 @@
1
+ import * as React from 'react';
2
+ export declare const Inspector: ({ production }: {
3
+ production?: boolean;
4
+ }) => React.JSX.Element | null;
package/dist/index.js ADDED
@@ -0,0 +1,251 @@
1
+ "use client";
2
+
3
+ // src/index.tsx
4
+ import * as React from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var isCssInjected = false;
7
+ var injectInspectorCSS = () => {
8
+ if (typeof document === "undefined" || isCssInjected) return;
9
+ const styleId = "plumeria-inspector-styles";
10
+ if (document.getElementById(styleId)) {
11
+ isCssInjected = true;
12
+ return;
13
+ }
14
+ const style = document.createElement("style");
15
+ style.id = styleId;
16
+ style.textContent = "/* @plumeria/unplugin:/Users/sou/create-app/plumeria/packages/inspector/src/index.zero.css */\n.xm2svzo4 {\n position: fixed;\n}\n.xwargx9a {\n right: 16px;\n}\n.xnr54dgw {\n bottom: 16px;\n}\n.xhtz6c5t {\n z-index: 9999999;\n}\n.xxp6epoh {\n display: flex;\n}\n.xi3fytem {\n gap: 8px;\n}\n.xj0ln8hv {\n align-items: center;\n}\n.x636886i {\n padding: 8px 16px;\n}\n.x7z3vutq:not(#\\#) {\n font-family:\n system-ui,\n -apple-system,\n sans-serif;\n}\n.xhrr6ses:not(#\\#) {\n font-size: 12px;\n}\n.x6vb0uvf:not(#\\#) {\n font-weight: 500;\n}\n.xd3oqkpm {\n color: #e5e7eb;\n}\n.x6iuzqez {\n cursor: pointer;\n}\n.x1gd64ir {\n -webkit-user-select: none;\n user-select: none;\n}\n.xb90zzvn:not(#\\#) {\n background-color: #111827d9;\n}\n.x7vpbndt {\n border: 1px solid #ffffff1a;\n}\n.xvickkgg {\n border-radius: 9999px;\n}\n.xfcpkn9a {\n -webkit-backdrop-filter: blur(12px);\n backdrop-filter: blur(12px);\n}\n.x1wwwd6e {\n transition: all .2s;\n}\n.xtby5ou6 {\n border-color: #a855f7;\n}\n.xkqfgj67 {\n box-shadow: 0 4px 15px #00000040;\n}\n.xdp2dozu {\n box-shadow: 0 4px 20px #a855f759;\n}\n.xq0askt6 {\n width: 8px;\n}\n.xsemx3zn {\n height: 8px;\n}\n.xyqzlrhp {\n border-radius: 50%;\n}\n.xs6s67ix {\n transition: background-color .2s;\n}\n.xlufsbde {\n box-shadow: 0 0 8px #a855f7;\n}\n.xy2ymyq7:not(#\\#) {\n background-color: #9ca3af;\n}\n.xbv1awaq:not(#\\#) {\n background-color: #a855f7;\n}\n.xtzw270y {\n pointer-events: none;\n}\n.xzjvpdw4 {\n border: 1px dashed #a855f7;\n}\n.xwghexut:not(#\\#) {\n background-color: #a855f70f;\n}\n.xlcznmkr {\n z-index: 999999;\n}\n.xnnxtoqx {\n transition: all 80ms cubic-bezier(.16, 1, .3, 1);\n}\n.xka75a01 {\n top: var(--overlay-top);\n}\n.xzwctcax {\n left: var(--overlay-left);\n}\n.xgwg3gye {\n width: var(--overlay-width);\n}\n.xerytlo0 {\n height: var(--overlay-height);\n}\n.x4o5bm7x {\n z-index: 1000000;\n}\n.x1jethze:not(#\\#) {\n background-color: #111827e6;\n}\n.xp7py9n1 {\n border: 1px solid #ffffff1f;\n}\n.xhdqk8nx {\n border-radius: 12px;\n}\n.xr7ynoef {\n padding: 12px 16px;\n}\n.xv21k0fp {\n box-shadow: 0 10px 25px -5px #0000004d, 0 8px 10px -6px #0000004d;\n}\n.xjwv009s {\n color: #f3f4f6;\n}\n.xgm2v5ee {\n width: 320px;\n}\n.xsc1chnd {\n transition: opacity .15s ease-out, transform .15s ease-out;\n}\n.xx2zw91q {\n pointer-events: var(--tooltip-pointerEvents);\n}\n.xlucmkc1 {\n top: var(--tooltip-top);\n}\n.xk0pf7xw {\n left: var(--tooltip-left);\n}\n.x6apt1gh {\n opacity: var(--tooltip-opacity);\n}\n.xy3932pz {\n transform: var(--tooltip-transform);\n}\n.xcvxfwrf:not(#\\#) {\n margin-bottom: 6px;\n}\n.xm6rg4e9:not(#\\#) {\n font-size: 13px;\n}\n.xybc5i37:not(#\\#) {\n font-weight: bold;\n}\n.xnuimpaw:not(#\\#) {\n margin-right: 6px;\n}\n.x8eb0qpq:not(#\\#) {\n font-family: monospace;\n}\n.xierkllv {\n color: #67e8f9;\n}\n.xjmc5lje {\n padding: 2px 6px;\n}\n.x7zjcec6:not(#\\#) {\n font-size: 11px;\n}\n.xwosyyr8 {\n color: #c084fc;\n}\n.xzdqhyei {\n word-break: break-all;\n}\n.xk197wcs:not(#\\#) {\n background-color: #a855f71a;\n}\n.x4vahbk7 {\n border-radius: 4px;\n}\n.xf05shqu {\n max-height: 180px;\n}\n.xwflkjz8:not(#\\#) {\n padding-top: 8px;\n}\n.xslz5qa9:not(#\\#) {\n margin-top: 6px;\n}\n.xx3zn3a6 {\n overflow-y: auto;\n}\n.x6hkxz0i {\n border-top: 1px solid #ffffff14;\n}\n.xw9ej4ts:not(#\\#) {\n font-style: italic;\n}\n.x9jma4hx {\n color: #9ca3af;\n}\n.xs291xkc:not(#\\#) {\n margin-bottom: 8px;\n}\n.xap91uzz {\n display: block;\n}\n.xxkhp7ge:not(#\\#) {\n margin-bottom: 2px;\n}\n.x3f36k41 {\n color: #a5b4fc;\n}\n.xo0i6zyh:not(#\\#) {\n padding-left: 12px;\n}\n.xsfilhj8:not(#\\#) {\n line-height: 1.4;\n}\n.xmzl9t4s {\n color: #d1d5db;\n}\n.x4ygbjrq {\n color: #f472b6;\n}\n.xyfpgrbg {\n color: #93c5fd;\n}\n.xhue4zxv:not(#\\#) {\n padding-top: 6px;\n}\n.xxb8i1k0:not(#\\#) {\n margin-top: 8px;\n}\n.xdatukya:not(#\\#) {\n font-size: 10px;\n}\n.xvaeii17 {\n text-align: center;\n}\n";
17
+ document.head.appendChild(style);
18
+ isCssInjected = true;
19
+ };
20
+ var getClassName = (el) => {
21
+ if (!el) return "";
22
+ const className = el.className;
23
+ if (typeof className === "string") {
24
+ return className;
25
+ }
26
+ if (className && typeof className.baseVal === "string") {
27
+ return className.baseVal;
28
+ }
29
+ return "";
30
+ };
31
+ var getStylesForElement = (el) => {
32
+ const rulesFound = [];
33
+ for (const sheet of Array.from(document.styleSheets)) {
34
+ try {
35
+ const rules = Array.from(sheet.cssRules || sheet.rules || []);
36
+ for (const rule of rules) {
37
+ if (rule instanceof CSSStyleRule) {
38
+ if (el.matches(rule.selectorText)) {
39
+ rulesFound.push({
40
+ selector: rule.selectorText,
41
+ cssText: rule.style.cssText
42
+ });
43
+ }
44
+ }
45
+ }
46
+ } catch (e) {
47
+ }
48
+ }
49
+ return rulesFound;
50
+ };
51
+ var parseCssText = (cssText) => {
52
+ if (!cssText) return [];
53
+ return cssText.split(";").map((p) => p.trim()).filter(Boolean).map((prop) => {
54
+ const index = prop.indexOf(":");
55
+ if (index === -1) return null;
56
+ return {
57
+ name: prop.substring(0, index).trim(),
58
+ value: prop.substring(index + 1).trim()
59
+ };
60
+ }).filter((x) => x !== null);
61
+ };
62
+ var DevInspector = () => {
63
+ React.useEffect(() => {
64
+ injectInspectorCSS();
65
+ }, []);
66
+ const [isActive, setIsActive] = React.useState(true);
67
+ const [isFrozen, setIsFrozen] = React.useState(false);
68
+ const [hoveredElement, setHoveredElement] = React.useState(null);
69
+ const [overlayStyle, setOverlayStyle] = React.useState({
70
+ opacity: 0
71
+ });
72
+ const [tooltipStyle, setTooltipStyle] = React.useState({
73
+ opacity: 0
74
+ });
75
+ const [rules, setRules] = React.useState([]);
76
+ const tooltipRef = React.useRef(null);
77
+ React.useEffect(() => {
78
+ const handleKeyDown = (e) => {
79
+ if ((e.altKey || e.metaKey) && e.key.toLowerCase() === "i") {
80
+ e.preventDefault();
81
+ setIsActive((prev) => !prev);
82
+ }
83
+ };
84
+ window.addEventListener("keydown", handleKeyDown);
85
+ return () => window.removeEventListener("keydown", handleKeyDown);
86
+ }, []);
87
+ React.useEffect(() => {
88
+ const handleKeyDown = (e) => {
89
+ if (e.key === "Shift") {
90
+ setIsFrozen((prev) => !prev);
91
+ }
92
+ };
93
+ window.addEventListener("keydown", handleKeyDown);
94
+ return () => {
95
+ window.removeEventListener("keydown", handleKeyDown);
96
+ };
97
+ }, []);
98
+ React.useEffect(() => {
99
+ if (!isActive) {
100
+ setHoveredElement(null);
101
+ setOverlayStyle({ opacity: 0 });
102
+ setTooltipStyle({ opacity: 0 });
103
+ return;
104
+ }
105
+ const handleMouseOver = (e) => {
106
+ if (isFrozen) return;
107
+ const target = e.target;
108
+ if (!target) return;
109
+ if (target.closest("[data-plumeria-inspector-ignore]")) {
110
+ return;
111
+ }
112
+ if (target === document.documentElement || target === document.body) {
113
+ setHoveredElement(null);
114
+ return;
115
+ }
116
+ setHoveredElement(target);
117
+ };
118
+ document.addEventListener("mouseover", handleMouseOver);
119
+ return () => {
120
+ document.removeEventListener("mouseover", handleMouseOver);
121
+ };
122
+ }, [isActive, isFrozen]);
123
+ React.useEffect(() => {
124
+ if (!hoveredElement || !isActive) return;
125
+ const update = () => {
126
+ const rect = hoveredElement.getBoundingClientRect();
127
+ setOverlayStyle({
128
+ top: `${rect.top}px`,
129
+ left: `${rect.left}px`,
130
+ width: `${rect.width}px`,
131
+ height: `${rect.height}px`,
132
+ opacity: 1
133
+ });
134
+ const matchedRules = getStylesForElement(hoveredElement);
135
+ setRules(matchedRules);
136
+ const tooltipWidth = tooltipRef.current ? tooltipRef.current.offsetWidth : 320;
137
+ const tooltipHeight = tooltipRef.current ? tooltipRef.current.offsetHeight : 180;
138
+ const margin = 20;
139
+ const viewportWidth = window.innerWidth;
140
+ const viewportHeight = window.innerHeight;
141
+ let top = rect.top - tooltipHeight - margin;
142
+ let left = rect.right - tooltipWidth;
143
+ if (top < margin) {
144
+ top = rect.bottom + margin;
145
+ }
146
+ if (left < margin) {
147
+ left = margin;
148
+ }
149
+ if (left + tooltipWidth > viewportWidth - margin) {
150
+ left = viewportWidth - tooltipWidth - margin;
151
+ }
152
+ if (top + tooltipHeight > viewportHeight - margin) {
153
+ top = rect.top - tooltipHeight - margin;
154
+ }
155
+ setTooltipStyle({
156
+ top: `${top}px`,
157
+ left: `${left}px`,
158
+ opacity: 1,
159
+ transform: "none"
160
+ });
161
+ };
162
+ update();
163
+ window.addEventListener("scroll", update, { passive: true });
164
+ window.addEventListener("resize", update, { passive: true });
165
+ return () => {
166
+ window.removeEventListener("scroll", update);
167
+ window.removeEventListener("resize", update);
168
+ };
169
+ }, [hoveredElement, isActive, isFrozen]);
170
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
171
+ /* @__PURE__ */ jsxs(
172
+ "div",
173
+ {
174
+ "data-plumeria-inspector-ignore": "true",
175
+ className: "xm2svzo4 xwargx9a xnr54dgw xhtz6c5t xxp6epoh xi3fytem xj0ln8hv x636886i x7z3vutq xhrr6ses x6vb0uvf xd3oqkpm x6iuzqez x1gd64ir xb90zzvn x7vpbndt xvickkgg xfcpkn9a x1wwwd6e " + (isActive ? "xtby5ou6" : "") + " " + ({ "0": "xkqfgj67", "1": "xdp2dozu" }[isActive ? "1" : "0"] || "xkqfgj67"),
176
+ onClick: () => setIsActive((prev) => !prev),
177
+ children: [
178
+ /* @__PURE__ */ jsx(
179
+ "span",
180
+ {
181
+ className: "xq0askt6 xsemx3zn xyqzlrhp xs6s67ix " + (isActive ? "xlufsbde" : "") + " " + ({ "0": "xy2ymyq7", "1": "xbv1awaq" }[isActive ? "1" : "0"] || "xy2ymyq7")
182
+ }
183
+ ),
184
+ /* @__PURE__ */ jsxs("span", { children: [
185
+ "Inspector: ",
186
+ isActive ? "ON" : "OFF",
187
+ " ",
188
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.6 }, children: "(\u2318 + I)" })
189
+ ] })
190
+ ]
191
+ }
192
+ ),
193
+ isActive && hoveredElement && /* @__PURE__ */ jsx(
194
+ "div",
195
+ {
196
+ "data-plumeria-inspector-ignore": "true",
197
+ className: "xm2svzo4 xtzw270y xzjvpdw4 xwghexut xlcznmkr xnnxtoqx xka75a01 xzwctcax xgwg3gye xerytlo0",
198
+ style: { "--overlay-top": typeof (overlayStyle.top || "0px") === "number" ? (overlayStyle.top || "0px") + "px" : overlayStyle.top || "0px", "--overlay-left": typeof (overlayStyle.left || "0px") === "number" ? (overlayStyle.left || "0px") + "px" : overlayStyle.left || "0px", "--overlay-width": typeof (overlayStyle.width || "0px") === "number" ? (overlayStyle.width || "0px") + "px" : overlayStyle.width || "0px", "--overlay-height": typeof (overlayStyle.height || "0px") === "number" ? (overlayStyle.height || "0px") + "px" : overlayStyle.height || "0px" }
199
+ }
200
+ ),
201
+ isActive && hoveredElement && /* @__PURE__ */ jsxs(
202
+ "div",
203
+ {
204
+ ref: tooltipRef,
205
+ "data-plumeria-inspector-ignore": "true",
206
+ className: "xm2svzo4 x4o5bm7x x1jethze xfcpkn9a xp7py9n1 xhdqk8nx xr7ynoef xv21k0fp xjwv009s x7z3vutq xgm2v5ee xsc1chnd xx2zw91q xlucmkc1 xk0pf7xw x6apt1gh xy3932pz",
207
+ style: { "--tooltip-pointerEvents": typeof (isFrozen ? "auto" : "none") === "number" ? (isFrozen ? "auto" : "none") + "px" : isFrozen ? "auto" : "none", "--tooltip-top": typeof (tooltipStyle.top || "0px") === "number" ? (tooltipStyle.top || "0px") + "px" : tooltipStyle.top || "0px", "--tooltip-left": typeof (tooltipStyle.left || "0px") === "number" ? (tooltipStyle.left || "0px") + "px" : tooltipStyle.left || "0px", "--tooltip-opacity": tooltipStyle.opacity ?? 0, "--tooltip-transform": typeof (tooltipStyle.transform || "none") === "number" ? (tooltipStyle.transform || "none") + "px" : tooltipStyle.transform || "none" },
208
+ children: [
209
+ /* @__PURE__ */ jsxs("div", { className: "xxp6epoh xj0ln8hv xcvxfwrf xm6rg4e9 xybc5i37", children: [
210
+ /* @__PURE__ */ jsxs("span", { className: "xnuimpaw x8eb0qpq xierkllv", children: [
211
+ "<",
212
+ hoveredElement.tagName.toLowerCase(),
213
+ ">"
214
+ ] }),
215
+ getClassName(hoveredElement) && /* @__PURE__ */ jsxs("span", { className: "xjmc5lje x8eb0qpq x7zjcec6 xwosyyr8 xzdqhyei xk197wcs x4vahbk7", children: [
216
+ ".",
217
+ getClassName(hoveredElement).split(" ").filter(Boolean).join(" .")
218
+ ] })
219
+ ] }),
220
+ /* @__PURE__ */ jsx("div", { className: "xf05shqu xwflkjz8 xslz5qa9 xx3zn3a6 x6hkxz0i", children: rules.length === 0 ? /* @__PURE__ */ jsx("div", { className: "x7zjcec6 xw9ej4ts x9jma4hx", children: "No matching Plumeria CSS rules" }) : rules.map((rule, idx) => {
221
+ const parsedProps = parseCssText(rule.cssText);
222
+ return /* @__PURE__ */ jsxs("div", { className: "xs291xkc", children: [
223
+ /* @__PURE__ */ jsxs("span", { className: "xap91uzz xxkhp7ge x8eb0qpq x7zjcec6 x3f36k41", children: [
224
+ rule.selector,
225
+ " ",
226
+ "{"
227
+ ] }),
228
+ parsedProps.map((prop, pIdx) => /* @__PURE__ */ jsxs("span", { className: "xap91uzz xo0i6zyh x8eb0qpq x7zjcec6 xsfilhj8 xmzl9t4s", children: [
229
+ /* @__PURE__ */ jsx("span", { className: "x4ygbjrq", children: prop.name }),
230
+ ": ",
231
+ /* @__PURE__ */ jsx("span", { className: "xyfpgrbg", children: prop.value }),
232
+ ";"
233
+ ] }, pIdx)),
234
+ /* @__PURE__ */ jsx("span", { className: "xap91uzz xxkhp7ge x8eb0qpq x7zjcec6 x3f36k41", children: "}" })
235
+ ] }, idx);
236
+ }) }),
237
+ /* @__PURE__ */ jsx("div", { className: "xhue4zxv xxb8i1k0 xdatukya xw9ej4ts x9jma4hx xvaeii17 x6hkxz0i", children: isFrozen ? "\u2744\uFE0F Targeting Locked (Press Shift to unlock)" : "\u26A1 Press Shift to lock targeting & scroll rules" })
238
+ ]
239
+ }
240
+ )
241
+ ] });
242
+ };
243
+ var Inspector = ({ production = false }) => {
244
+ if (!production && process.env.NODE_ENV === "production") {
245
+ return null;
246
+ }
247
+ return /* @__PURE__ */ jsx(DevInspector, {});
248
+ };
249
+ export {
250
+ Inspector
251
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@plumeria/inspector",
3
+ "version": "16.1.0",
4
+ "description": "Styled components inspector for development",
5
+ "author": "Refirst 11",
6
+ "license": "MIT",
7
+ "funding": "https://github.com/sponsors/refirst11",
8
+ "homepage": "https://plumeria.dev",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/zss-in-js/plumeria.git",
12
+ "directory": "packages/inspector"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/zss-in-js/plumeria/issues"
16
+ },
17
+ "keywords": [
18
+ "react",
19
+ "inspector",
20
+ "devtools",
21
+ "plumeria",
22
+ "css-in-js"
23
+ ],
24
+ "sideEffects": false,
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ }
30
+ },
31
+ "type": "module",
32
+ "main": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "files": [
35
+ "dist/"
36
+ ],
37
+ "scripts": {
38
+ "build": "rimraf dist && node scripts/build.js && tsc --emitDeclarationOnly --declarationDir dist"
39
+ },
40
+ "dependencies": {
41
+ "@plumeria/headlessui": "workspace:^"
42
+ },
43
+ "devDependencies": {
44
+ "@plumeria/core": "workspace:^",
45
+ "@plumeria/unplugin": "workspace:^",
46
+ "@types/react": "^19.2.17",
47
+ "@types/react-dom": "^19.2.3",
48
+ "esbuild": "^0.28.0"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }