@nuraly/lumenjs 0.1.3 → 0.2.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/README.md +62 -282
- package/dist/auth/config.d.ts +23 -0
- package/dist/auth/config.js +115 -0
- package/dist/auth/guard.d.ts +12 -0
- package/dist/auth/guard.js +28 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/middleware.d.ts +23 -0
- package/dist/auth/middleware.js +89 -0
- package/dist/auth/native-auth.d.ts +82 -0
- package/dist/auth/native-auth.js +340 -0
- package/dist/auth/oidc-client.d.ts +17 -0
- package/dist/auth/oidc-client.js +123 -0
- package/dist/auth/providers/google.d.ts +23 -0
- package/dist/auth/providers/google.js +25 -0
- package/dist/auth/providers/index.d.ts +2 -0
- package/dist/auth/providers/index.js +1 -0
- package/dist/auth/routes/login.d.ts +8 -0
- package/dist/auth/routes/login.js +121 -0
- package/dist/auth/routes/logout.d.ts +4 -0
- package/dist/auth/routes/logout.js +79 -0
- package/dist/auth/routes/oidc-callback.d.ts +3 -0
- package/dist/auth/routes/oidc-callback.js +70 -0
- package/dist/auth/routes/password.d.ts +5 -0
- package/dist/auth/routes/password.js +149 -0
- package/dist/auth/routes/signup.d.ts +3 -0
- package/dist/auth/routes/signup.js +81 -0
- package/dist/auth/routes/token.d.ts +4 -0
- package/dist/auth/routes/token.js +70 -0
- package/dist/auth/routes/totp.d.ts +22 -0
- package/dist/auth/routes/totp.js +232 -0
- package/dist/auth/routes/utils.d.ts +7 -0
- package/dist/auth/routes/utils.js +35 -0
- package/dist/auth/routes/verify.d.ts +3 -0
- package/dist/auth/routes/verify.js +26 -0
- package/dist/auth/routes.d.ts +8 -0
- package/dist/auth/routes.js +124 -0
- package/dist/auth/session.d.ts +8 -0
- package/dist/auth/session.js +54 -0
- package/dist/auth/token.d.ts +33 -0
- package/dist/auth/token.js +90 -0
- package/dist/auth/types.d.ts +156 -0
- package/dist/auth/types.js +2 -0
- package/dist/build/build-client.d.ts +15 -0
- package/dist/build/build-client.js +45 -0
- package/dist/build/build-prerender.d.ts +11 -0
- package/dist/build/build-prerender.js +159 -0
- package/dist/build/build-server.d.ts +18 -0
- package/dist/build/build-server.js +107 -0
- package/dist/build/build.js +60 -123
- package/dist/build/scan.d.ts +18 -0
- package/dist/build/scan.js +77 -6
- package/dist/build/serve-api.js +8 -2
- package/dist/build/serve-loaders.d.ts +4 -4
- package/dist/build/serve-loaders.js +26 -18
- package/dist/build/serve-ssr.js +38 -11
- package/dist/build/serve-static.js +3 -3
- package/dist/build/serve.js +341 -18
- package/dist/cli.js +37 -6
- package/dist/communication/encryption.d.ts +35 -0
- package/dist/communication/encryption.js +90 -0
- package/dist/communication/handlers/context.d.ts +27 -0
- package/dist/communication/handlers/context.js +1 -0
- package/dist/communication/handlers/conversation.d.ts +24 -0
- package/dist/communication/handlers/conversation.js +113 -0
- package/dist/communication/handlers/file-upload.d.ts +17 -0
- package/dist/communication/handlers/file-upload.js +62 -0
- package/dist/communication/handlers/messaging.d.ts +30 -0
- package/dist/communication/handlers/messaging.js +237 -0
- package/dist/communication/handlers/presence.d.ts +15 -0
- package/dist/communication/handlers/presence.js +76 -0
- package/dist/communication/handlers.d.ts +5 -0
- package/dist/communication/handlers.js +5 -0
- package/dist/communication/index.d.ts +9 -0
- package/dist/communication/index.js +7 -0
- package/dist/communication/link-preview.d.ts +18 -0
- package/dist/communication/link-preview.js +115 -0
- package/dist/communication/schema.d.ts +10 -0
- package/dist/communication/schema.js +101 -0
- package/dist/communication/server.d.ts +86 -0
- package/dist/communication/server.js +212 -0
- package/dist/communication/signaling.d.ts +43 -0
- package/dist/communication/signaling.js +271 -0
- package/dist/communication/store.d.ts +71 -0
- package/dist/communication/store.js +289 -0
- package/dist/communication/types.d.ts +454 -0
- package/dist/communication/types.js +1 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +55 -0
- package/dist/db/auto-migrate.d.ts +3 -0
- package/dist/db/auto-migrate.js +100 -0
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.js +18 -0
- package/dist/db/index.d.ts +17 -13
- package/dist/db/index.js +205 -26
- package/dist/db/seed.d.ts +12 -0
- package/dist/db/seed.js +88 -0
- package/dist/db/table.d.ts +10 -0
- package/dist/db/table.js +12 -0
- package/dist/dev-server/config.d.ts +11 -0
- package/dist/dev-server/config.js +40 -20
- package/dist/dev-server/index-html.d.ts +4 -0
- package/dist/dev-server/index-html.js +21 -6
- package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
- package/dist/dev-server/nuralyui-aliases.js +115 -94
- package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
- package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
- package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
- package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
- package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
- package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
- package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
- package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
- package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
- package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
- package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
- package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
- package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
- package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
- package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
- package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
- package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
- package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
- package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
- package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
- package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
- package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
- package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
- package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
- package/dist/dev-server/server.js +242 -70
- package/dist/dev-server/ssr-render.d.ts +2 -1
- package/dist/dev-server/ssr-render.js +117 -50
- package/dist/editor/ai/backend.d.ts +20 -0
- package/dist/editor/ai/backend.js +113 -0
- package/dist/editor/ai/claude-code-client.d.ts +20 -0
- package/dist/editor/ai/claude-code-client.js +145 -0
- package/dist/editor/ai/deepseek-client.d.ts +7 -0
- package/dist/editor/ai/deepseek-client.js +113 -0
- package/dist/editor/ai/opencode-client.d.ts +14 -0
- package/dist/editor/ai/opencode-client.js +99 -0
- package/dist/editor/ai/snapshot-store.d.ts +22 -0
- package/dist/editor/ai/snapshot-store.js +35 -0
- package/dist/editor/ai/types.d.ts +30 -0
- package/dist/editor/ai/types.js +136 -0
- package/dist/editor/ai-chat-panel.d.ts +13 -0
- package/dist/editor/ai-chat-panel.js +613 -0
- package/dist/editor/ai-markdown.d.ts +10 -0
- package/dist/editor/ai-markdown.js +70 -0
- package/dist/editor/ai-project-panel.d.ts +11 -0
- package/dist/editor/ai-project-panel.js +332 -0
- package/dist/editor/ast-modification.d.ts +11 -0
- package/dist/editor/ast-modification.js +1 -0
- package/dist/editor/ast-service.d.ts +30 -0
- package/dist/editor/ast-service.js +180 -0
- package/dist/editor/css-rules.d.ts +54 -0
- package/dist/editor/css-rules.js +423 -0
- package/dist/editor/editor-api-client.d.ts +51 -0
- package/dist/editor/editor-api-client.js +162 -0
- package/dist/editor/editor-bridge.d.ts +1 -0
- package/dist/editor/editor-bridge.js +18 -8
- package/dist/editor/editor-toolbar.d.ts +14 -0
- package/dist/editor/editor-toolbar.js +115 -0
- package/dist/editor/file-editor.d.ts +9 -0
- package/dist/editor/file-editor.js +236 -0
- package/dist/editor/file-service.d.ts +16 -0
- package/dist/editor/file-service.js +52 -0
- package/dist/editor/i18n-key-gen.d.ts +1 -0
- package/dist/editor/i18n-key-gen.js +7 -0
- package/dist/editor/inline-text-edit.d.ts +5 -0
- package/dist/editor/inline-text-edit.js +173 -92
- package/dist/editor/overlay-events.d.ts +5 -0
- package/dist/editor/overlay-events.js +364 -0
- package/dist/editor/overlay-hmr.d.ts +2 -0
- package/dist/editor/overlay-hmr.js +76 -0
- package/dist/editor/overlay-selection.d.ts +29 -0
- package/dist/editor/overlay-selection.js +148 -0
- package/dist/editor/overlay-utils.d.ts +12 -0
- package/dist/editor/overlay-utils.js +59 -0
- package/dist/editor/properties-panel-persist.d.ts +14 -0
- package/dist/editor/properties-panel-persist.js +70 -0
- package/dist/editor/properties-panel-rows.d.ts +10 -0
- package/dist/editor/properties-panel-rows.js +349 -0
- package/dist/editor/properties-panel-styles.d.ts +4 -0
- package/dist/editor/properties-panel-styles.js +174 -0
- package/dist/editor/properties-panel.d.ts +4 -0
- package/dist/editor/properties-panel.js +148 -0
- package/dist/editor/property-registry.d.ts +16 -0
- package/dist/editor/property-registry.js +303 -0
- package/dist/editor/standalone-file-panel.d.ts +0 -0
- package/dist/editor/standalone-file-panel.js +1 -0
- package/dist/editor/standalone-overlay-dom.d.ts +0 -0
- package/dist/editor/standalone-overlay-dom.js +1 -0
- package/dist/editor/standalone-overlay-styles.d.ts +0 -0
- package/dist/editor/standalone-overlay-styles.js +1 -0
- package/dist/editor/standalone-overlay.d.ts +1 -0
- package/dist/editor/standalone-overlay.js +76 -0
- package/dist/editor/syntax-highlighter.d.ts +4 -0
- package/dist/editor/syntax-highlighter.js +81 -0
- package/dist/editor/text-toolbar.d.ts +11 -0
- package/dist/editor/text-toolbar.js +327 -0
- package/dist/editor/toolbar-styles.d.ts +4 -0
- package/dist/editor/toolbar-styles.js +198 -0
- package/dist/email/index.d.ts +32 -0
- package/dist/email/index.js +154 -0
- package/dist/email/providers/resend.d.ts +2 -0
- package/dist/email/providers/resend.js +24 -0
- package/dist/email/providers/sendgrid.d.ts +2 -0
- package/dist/email/providers/sendgrid.js +31 -0
- package/dist/email/providers/smtp.d.ts +13 -0
- package/dist/email/providers/smtp.js +125 -0
- package/dist/email/template-engine.d.ts +18 -0
- package/dist/email/template-engine.js +116 -0
- package/dist/email/templates/base.d.ts +9 -0
- package/dist/email/templates/base.js +65 -0
- package/dist/email/templates/password-reset.d.ts +5 -0
- package/dist/email/templates/password-reset.js +15 -0
- package/dist/email/templates/verify-email.d.ts +5 -0
- package/dist/email/templates/verify-email.js +15 -0
- package/dist/email/templates/welcome.d.ts +5 -0
- package/dist/email/templates/welcome.js +13 -0
- package/dist/email/types.d.ts +49 -0
- package/dist/email/types.js +1 -0
- package/dist/llms/generate.d.ts +46 -0
- package/dist/llms/generate.js +185 -0
- package/dist/permissions/guard.d.ts +28 -0
- package/dist/permissions/guard.js +30 -0
- package/dist/permissions/index.d.ts +6 -0
- package/dist/permissions/index.js +3 -0
- package/dist/permissions/service.d.ts +80 -0
- package/dist/permissions/service.js +210 -0
- package/dist/permissions/tables.d.ts +5 -0
- package/dist/permissions/tables.js +68 -0
- package/dist/permissions/types.d.ts +33 -0
- package/dist/permissions/types.js +1 -0
- package/dist/runtime/app-shell.d.ts +1 -1
- package/dist/runtime/app-shell.js +164 -0
- package/dist/runtime/auth.d.ts +10 -0
- package/dist/runtime/auth.js +30 -0
- package/dist/runtime/communication.d.ts +137 -0
- package/dist/runtime/communication.js +228 -0
- package/dist/runtime/error-boundary.d.ts +23 -0
- package/dist/runtime/error-boundary.js +120 -0
- package/dist/runtime/i18n.d.ts +6 -1
- package/dist/runtime/i18n.js +42 -21
- package/dist/runtime/island.d.ts +16 -0
- package/dist/runtime/island.js +80 -0
- package/dist/runtime/router-data.d.ts +3 -0
- package/dist/runtime/router-data.js +102 -17
- package/dist/runtime/router-hydration.js +34 -2
- package/dist/runtime/router.d.ts +19 -2
- package/dist/runtime/router.js +237 -43
- package/dist/runtime/socket-client.d.ts +2 -0
- package/dist/runtime/socket-client.js +30 -0
- package/dist/runtime/webrtc.d.ts +91 -0
- package/dist/runtime/webrtc.js +428 -0
- package/dist/shared/dom-shims.js +4 -2
- package/dist/shared/graceful-shutdown.d.ts +8 -0
- package/dist/shared/graceful-shutdown.js +36 -0
- package/dist/shared/health.d.ts +8 -0
- package/dist/shared/health.js +25 -0
- package/dist/shared/llms-txt.d.ts +31 -0
- package/dist/shared/llms-txt.js +85 -0
- package/dist/shared/logger.d.ts +32 -0
- package/dist/shared/logger.js +93 -0
- package/dist/shared/meta.d.ts +27 -0
- package/dist/shared/meta.js +71 -0
- package/dist/shared/middleware-runner.d.ts +9 -0
- package/dist/shared/middleware-runner.js +29 -0
- package/dist/shared/rate-limit.d.ts +18 -0
- package/dist/shared/rate-limit.js +71 -0
- package/dist/shared/request-id.d.ts +5 -0
- package/dist/shared/request-id.js +18 -0
- package/dist/shared/route-matching.js +16 -1
- package/dist/shared/security-headers.d.ts +18 -0
- package/dist/shared/security-headers.js +38 -0
- package/dist/shared/socket-io-setup.d.ts +11 -0
- package/dist/shared/socket-io-setup.js +51 -0
- package/dist/shared/types.d.ts +15 -0
- package/dist/shared/utils.d.ts +33 -7
- package/dist/shared/utils.js +164 -27
- package/dist/storage/adapters/local.d.ts +44 -0
- package/dist/storage/adapters/local.js +85 -0
- package/dist/storage/adapters/s3.d.ts +32 -0
- package/dist/storage/adapters/s3.js +119 -0
- package/dist/storage/adapters/types.d.ts +53 -0
- package/dist/storage/adapters/types.js +1 -0
- package/dist/storage/index.d.ts +76 -0
- package/dist/storage/index.js +83 -0
- package/package.json +45 -7
- package/templates/blog/api/posts.ts +4 -18
- package/templates/blog/data/migrations/001_init.sql +6 -5
- package/templates/blog/lumenjs.config.ts +3 -0
- package/templates/blog/package.json +14 -0
- package/templates/blog/pages/_layout.ts +25 -0
- package/templates/blog/pages/index.ts +48 -22
- package/templates/blog/pages/posts/[slug].ts +45 -20
- package/templates/blog/pages/tag/[tag].ts +44 -0
- package/templates/dashboard/api/stats.ts +8 -5
- package/templates/dashboard/lumenjs.config.ts +3 -0
- package/templates/dashboard/package.json +14 -0
- package/templates/dashboard/pages/_layout.ts +25 -0
- package/templates/dashboard/pages/index.ts +54 -23
- package/templates/dashboard/pages/settings/index.ts +29 -0
- package/templates/default/lumenjs.config.ts +3 -0
- package/templates/default/package.json +14 -0
- package/templates/default/pages/index.ts +24 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { CSS_ENUMS, COMMON_CSS_PROPS, isColorValue, normalizeToHex, notifyLayoutChange, } from './css-rules.js';
|
|
2
|
+
import { persistAttribute, persistAttributeDebounced, persistStyle, persistStyleDebounced, } from './properties-panel-persist.js';
|
|
3
|
+
export function createGroup(label) {
|
|
4
|
+
const group = document.createElement('div');
|
|
5
|
+
group.className = 'nk-pp-group';
|
|
6
|
+
group.innerHTML = `<div class="nk-pp-group-header">${label}</div>`;
|
|
7
|
+
return group;
|
|
8
|
+
}
|
|
9
|
+
export function parseInlineStyles(element) {
|
|
10
|
+
const result = [];
|
|
11
|
+
const style = element.style;
|
|
12
|
+
for (let i = 0; i < style.length; i++) {
|
|
13
|
+
const prop = style[i];
|
|
14
|
+
if (prop === 'outline' || prop === 'outline-offset')
|
|
15
|
+
continue;
|
|
16
|
+
const val = style.getPropertyValue(prop);
|
|
17
|
+
if (val)
|
|
18
|
+
result.push([prop, val]);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
export function createPropertyRow(prop, element) {
|
|
23
|
+
const row = document.createElement('div');
|
|
24
|
+
row.className = 'nk-pp-row';
|
|
25
|
+
const label = document.createElement('div');
|
|
26
|
+
label.className = 'nk-pp-label';
|
|
27
|
+
label.textContent = prop.name;
|
|
28
|
+
label.title = prop.name;
|
|
29
|
+
row.appendChild(label);
|
|
30
|
+
const control = document.createElement('div');
|
|
31
|
+
control.className = 'nk-pp-control';
|
|
32
|
+
if (prop.type === 'Boolean') {
|
|
33
|
+
const toggle = document.createElement('button');
|
|
34
|
+
toggle.className = 'nk-pp-toggle' + (prop.value ? ' on' : '');
|
|
35
|
+
toggle.addEventListener('click', () => {
|
|
36
|
+
const newVal = !toggle.classList.contains('on');
|
|
37
|
+
toggle.classList.toggle('on', newVal);
|
|
38
|
+
element[prop.name] = newVal;
|
|
39
|
+
if (newVal) {
|
|
40
|
+
element.setAttribute(prop.attrName, '');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
element.removeAttribute(prop.attrName);
|
|
44
|
+
}
|
|
45
|
+
persistAttribute(element, prop.attrName, newVal ? '' : undefined);
|
|
46
|
+
});
|
|
47
|
+
control.appendChild(toggle);
|
|
48
|
+
}
|
|
49
|
+
else if (prop.type === 'Array' || prop.type === 'Object') {
|
|
50
|
+
const ro = document.createElement('div');
|
|
51
|
+
ro.className = 'nk-pp-readonly';
|
|
52
|
+
try {
|
|
53
|
+
ro.textContent = JSON.stringify(prop.value);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
ro.textContent = String(prop.value);
|
|
57
|
+
}
|
|
58
|
+
control.appendChild(ro);
|
|
59
|
+
}
|
|
60
|
+
else if (prop.enumValues && prop.enumValues.length > 0) {
|
|
61
|
+
const select = document.createElement('select');
|
|
62
|
+
const emptyOpt = document.createElement('option');
|
|
63
|
+
emptyOpt.value = '';
|
|
64
|
+
emptyOpt.textContent = '—';
|
|
65
|
+
select.appendChild(emptyOpt);
|
|
66
|
+
for (const val of prop.enumValues) {
|
|
67
|
+
const opt = document.createElement('option');
|
|
68
|
+
opt.value = val;
|
|
69
|
+
opt.textContent = val;
|
|
70
|
+
select.appendChild(opt);
|
|
71
|
+
}
|
|
72
|
+
select.value = prop.value != null ? String(prop.value) : '';
|
|
73
|
+
select.addEventListener('change', () => {
|
|
74
|
+
const v = select.value;
|
|
75
|
+
element[prop.name] = v;
|
|
76
|
+
if (v) {
|
|
77
|
+
element.setAttribute(prop.attrName, v);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
element.removeAttribute(prop.attrName);
|
|
81
|
+
}
|
|
82
|
+
persistAttribute(element, prop.attrName, v || undefined);
|
|
83
|
+
});
|
|
84
|
+
control.appendChild(select);
|
|
85
|
+
}
|
|
86
|
+
else if (isColorValue(prop.name, String(prop.value ?? ''))) {
|
|
87
|
+
const wrap = document.createElement('div');
|
|
88
|
+
wrap.className = 'nk-pp-color-wrap';
|
|
89
|
+
const colorInput = document.createElement('input');
|
|
90
|
+
colorInput.type = 'color';
|
|
91
|
+
colorInput.value = normalizeToHex(String(prop.value ?? '#000000'));
|
|
92
|
+
const textInput = document.createElement('input');
|
|
93
|
+
textInput.type = 'text';
|
|
94
|
+
textInput.value = String(prop.value ?? '');
|
|
95
|
+
const sync = (val) => {
|
|
96
|
+
element[prop.name] = val;
|
|
97
|
+
element.setAttribute(prop.attrName, val);
|
|
98
|
+
persistAttributeDebounced(element, prop.attrName, val, prop.name);
|
|
99
|
+
};
|
|
100
|
+
colorInput.addEventListener('input', () => {
|
|
101
|
+
textInput.value = colorInput.value;
|
|
102
|
+
sync(colorInput.value);
|
|
103
|
+
});
|
|
104
|
+
textInput.addEventListener('input', () => {
|
|
105
|
+
sync(textInput.value);
|
|
106
|
+
});
|
|
107
|
+
wrap.appendChild(colorInput);
|
|
108
|
+
wrap.appendChild(textInput);
|
|
109
|
+
control.appendChild(wrap);
|
|
110
|
+
}
|
|
111
|
+
else if (prop.type === 'Number') {
|
|
112
|
+
const input = document.createElement('input');
|
|
113
|
+
input.type = 'number';
|
|
114
|
+
input.value = prop.value != null ? String(prop.value) : '';
|
|
115
|
+
input.addEventListener('input', () => {
|
|
116
|
+
const v = input.value;
|
|
117
|
+
element[prop.name] = v ? Number(v) : undefined;
|
|
118
|
+
if (v) {
|
|
119
|
+
element.setAttribute(prop.attrName, v);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
element.removeAttribute(prop.attrName);
|
|
123
|
+
}
|
|
124
|
+
persistAttributeDebounced(element, prop.attrName, v || undefined, prop.name);
|
|
125
|
+
});
|
|
126
|
+
control.appendChild(input);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const input = document.createElement('input');
|
|
130
|
+
input.type = 'text';
|
|
131
|
+
input.value = prop.value != null ? String(prop.value) : '';
|
|
132
|
+
input.addEventListener('input', () => {
|
|
133
|
+
const v = input.value;
|
|
134
|
+
element[prop.name] = v;
|
|
135
|
+
if (v) {
|
|
136
|
+
element.setAttribute(prop.attrName, v);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
element.removeAttribute(prop.attrName);
|
|
140
|
+
}
|
|
141
|
+
persistAttributeDebounced(element, prop.attrName, v || undefined, prop.name);
|
|
142
|
+
});
|
|
143
|
+
control.appendChild(input);
|
|
144
|
+
}
|
|
145
|
+
row.appendChild(control);
|
|
146
|
+
return row;
|
|
147
|
+
}
|
|
148
|
+
export function createStyleRow(cssProp, cssVal, element, group) {
|
|
149
|
+
const row = document.createElement('div');
|
|
150
|
+
row.className = 'nk-pp-row';
|
|
151
|
+
const label = document.createElement('div');
|
|
152
|
+
label.className = 'nk-pp-label';
|
|
153
|
+
label.textContent = cssProp;
|
|
154
|
+
label.title = cssProp;
|
|
155
|
+
row.appendChild(label);
|
|
156
|
+
const control = document.createElement('div');
|
|
157
|
+
control.className = 'nk-pp-control';
|
|
158
|
+
const enumVals = CSS_ENUMS[cssProp];
|
|
159
|
+
if (isColorValue(cssProp, cssVal)) {
|
|
160
|
+
const wrap = document.createElement('div');
|
|
161
|
+
wrap.className = 'nk-pp-color-wrap';
|
|
162
|
+
const colorInput = document.createElement('input');
|
|
163
|
+
colorInput.type = 'color';
|
|
164
|
+
colorInput.value = normalizeToHex(cssVal);
|
|
165
|
+
const textInput = document.createElement('input');
|
|
166
|
+
textInput.type = 'text';
|
|
167
|
+
textInput.value = cssVal;
|
|
168
|
+
const sync = (val) => {
|
|
169
|
+
element.style.setProperty(cssProp, val);
|
|
170
|
+
notifyLayoutChange();
|
|
171
|
+
persistStyleDebounced(element);
|
|
172
|
+
};
|
|
173
|
+
colorInput.addEventListener('input', () => {
|
|
174
|
+
textInput.value = colorInput.value;
|
|
175
|
+
sync(colorInput.value);
|
|
176
|
+
});
|
|
177
|
+
textInput.addEventListener('input', () => {
|
|
178
|
+
sync(textInput.value);
|
|
179
|
+
});
|
|
180
|
+
wrap.appendChild(colorInput);
|
|
181
|
+
wrap.appendChild(textInput);
|
|
182
|
+
control.appendChild(wrap);
|
|
183
|
+
}
|
|
184
|
+
else if (enumVals) {
|
|
185
|
+
const select = document.createElement('select');
|
|
186
|
+
const emptyOpt = document.createElement('option');
|
|
187
|
+
emptyOpt.value = '';
|
|
188
|
+
emptyOpt.textContent = '—';
|
|
189
|
+
select.appendChild(emptyOpt);
|
|
190
|
+
for (const v of enumVals) {
|
|
191
|
+
const opt = document.createElement('option');
|
|
192
|
+
opt.value = v;
|
|
193
|
+
opt.textContent = v;
|
|
194
|
+
select.appendChild(opt);
|
|
195
|
+
}
|
|
196
|
+
select.value = cssVal;
|
|
197
|
+
select.addEventListener('change', () => {
|
|
198
|
+
element.style.setProperty(cssProp, select.value);
|
|
199
|
+
notifyLayoutChange();
|
|
200
|
+
persistStyleDebounced(element);
|
|
201
|
+
});
|
|
202
|
+
control.appendChild(select);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const input = document.createElement('input');
|
|
206
|
+
input.type = 'text';
|
|
207
|
+
input.value = cssVal;
|
|
208
|
+
input.addEventListener('input', () => {
|
|
209
|
+
element.style.setProperty(cssProp, input.value);
|
|
210
|
+
notifyLayoutChange();
|
|
211
|
+
persistStyleDebounced(element);
|
|
212
|
+
});
|
|
213
|
+
control.appendChild(input);
|
|
214
|
+
}
|
|
215
|
+
row.appendChild(control);
|
|
216
|
+
// Remove button
|
|
217
|
+
const removeBtn = document.createElement('button');
|
|
218
|
+
removeBtn.className = 'nk-pp-remove';
|
|
219
|
+
removeBtn.innerHTML = '×';
|
|
220
|
+
removeBtn.title = 'Remove style';
|
|
221
|
+
removeBtn.addEventListener('click', () => {
|
|
222
|
+
element.style.removeProperty(cssProp);
|
|
223
|
+
row.remove();
|
|
224
|
+
notifyLayoutChange();
|
|
225
|
+
persistStyle(element);
|
|
226
|
+
});
|
|
227
|
+
row.appendChild(removeBtn);
|
|
228
|
+
return row;
|
|
229
|
+
}
|
|
230
|
+
export function createAddAttributeRow(element, group) {
|
|
231
|
+
const wrapper = document.createElement('div');
|
|
232
|
+
wrapper.className = 'nk-pp-add-row';
|
|
233
|
+
const addBtn = document.createElement('button');
|
|
234
|
+
addBtn.className = 'nk-pp-add-btn';
|
|
235
|
+
addBtn.textContent = '+ Add Attribute';
|
|
236
|
+
wrapper.appendChild(addBtn);
|
|
237
|
+
addBtn.addEventListener('click', () => {
|
|
238
|
+
addBtn.style.display = 'none';
|
|
239
|
+
const form = document.createElement('div');
|
|
240
|
+
form.className = 'nk-pp-add-form';
|
|
241
|
+
const nameInput = document.createElement('input');
|
|
242
|
+
nameInput.placeholder = 'name';
|
|
243
|
+
const valInput = document.createElement('input');
|
|
244
|
+
valInput.placeholder = 'value';
|
|
245
|
+
const confirmBtn = document.createElement('button');
|
|
246
|
+
confirmBtn.textContent = '\u2713';
|
|
247
|
+
form.appendChild(nameInput);
|
|
248
|
+
form.appendChild(valInput);
|
|
249
|
+
form.appendChild(confirmBtn);
|
|
250
|
+
wrapper.appendChild(form);
|
|
251
|
+
nameInput.focus();
|
|
252
|
+
const commit = () => {
|
|
253
|
+
const name = nameInput.value.trim();
|
|
254
|
+
const val = valInput.value;
|
|
255
|
+
if (!name) {
|
|
256
|
+
addBtn.style.display = '';
|
|
257
|
+
form.remove();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (element.hasAttribute(name)) {
|
|
261
|
+
nameInput.style.borderColor = '#f87171';
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
element.setAttribute(name, val);
|
|
265
|
+
persistAttribute(element, name, val);
|
|
266
|
+
const prop = { name, attrName: name, type: 'String', value: val };
|
|
267
|
+
group.insertBefore(createPropertyRow(prop, element), wrapper);
|
|
268
|
+
addBtn.style.display = '';
|
|
269
|
+
form.remove();
|
|
270
|
+
};
|
|
271
|
+
confirmBtn.addEventListener('click', commit);
|
|
272
|
+
nameInput.addEventListener('keydown', (e) => { if (e.key === 'Enter')
|
|
273
|
+
commit(); if (e.key === 'Escape') {
|
|
274
|
+
addBtn.style.display = '';
|
|
275
|
+
form.remove();
|
|
276
|
+
} });
|
|
277
|
+
valInput.addEventListener('keydown', (e) => { if (e.key === 'Enter')
|
|
278
|
+
commit(); if (e.key === 'Escape') {
|
|
279
|
+
addBtn.style.display = '';
|
|
280
|
+
form.remove();
|
|
281
|
+
} });
|
|
282
|
+
});
|
|
283
|
+
return wrapper;
|
|
284
|
+
}
|
|
285
|
+
export function createAddStyleRow(element, group) {
|
|
286
|
+
const wrapper = document.createElement('div');
|
|
287
|
+
wrapper.className = 'nk-pp-add-row';
|
|
288
|
+
const addBtn = document.createElement('button');
|
|
289
|
+
addBtn.className = 'nk-pp-add-btn';
|
|
290
|
+
addBtn.textContent = '+ Add Style';
|
|
291
|
+
wrapper.appendChild(addBtn);
|
|
292
|
+
addBtn.addEventListener('click', () => {
|
|
293
|
+
addBtn.style.display = 'none';
|
|
294
|
+
const form = document.createElement('div');
|
|
295
|
+
form.className = 'nk-pp-add-form';
|
|
296
|
+
const nameInput = document.createElement('input');
|
|
297
|
+
nameInput.placeholder = 'property';
|
|
298
|
+
nameInput.setAttribute('list', 'nk-pp-css-list');
|
|
299
|
+
const valInput = document.createElement('input');
|
|
300
|
+
valInput.placeholder = 'value';
|
|
301
|
+
const confirmBtn = document.createElement('button');
|
|
302
|
+
confirmBtn.textContent = '\u2713';
|
|
303
|
+
form.appendChild(nameInput);
|
|
304
|
+
form.appendChild(valInput);
|
|
305
|
+
form.appendChild(confirmBtn);
|
|
306
|
+
wrapper.appendChild(form);
|
|
307
|
+
// Datalist for autocomplete
|
|
308
|
+
if (!document.getElementById('nk-pp-css-list')) {
|
|
309
|
+
const dl = document.createElement('datalist');
|
|
310
|
+
dl.id = 'nk-pp-css-list';
|
|
311
|
+
for (const p of COMMON_CSS_PROPS) {
|
|
312
|
+
const opt = document.createElement('option');
|
|
313
|
+
opt.value = p;
|
|
314
|
+
dl.appendChild(opt);
|
|
315
|
+
}
|
|
316
|
+
document.body.appendChild(dl);
|
|
317
|
+
}
|
|
318
|
+
nameInput.focus();
|
|
319
|
+
const commit = () => {
|
|
320
|
+
const prop = nameInput.value.trim();
|
|
321
|
+
const val = valInput.value.trim();
|
|
322
|
+
if (!prop || !val) {
|
|
323
|
+
addBtn.style.display = '';
|
|
324
|
+
form.remove();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
element.style.setProperty(prop, val);
|
|
328
|
+
notifyLayoutChange();
|
|
329
|
+
persistStyle(element);
|
|
330
|
+
group.insertBefore(createStyleRow(prop, val, element, group), wrapper);
|
|
331
|
+
addBtn.style.display = '';
|
|
332
|
+
form.remove();
|
|
333
|
+
};
|
|
334
|
+
confirmBtn.addEventListener('click', commit);
|
|
335
|
+
nameInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') {
|
|
336
|
+
valInput.focus();
|
|
337
|
+
e.preventDefault();
|
|
338
|
+
} if (e.key === 'Escape') {
|
|
339
|
+
addBtn.style.display = '';
|
|
340
|
+
form.remove();
|
|
341
|
+
} });
|
|
342
|
+
valInput.addEventListener('keydown', (e) => { if (e.key === 'Enter')
|
|
343
|
+
commit(); if (e.key === 'Escape') {
|
|
344
|
+
addBtn.style.display = '';
|
|
345
|
+
form.remove();
|
|
346
|
+
} });
|
|
347
|
+
});
|
|
348
|
+
return wrapper;
|
|
349
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Properties Panel — CSS styles, extracted from properties-panel.ts.
|
|
3
|
+
*/
|
|
4
|
+
export function injectPropertiesPanelStyles() {
|
|
5
|
+
const style = document.createElement('style');
|
|
6
|
+
style.textContent = `
|
|
7
|
+
#nk-props-panel {
|
|
8
|
+
position: fixed; top: 44px; right: 0; width: 300px;
|
|
9
|
+
height: calc(100vh - 44px); background: #1e1b2e;
|
|
10
|
+
border-left: 1px solid #334155;
|
|
11
|
+
z-index: 99999; display: none; flex-direction: column;
|
|
12
|
+
font-family: system-ui, -apple-system, sans-serif; font-size: 12px;
|
|
13
|
+
box-shadow: -4px 0 16px rgba(0,0,0,0.3); color: #e2e8f0;
|
|
14
|
+
}
|
|
15
|
+
#nk-props-panel.open { display: flex; }
|
|
16
|
+
.nk-pp-header {
|
|
17
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
18
|
+
padding: 10px 12px; border-bottom: 1px solid #334155; flex-shrink: 0;
|
|
19
|
+
}
|
|
20
|
+
.nk-pp-tag {
|
|
21
|
+
font-family: 'SF Mono', ui-monospace, monospace; font-size: 13px;
|
|
22
|
+
color: #67e8f9; font-weight: 600;
|
|
23
|
+
}
|
|
24
|
+
.nk-pp-close {
|
|
25
|
+
background: none; border: none; color: #94a3b8; cursor: pointer;
|
|
26
|
+
padding: 4px; display: flex; align-items: center;
|
|
27
|
+
-webkit-tap-highlight-color: transparent;
|
|
28
|
+
}
|
|
29
|
+
.nk-pp-close:hover { color: #e2e8f0; }
|
|
30
|
+
.nk-pp-content {
|
|
31
|
+
flex: 1; overflow-y: auto; padding: 0;
|
|
32
|
+
-webkit-overflow-scrolling: touch;
|
|
33
|
+
}
|
|
34
|
+
.nk-pp-group {
|
|
35
|
+
border-bottom: 1px solid #334155;
|
|
36
|
+
}
|
|
37
|
+
.nk-pp-group-header {
|
|
38
|
+
padding: 8px 12px; font-size: 10px; font-weight: 700;
|
|
39
|
+
text-transform: uppercase; letter-spacing: 0.08em; color: #64748b;
|
|
40
|
+
background: #161325;
|
|
41
|
+
}
|
|
42
|
+
.nk-pp-row {
|
|
43
|
+
display: flex; align-items: center; gap: 8px; padding: 6px 12px;
|
|
44
|
+
border-bottom: 1px solid #1a1730;
|
|
45
|
+
}
|
|
46
|
+
.nk-pp-row:last-child { border-bottom: none; }
|
|
47
|
+
.nk-pp-label {
|
|
48
|
+
width: 90px; flex-shrink: 0; font-size: 11px; color: #94a3b8;
|
|
49
|
+
font-family: 'SF Mono', ui-monospace, monospace;
|
|
50
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
51
|
+
}
|
|
52
|
+
.nk-pp-control { flex: 1; min-width: 0; }
|
|
53
|
+
.nk-pp-control input[type="text"],
|
|
54
|
+
.nk-pp-control input[type="number"] {
|
|
55
|
+
width: 100%; background: #0f0d1a; color: #e2e8f0;
|
|
56
|
+
border: 1px solid #334155; border-radius: 4px; padding: 4px 8px;
|
|
57
|
+
font-size: 11px; font-family: 'SF Mono', ui-monospace, monospace;
|
|
58
|
+
outline: none; box-sizing: border-box;
|
|
59
|
+
}
|
|
60
|
+
.nk-pp-control input:focus { border-color: #7c3aed; }
|
|
61
|
+
.nk-pp-control select {
|
|
62
|
+
width: 100%; background: #0f0d1a; color: #e2e8f0;
|
|
63
|
+
border: 1px solid #334155; border-radius: 4px; padding: 4px 6px;
|
|
64
|
+
font-size: 11px; font-family: inherit; cursor: pointer; outline: none;
|
|
65
|
+
}
|
|
66
|
+
.nk-pp-control select:hover { border-color: #475569; }
|
|
67
|
+
.nk-pp-control select:focus { border-color: #7c3aed; }
|
|
68
|
+
.nk-pp-toggle {
|
|
69
|
+
position: relative; width: 32px; height: 18px; background: #334155;
|
|
70
|
+
border-radius: 9px; cursor: pointer; transition: background 0.2s;
|
|
71
|
+
border: none; padding: 0;
|
|
72
|
+
}
|
|
73
|
+
.nk-pp-toggle.on { background: #7c3aed; }
|
|
74
|
+
.nk-pp-toggle::after {
|
|
75
|
+
content: ''; position: absolute; top: 2px; left: 2px;
|
|
76
|
+
width: 14px; height: 14px; background: #e2e8f0; border-radius: 50%;
|
|
77
|
+
transition: transform 0.2s;
|
|
78
|
+
}
|
|
79
|
+
.nk-pp-toggle.on::after { transform: translateX(14px); }
|
|
80
|
+
.nk-pp-readonly {
|
|
81
|
+
font-size: 10px; color: #64748b; font-family: 'SF Mono', ui-monospace, monospace;
|
|
82
|
+
max-height: 60px; overflow: auto; word-break: break-all;
|
|
83
|
+
}
|
|
84
|
+
.nk-pp-add-row {
|
|
85
|
+
padding: 6px 12px;
|
|
86
|
+
}
|
|
87
|
+
.nk-pp-add-btn {
|
|
88
|
+
background: none; border: 1px dashed #334155; border-radius: 4px;
|
|
89
|
+
color: #64748b; cursor: pointer; font-size: 11px; padding: 4px 10px;
|
|
90
|
+
width: 100%; text-align: center; font-family: inherit;
|
|
91
|
+
-webkit-tap-highlight-color: transparent;
|
|
92
|
+
}
|
|
93
|
+
.nk-pp-add-btn:hover { border-color: #7c3aed; color: #c084fc; }
|
|
94
|
+
.nk-pp-add-form {
|
|
95
|
+
display: flex; gap: 4px; align-items: center; padding: 6px 12px;
|
|
96
|
+
}
|
|
97
|
+
.nk-pp-add-form input {
|
|
98
|
+
flex: 1; background: #0f0d1a; color: #e2e8f0; border: 1px solid #334155;
|
|
99
|
+
border-radius: 4px; padding: 4px 6px; font-size: 11px;
|
|
100
|
+
font-family: 'SF Mono', ui-monospace, monospace; outline: none;
|
|
101
|
+
min-width: 0;
|
|
102
|
+
}
|
|
103
|
+
.nk-pp-add-form input:focus { border-color: #7c3aed; }
|
|
104
|
+
.nk-pp-add-form button {
|
|
105
|
+
background: #7c3aed; color: white; border: none; border-radius: 4px;
|
|
106
|
+
padding: 4px 8px; cursor: pointer; font-size: 11px; flex-shrink: 0;
|
|
107
|
+
}
|
|
108
|
+
.nk-pp-add-form button:hover { background: #6d28d9; }
|
|
109
|
+
.nk-pp-remove {
|
|
110
|
+
background: none; border: none; color: #475569; cursor: pointer;
|
|
111
|
+
padding: 2px; font-size: 14px; line-height: 1; flex-shrink: 0;
|
|
112
|
+
-webkit-tap-highlight-color: transparent;
|
|
113
|
+
}
|
|
114
|
+
.nk-pp-remove:hover { color: #f87171; }
|
|
115
|
+
.nk-pp-color-wrap {
|
|
116
|
+
display: flex; align-items: center; gap: 4px; width: 100%;
|
|
117
|
+
}
|
|
118
|
+
.nk-pp-rule-header {
|
|
119
|
+
padding: 6px 12px; font-size: 11px; color: #c084fc;
|
|
120
|
+
font-family: 'SF Mono', ui-monospace, monospace; background: #1a1730;
|
|
121
|
+
border-bottom: 1px solid #1a1730; cursor: pointer; display: flex;
|
|
122
|
+
align-items: center; justify-content: space-between;
|
|
123
|
+
}
|
|
124
|
+
.nk-pp-rule-header:hover { background: #201d33; }
|
|
125
|
+
.nk-pp-rule-header .nk-pp-toggle-arrow {
|
|
126
|
+
font-size: 10px; color: #64748b; transition: transform 0.15s;
|
|
127
|
+
}
|
|
128
|
+
.nk-pp-rule-header .nk-pp-toggle-arrow.open { transform: rotate(90deg); }
|
|
129
|
+
.nk-pp-rule-body { display: none; }
|
|
130
|
+
.nk-pp-rule-body.open { display: block; }
|
|
131
|
+
.nk-pp-color-wrap input[type="color"] {
|
|
132
|
+
width: 28px; height: 24px; border: 1px solid #334155; border-radius: 4px;
|
|
133
|
+
background: #0f0d1a; cursor: pointer; padding: 0; flex-shrink: 0;
|
|
134
|
+
}
|
|
135
|
+
.nk-pp-color-wrap input[type="text"] { flex: 1; }
|
|
136
|
+
.nk-pp-ai-row {
|
|
137
|
+
display: none; padding: 8px 12px; border-bottom: 1px solid #334155;
|
|
138
|
+
}
|
|
139
|
+
.nk-pp-ai-btn {
|
|
140
|
+
width: 100%; padding: 8px 12px; background: #7c3aed; color: #fff;
|
|
141
|
+
border: none; border-radius: 6px; font-size: 12px; font-weight: 600;
|
|
142
|
+
font-family: inherit; cursor: pointer; display: flex; align-items: center;
|
|
143
|
+
justify-content: center; gap: 6px;
|
|
144
|
+
-webkit-tap-highlight-color: transparent; touch-action: manipulation;
|
|
145
|
+
}
|
|
146
|
+
.nk-pp-ai-btn:hover { background: #6d28d9; }
|
|
147
|
+
.nk-pp-ai-btn:active { background: #5b21b6; }
|
|
148
|
+
|
|
149
|
+
/* Floating properties icon (mobile only) */
|
|
150
|
+
#nk-pp-fab {
|
|
151
|
+
display: none; position: fixed; bottom: 24px; right: 16px;
|
|
152
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
153
|
+
background: #7c3aed; color: #fff; border: none;
|
|
154
|
+
box-shadow: 0 4px 16px rgba(124,58,237,0.5);
|
|
155
|
+
z-index: 99999; cursor: pointer; align-items: center; justify-content: center;
|
|
156
|
+
font-size: 20px; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
|
|
157
|
+
}
|
|
158
|
+
#nk-pp-fab.visible { display: flex; }
|
|
159
|
+
#nk-pp-fab:active { background: #5b21b6; transform: scale(0.92); }
|
|
160
|
+
|
|
161
|
+
/* Mobile responsive */
|
|
162
|
+
@media (max-width: 640px) {
|
|
163
|
+
#nk-props-panel {
|
|
164
|
+
width: 100%; left: 0; right: 0; top: 0;
|
|
165
|
+
height: 100vh; max-height: 100vh;
|
|
166
|
+
border-left: none; border-bottom: none;
|
|
167
|
+
touch-action: manipulation;
|
|
168
|
+
z-index: 199999;
|
|
169
|
+
}
|
|
170
|
+
.nk-pp-ai-row { display: block; }
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
document.head.appendChild(style);
|
|
174
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Properties Panel — right-side panel for editing element properties and styles.
|
|
3
|
+
*/
|
|
4
|
+
import { discoverProperties } from './property-registry.js';
|
|
5
|
+
import { loadCssRulesForElement } from './css-rules.js';
|
|
6
|
+
import { injectPropertiesPanelStyles } from './properties-panel-styles.js';
|
|
7
|
+
import { state as persistState, resetDebounceTimers } from './properties-panel-persist.js';
|
|
8
|
+
import { createGroup, parseInlineStyles, createPropertyRow, createStyleRow, createAddAttributeRow, createAddStyleRow, } from './properties-panel-rows.js';
|
|
9
|
+
let panel;
|
|
10
|
+
let currentElement = null;
|
|
11
|
+
export function createPropertiesPanel() {
|
|
12
|
+
panel = document.createElement('div');
|
|
13
|
+
panel.id = 'nk-props-panel';
|
|
14
|
+
panel.innerHTML = `
|
|
15
|
+
<div class="nk-pp-header">
|
|
16
|
+
<span class="nk-pp-tag"></span>
|
|
17
|
+
<button class="nk-pp-close">
|
|
18
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="nk-pp-ai-row">
|
|
22
|
+
<button class="nk-pp-ai-btn" id="nk-pp-ai-btn">
|
|
23
|
+
<span>✦</span> Ask AI about this element
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="nk-pp-content"></div>
|
|
27
|
+
`;
|
|
28
|
+
injectPropertiesPanelStyles();
|
|
29
|
+
// Close button
|
|
30
|
+
panel.querySelector('.nk-pp-close').addEventListener('click', (e) => {
|
|
31
|
+
e.stopPropagation();
|
|
32
|
+
hidePropertiesPanel();
|
|
33
|
+
});
|
|
34
|
+
// AI button — prefills toolbar AI input with element context
|
|
35
|
+
panel.querySelector('#nk-pp-ai-btn').addEventListener('click', (e) => {
|
|
36
|
+
e.stopPropagation();
|
|
37
|
+
if (!currentElement)
|
|
38
|
+
return;
|
|
39
|
+
const tag = currentElement.tagName.toLowerCase();
|
|
40
|
+
const aiInput = document.querySelector('.nk-tb-page-ai-input');
|
|
41
|
+
if (aiInput) {
|
|
42
|
+
hidePropertiesPanel();
|
|
43
|
+
aiInput.value = `Change the <${tag}> element: `;
|
|
44
|
+
aiInput.focus();
|
|
45
|
+
const sendBtn = document.querySelector('.nk-tb-page-ai-send');
|
|
46
|
+
if (sendBtn)
|
|
47
|
+
sendBtn.disabled = false;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// Prevent clicks from propagating to element selection
|
|
51
|
+
panel.addEventListener('click', (e) => e.stopPropagation());
|
|
52
|
+
panel.addEventListener('mousedown', (e) => e.stopPropagation());
|
|
53
|
+
panel.addEventListener('touchend', (e) => e.stopPropagation());
|
|
54
|
+
document.body.appendChild(panel);
|
|
55
|
+
// Floating action button for mobile
|
|
56
|
+
const fab = document.createElement('button');
|
|
57
|
+
fab.id = 'nk-pp-fab';
|
|
58
|
+
fab.innerHTML = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 113 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>';
|
|
59
|
+
fab.title = 'Edit properties';
|
|
60
|
+
fab.addEventListener('click', (e) => {
|
|
61
|
+
e.stopPropagation();
|
|
62
|
+
if (!currentElement)
|
|
63
|
+
return;
|
|
64
|
+
fab.classList.remove('visible');
|
|
65
|
+
openPanelFull(currentElement);
|
|
66
|
+
});
|
|
67
|
+
fab.addEventListener('touchend', (e) => e.stopPropagation());
|
|
68
|
+
document.body.appendChild(fab);
|
|
69
|
+
return panel;
|
|
70
|
+
}
|
|
71
|
+
function isMobile() { return window.innerWidth <= 640; }
|
|
72
|
+
function toggleToolbarForPanel(show) {
|
|
73
|
+
const toolbar = document.getElementById('nk-editor-toolbar');
|
|
74
|
+
if (toolbar && isMobile())
|
|
75
|
+
toolbar.style.display = show ? '' : 'none';
|
|
76
|
+
}
|
|
77
|
+
function openPanelFull(element) {
|
|
78
|
+
const tag = element.tagName.toLowerCase();
|
|
79
|
+
panel.querySelector('.nk-pp-tag').textContent = `<${tag}>`;
|
|
80
|
+
toggleToolbarForPanel(false);
|
|
81
|
+
// Hide AI chat on mobile when fullscreen panel opens
|
|
82
|
+
const aiChat = document.getElementById('nk-ai-chat');
|
|
83
|
+
if (aiChat)
|
|
84
|
+
aiChat.classList.remove('open');
|
|
85
|
+
buildPanelContent(element);
|
|
86
|
+
panel.classList.add('open');
|
|
87
|
+
}
|
|
88
|
+
export function showPropertiesForElement(element) {
|
|
89
|
+
currentElement = element;
|
|
90
|
+
resetDebounceTimers();
|
|
91
|
+
if (isMobile()) {
|
|
92
|
+
// On mobile: close any existing open panel, restore toolbar, show FAB
|
|
93
|
+
if (panel.classList.contains('open')) {
|
|
94
|
+
panel.classList.remove('open');
|
|
95
|
+
toggleToolbarForPanel(true);
|
|
96
|
+
}
|
|
97
|
+
const fab = document.getElementById('nk-pp-fab');
|
|
98
|
+
if (fab)
|
|
99
|
+
fab.classList.add('visible');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const tag = element.tagName.toLowerCase();
|
|
103
|
+
panel.querySelector('.nk-pp-tag').textContent = `<${tag}>`;
|
|
104
|
+
buildPanelContent(element);
|
|
105
|
+
panel.classList.add('open');
|
|
106
|
+
}
|
|
107
|
+
function buildPanelContent(element) {
|
|
108
|
+
resetDebounceTimers();
|
|
109
|
+
const content = panel.querySelector('.nk-pp-content');
|
|
110
|
+
content.innerHTML = '';
|
|
111
|
+
// Attributes group
|
|
112
|
+
const props = discoverProperties(element);
|
|
113
|
+
const attrGroup = createGroup('Attributes');
|
|
114
|
+
for (const prop of props) {
|
|
115
|
+
attrGroup.appendChild(createPropertyRow(prop, element));
|
|
116
|
+
}
|
|
117
|
+
attrGroup.appendChild(createAddAttributeRow(element, attrGroup));
|
|
118
|
+
content.appendChild(attrGroup);
|
|
119
|
+
// Inline Styles group
|
|
120
|
+
const styleGroup = createGroup('Inline Styles');
|
|
121
|
+
const inlineStyles = parseInlineStyles(element);
|
|
122
|
+
for (const [cssProp, cssVal] of inlineStyles) {
|
|
123
|
+
styleGroup.appendChild(createStyleRow(cssProp, cssVal, element, styleGroup));
|
|
124
|
+
}
|
|
125
|
+
styleGroup.appendChild(createAddStyleRow(element, styleGroup));
|
|
126
|
+
content.appendChild(styleGroup);
|
|
127
|
+
// CSS Rules group
|
|
128
|
+
const cssGroup = createGroup('CSS Rules');
|
|
129
|
+
const cssLoading = document.createElement('div');
|
|
130
|
+
cssLoading.className = 'nk-pp-row';
|
|
131
|
+
cssLoading.innerHTML = '<span class="nk-pp-label" style="width:auto;color:#4a4662">Loading...</span>';
|
|
132
|
+
cssGroup.appendChild(cssLoading);
|
|
133
|
+
content.appendChild(cssGroup);
|
|
134
|
+
const currentElementRef = { get current() { return currentElement; } };
|
|
135
|
+
loadCssRulesForElement(element, cssGroup, currentElementRef, persistState.debounceTimers);
|
|
136
|
+
}
|
|
137
|
+
export function hidePropertiesPanel() {
|
|
138
|
+
panel.classList.remove('open');
|
|
139
|
+
toggleToolbarForPanel(true);
|
|
140
|
+
const fab = document.getElementById('nk-pp-fab');
|
|
141
|
+
if (fab)
|
|
142
|
+
fab.classList.remove('visible');
|
|
143
|
+
currentElement = null;
|
|
144
|
+
resetDebounceTimers();
|
|
145
|
+
}
|
|
146
|
+
export function isPropertiesPanelOpen() {
|
|
147
|
+
return panel.classList.contains('open');
|
|
148
|
+
}
|