@appaflytech/zeyrek-ai-mcp 0.0.18 → 0.0.20

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 (65) hide show
  1. package/README.md +13 -14
  2. package/dist/config.js +2 -0
  3. package/dist/knowledge/componentCatalog.js +204 -836
  4. package/dist/knowledge/componentEvents.js +75 -368
  5. package/dist/knowledge/mobileComponentCatalog.js +331 -0
  6. package/dist/knowledge/mobileComponentEvents.js +50 -0
  7. package/dist/knowledge/normalize.js +63 -66
  8. package/dist/knowledge/platform/lint.js +205 -0
  9. package/dist/knowledge/platform/mobile.js +78 -0
  10. package/dist/knowledge/platform/mobileRules.js +121 -0
  11. package/dist/knowledge/platform/mobileTabs.js +108 -0
  12. package/dist/knowledge/platform/profile.js +20 -0
  13. package/dist/knowledge/platform/resolve.js +122 -0
  14. package/dist/knowledge/platform/web.js +38 -0
  15. package/dist/knowledge/rules.js +6 -29
  16. package/dist/knowledge/styleResolve.js +1 -1
  17. package/dist/knowledge/styleResolveNative.js +410 -0
  18. package/dist/knowledge/themeResolve.js +3 -3
  19. package/dist/platform/capture/actions.js +333 -0
  20. package/dist/platform/capture/browser.js +1 -0
  21. package/dist/platform/{screenshot.js → capture/capture.js} +23 -509
  22. package/dist/platform/capture/index.js +10 -0
  23. package/dist/platform/capture/readiness.js +103 -0
  24. package/dist/platform/capture/summary.js +77 -0
  25. package/dist/platform/capture/target.js +5 -0
  26. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  27. package/dist/platform/capture/targets/studio-web.js +5 -0
  28. package/dist/platform/domSummary.js +1 -1
  29. package/dist/platform/responses.js +1 -2
  30. package/dist/platform/strictArguments.js +2 -22
  31. package/dist/platform/writeDiff.js +31 -58
  32. package/dist/resources/examples/mobile-app-shell-drawer.json +209 -0
  33. package/dist/resources/examples/mobile-app-shell-stack.json +123 -0
  34. package/dist/resources/examples/mobile-app-shell-tab.json +174 -0
  35. package/dist/resources/examples/mobile-list-detail-form.json +656 -0
  36. package/dist/resources/guides/component.md +6 -9
  37. package/dist/resources/guides/functions.md +3 -32
  38. package/dist/resources/guides/getting-started.md +0 -35
  39. package/dist/resources/guides/layout.md +11 -7
  40. package/dist/resources/guides/mobile.md +215 -0
  41. package/dist/resources/guides/page.md +71 -49
  42. package/dist/resources/guides/translation.md +6 -5
  43. package/dist/resources/schema-fingerprint.json +2 -2
  44. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +0 -18
  45. package/dist/server.js +2 -0
  46. package/dist/tools/applications.js +60 -3
  47. package/dist/tools/components.js +50 -10
  48. package/dist/tools/designSchemaPatch.js +18 -13
  49. package/dist/tools/externalComponents.js +25 -4
  50. package/dist/tools/flows.js +51 -38
  51. package/dist/tools/functions.js +10 -20
  52. package/dist/tools/layouts.js +41 -9
  53. package/dist/tools/microservices.js +11 -39
  54. package/dist/tools/mobile.js +203 -0
  55. package/dist/tools/pages.js +127 -95
  56. package/dist/tools/platformInfo.js +2 -1
  57. package/dist/tools/profileWrite.js +45 -0
  58. package/dist/tools/schemaOps.js +2 -2
  59. package/dist/tools/screenWrite.js +2 -2
  60. package/dist/tools/screens.js +9 -3
  61. package/dist/tools/workflows.js +12 -39
  62. package/package.json +2 -7
  63. package/dist/platform/diagnostics.js +0 -85
  64. package/dist/platform/ifExists.js +0 -129
  65. package/dist/platform/writeView.js +0 -17
@@ -0,0 +1,38 @@
1
+ import { ENGINE_STEP_TYPES, STEP_ALIASES } from './profile.js';
2
+ import { pageContainerNode } from '../normalize.js';
3
+ /**
4
+ * Web profili = gateway'in profil öncesi davranışı. Buradaki her değer bugün kodda bir yerde
5
+ * örtük olarak vardı; `test/platform-profile.test.ts` golden'ı bu profilin çıktıyı değiştirmediğini
6
+ * kilitler. Değer değiştirmek web davranışını değiştirir — golden kırmızıya döner.
7
+ */
8
+ export const webProfile = Object.freeze({
9
+ platform: 'web',
10
+ catalogType: 'web',
11
+ layout: {
12
+ modes: new Set(['grid', 'flex', 'cssgrid']),
13
+ blocksMeaningful: true,
14
+ rootContainer: pageContainerNode,
15
+ orderBy: 'block-position'
16
+ },
17
+ appearance: {
18
+ ignoredKeys: new Set(),
19
+ sizeUnits: new Set(['px', '%', 'em', 'rem', 'calc'])
20
+ },
21
+ style: { freeStyle: true, hoverStyle: true, resolver: 'css' },
22
+ steps: {
23
+ registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie']),
24
+ aliases: STEP_ALIASES,
25
+ infoOnly: new Set()
26
+ },
27
+ shell: {
28
+ components: new Set(['navigations', 'sidebar', 'header']),
29
+ suggestions: {},
30
+ legacyShellNodes: new Set()
31
+ },
32
+ externalComponents: true,
33
+ capture: {
34
+ target: 'studio-web',
35
+ devices: { desktop: { width: 1280, height: 800, dpr: 1 } },
36
+ defaultDevice: 'desktop'
37
+ }
38
+ });
@@ -170,13 +170,11 @@ const flowLiteralTextFields = new Map([
170
170
  ['confirm', ['message', 'title', 'confirmText', 'cancelText']]
171
171
  ]);
172
172
  const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
173
- // Barındırıcıya özel tipler (`navigate` web'de, `confirm`/`download` mobilde register edilmez)
174
- // BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten `redirect`e çeviriyor
175
- // (normalize.ts `normalizeFlowHostGaps`) — `redirect` iki barındırıcıda da koşar, yani vaka
176
- // uyarılmaz, ORTADAN KALKAR. Geriye kalan `confirm`/`download` tek başına kusur değildir: sayfa
177
- // tek bir uygulamaya aittir (`Page.ApplicationId`), uygulamanın tipi web ya da mobile'dır
178
- // (`ApplicationType.cs`) ve gateway hangisi olduğunu bilmez. Mobil ayağı uygulama tarafında
179
- // kapanır (`ScreenRuntime.tsx`), lint'te değil.
173
+ // Barındırıcıya özel tipler BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten
174
+ // `redirect`e çeviriyor (normalize.ts `normalizeFlowHostGaps`) — `redirect` iki barındırıcıda da
175
+ // koşar, yani vaka uyarılmaz, ORTADAN KALKAR. Gateway uygulamanın tipini platform profilinden bilir
176
+ // ve barındırıcı sözlüğü `platform/*.ts`'tedir (`profile.steps`); mobil farkları orada `MOB-005`
177
+ // olarak konuşulur (`platform/lint.ts`). Yeni mobil runtime `confirm`/`download`'ı kaydeder.
180
178
  const flowUnregisteredStepTypes = new Set(['end', 'query']);
181
179
  const flowSetterIterateeTypes = new Set(['change', 'fill']);
182
180
  function flowStepList(value) {
@@ -422,8 +420,6 @@ export function rawQueryParameterNames(rawQuery) {
422
420
  }
423
421
  const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
424
422
  const functionsGuide = 'ngapps://guides/functions';
425
- /** FN-006: bu kadar dolu satırdan uzun gövde yorumsuz kalmaz; tek satırlık `return` yorum istemez. */
426
- const FUNCTION_COMMENT_MIN_LINES = 5;
427
423
  export const policyRules = [
428
424
  {
429
425
  id: 'MS-001',
@@ -2034,25 +2030,6 @@ export const policyRules = [
2034
2030
  return [];
2035
2031
  }
2036
2032
  },
2037
- {
2038
- id: 'FN-006',
2039
- module: 'function',
2040
- appliesTo: ['create', 'update'],
2041
- sourceRef: "Kullanıcı isteği 2026-09-18: ajan fonksiyon yazarken gövdeye ne yaptığını söyleyen yorum koyar; Studio editörü yorumu yeşil italik çizer. Gövde kendi satırına sarılır (function-source.ts, GlobalFunction.cs AppendLine), '//' kapanış parantezini yutmaz",
2042
- check: definition => {
2043
- const name = String(definition.name ?? '?');
2044
- const body = typeof definition.body === 'string' ? definition.body : '';
2045
- const lineCount = body.split('\n').filter(line => line.trim() !== '').length;
2046
- if (lineCount < FUNCTION_COMMENT_MIN_LINES || /(^|\s)\/\/|\/\*/m.test(body))
2047
- return [];
2048
- return [{
2049
- ruleId: 'FN-006',
2050
- severity: 'warn',
2051
- message: `'${name}' gövdesi ${lineCount} satır ama hiç yorum yok. Başa ne yaptığını, parametreleri ve dönüşü söyleyen kısa bir '//' yorumu, açık olmayan adımların üstüne de birer satır yorum ekleyin; kodu tekrar eden yorum yazmayın.`,
2052
- guide: functionsGuide
2053
- }];
2054
- }
2055
- },
2056
2033
  {
2057
2034
  id: 'FLOW-024',
2058
2035
  module: 'flow',
@@ -2389,7 +2366,7 @@ export const policyRules = [
2389
2366
  id: 'FLOW-023',
2390
2367
  module: 'flow',
2391
2368
  appliesTo: ['create', 'update'],
2392
- sourceRef: "Adım tipi sözlüğü backend'indir (StepType.cs) ama koşan tipler barındırıcıdan gelir: flow-engine.ts start·setter·code·condition·microservice·validation·flow-trigger register eder, web Runtime.svelte alert·confirm·redirect·download·storage·setcookie ekler, mobil ScreenRuntime.tsx alert·redirect·navigate·storage·setcookie ekler. navigate web'de, confirm/download mobilde YOKTUR; end ve query hiçbir barındırıcıda yoktur (Studio kataloğu da bunu visibility ile söyler: navigate 'mobile', confirm/download 'web'; End eklenebilir adımlar arasında değil — EventActionsAdd.svelte). Register edilmemiş tipe gelen akış SESSİZCE durur: flow-engine.ts process() callback bulamazsa hiçbir şey yapmaz (next yok, completed/stopped yok, hata yok, log yok), run() Promise'i çözülmez ve flowRunFinished() hiç koşmaz — __ngAppsFlowActivity.running sıfıra inmez, page_screenshot'ın hazır bekleyişi her eylemde bütçesini sonuna kadar yakar",
2369
+ sourceRef: "Adım tipi sözlüğü backend'indir (StepType.cs) ama koşan tipler barındırıcıdan gelir: flow-engine.ts start·setter·code·condition·microservice·validation·flow-trigger register eder, web Runtime.svelte alert·confirm·redirect·download·storage·setcookie ekler, mobil @ng-apps/ui-native runtime-tasks.ts alert·confirm·redirect·navigate·storage·setcookie·download ekler. navigate web'de YOKTUR; end ve query hiçbir barındırıcıda yoktur (Studio kataloğu da bunu visibility ile söyler: navigate 'mobile', End eklenebilir adımlar arasında değil — EventActionsAdd.svelte). Register edilmemiş tipe gelen akış SESSİZCE durur: flow-engine.ts process() callback bulamazsa hiçbir şey yapmaz (next yok, completed/stopped yok, hata yok, log yok), run() Promise'i çözülmez ve flowRunFinished() hiç koşmaz — __ngAppsFlowActivity.running sıfıra inmez, page_screenshot'ın hazır bekleyişi her eylemde bütçesini sonuna kadar yakar",
2393
2370
  check: definition => {
2394
2371
  const steps = flowAllSteps(definition);
2395
2372
  // Studio her akış tuvaline dekoratif bir End düğümü koyar ve onu sildirmez; zararlı olan
@@ -539,7 +539,7 @@ const unwrapProp = (value) => {
539
539
  result[key] = unwrapProp(item);
540
540
  return result;
541
541
  };
542
- const rawProp = (props, name) => {
542
+ export const rawProp = (props, name) => {
543
543
  const prop = props?.[name];
544
544
  // İfadeye bağlı prop'un değeri çalışma anında oluşur, burada çözülemez.
545
545
  return prop && typeof prop === 'object' && 'data' in prop ? unwrapProp(prop.data) : undefined;
@@ -0,0 +1,410 @@
1
+ import { rawProp } from './styleResolve.js';
2
+ import { mergeTypeScale, resolveTheme } from './themeResolve.js';
3
+ /* ---- css-value.ts ---- */
4
+ const warn = (context, message) => {
5
+ context?.warnings?.push(message);
6
+ return null;
7
+ };
8
+ export function parseLength(value, context) {
9
+ if (value === undefined || value === null || value === '')
10
+ return null;
11
+ if (typeof value === 'number')
12
+ return Number.isFinite(value) ? value : null;
13
+ const text = String(value).trim();
14
+ if (!text)
15
+ return null;
16
+ const numeric = Number(text);
17
+ if (Number.isFinite(numeric))
18
+ return numeric;
19
+ if (text.endsWith('%')) {
20
+ const percent = Number(text.slice(0, -1));
21
+ return Number.isFinite(percent) ? `${percent}%` : null;
22
+ }
23
+ const unit = /^(-?[\d.]+)(px|em|rem)$/.exec(text);
24
+ if (unit) {
25
+ const amount = Number(unit[1]);
26
+ if (!Number.isFinite(amount))
27
+ return null;
28
+ if (unit[2] === 'px')
29
+ return amount;
30
+ const base = unit[2] === 'em' ? context?.base ?? context?.root : context?.root;
31
+ if (base === undefined)
32
+ return warn(context, `${text}: em/rem tabanı yok, değer atlandı`);
33
+ return Math.round(amount * base * 1000) / 1000;
34
+ }
35
+ return warn(context, `${text}: React Native'de karşılığı olmayan uzunluk`);
36
+ }
37
+ export function parsePanelLength(value, context) {
38
+ if (typeof value === 'number' && value === 0)
39
+ return null;
40
+ return parseLength(value, context);
41
+ }
42
+ export function parseFontFamily(value) {
43
+ if (value === undefined || value === null || value === '')
44
+ return null;
45
+ const first = String(value).split(',')[0].trim().replace(/^["']|["']$/g, '');
46
+ return first || null;
47
+ }
48
+ export function parseLineHeight(value, fontSize, context) {
49
+ if (value === undefined || value === null || value === '')
50
+ return null;
51
+ const text = String(value).trim();
52
+ const numeric = Number(text);
53
+ const base = fontSize ?? context?.base ?? context?.root;
54
+ if (Number.isFinite(numeric)) {
55
+ if (numeric <= 0)
56
+ return null;
57
+ const ratio = numeric >= 4 ? numeric / 100 : numeric;
58
+ if (base === undefined)
59
+ return warn(context, `lineHeight ${text}: punto bilinmiyor, atlandı`);
60
+ return Math.round(ratio * base * 1000) / 1000;
61
+ }
62
+ if (text.endsWith('%')) {
63
+ const percent = Number(text.slice(0, -1));
64
+ if (!Number.isFinite(percent) || base === undefined)
65
+ return warn(context, `lineHeight ${text}: punto bilinmiyor, atlandı`);
66
+ return Math.round((percent / 100) * base * 1000) / 1000;
67
+ }
68
+ const length = parseLength(text, context);
69
+ return typeof length === 'number' ? length : null;
70
+ }
71
+ export function parseOpacity(value) {
72
+ if (value === undefined || value === null || value === '')
73
+ return null;
74
+ const amount = Number(value);
75
+ if (!Number.isFinite(amount) || amount < 0 || amount >= 100)
76
+ return null;
77
+ return Math.round((amount / 100) * 1000) / 1000;
78
+ }
79
+ export function parseBorderShorthand(value, context) {
80
+ if (value === undefined || value === null || value === '')
81
+ return null;
82
+ const text = String(value).trim();
83
+ if (!text || text === 'none')
84
+ return { width: 0, style: 'solid' };
85
+ const parts = text.split(/\s+(?![^(]*\))/);
86
+ const width = parseLength(parts[0], context);
87
+ if (typeof width !== 'number')
88
+ return warn(context, `${text}: kenarlık genişliği çözülemedi`);
89
+ const style = parts.find(part => /^(solid|dashed|dotted)$/.test(part)) ?? 'solid';
90
+ const color = parts.find(part => part !== parts[0] && !/^(solid|dashed|dotted)$/.test(part));
91
+ return { width, style, ...(color ? { color } : {}) };
92
+ }
93
+ /* ---- theme-native.ts ---- */
94
+ const camelize = (name) => name.replace(/^--ng-/, '').replace(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
95
+ const hexToRgb = (hex) => {
96
+ const match = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
97
+ if (!match)
98
+ return null;
99
+ const digits = match[1].length === 3 ? match[1].split('').map(digit => digit + digit).join('') : match[1];
100
+ const value = parseInt(digits, 16);
101
+ return [(value >> 16) & 255, (value >> 8) & 255, value & 255];
102
+ };
103
+ const toHex = (channel) => Math.round(Math.max(0, Math.min(255, channel))).toString(16).padStart(2, '0');
104
+ const NAMED_COLORS = { white: '#ffffff', black: '#000000' };
105
+ const resolveColorMix = (value) => {
106
+ const match = /^color-mix\(\s*in\s+srgb\s*,\s*([^,]+?)\s+([\d.]+)%\s*,\s*([^)]+?)\s*\)$/i.exec(value.trim());
107
+ if (!match)
108
+ return value;
109
+ const [, rawFrom = '', percent = '0', rawTo = ''] = match;
110
+ const from = hexToRgb(NAMED_COLORS[rawFrom.trim()] ?? rawFrom);
111
+ const to = hexToRgb(NAMED_COLORS[rawTo.trim()] ?? rawTo);
112
+ if (!from || !to)
113
+ return rawFrom.trim();
114
+ const weight = Number(percent) / 100;
115
+ const mixed = from.map((channel, index) => channel * weight + to[index] * (1 - weight));
116
+ return `#${mixed.map(toHex).join('')}`;
117
+ };
118
+ const CALC_UNIT = /^(-?[\d.]+)([a-z%]*)$/;
119
+ const evaluateCalc = (value) => {
120
+ let result = value;
121
+ while (true) {
122
+ const match = /calc\(([^()]*)\)/.exec(result);
123
+ if (!match)
124
+ return result;
125
+ const [left, operator = '', right] = match[1].trim().split(/\s+/);
126
+ const leftParts = CALC_UNIT.exec(left ?? '');
127
+ const rightAmount = Number(right);
128
+ if (!leftParts || !Number.isFinite(rightAmount) || !'*/'.includes(operator)) {
129
+ result = result.replace(match[0], match[1].trim());
130
+ return result;
131
+ }
132
+ const amount = operator === '*' ? Number(leftParts[1]) * rightAmount : Number(leftParts[1]) / rightAmount;
133
+ result = result.replace(match[0], `${Math.round(amount * 1000) / 1000}${leftParts[2]}`);
134
+ }
135
+ };
136
+ const resolveVars = (raw) => {
137
+ const resolved = {};
138
+ const expand = (value, seen) => {
139
+ const expanded = value.replace(/var\((--[a-z0-9-]+)(?:\s*,\s*([^)]+))?\)/g, (whole, name, fallback) => {
140
+ if (seen.has(name))
141
+ return whole;
142
+ const target = raw[name];
143
+ if (target === undefined)
144
+ return fallback?.trim() ?? whole;
145
+ return expand(target, new Set([...seen, name]));
146
+ });
147
+ return resolveColorMix(evaluateCalc(expanded));
148
+ };
149
+ for (const [name, value] of Object.entries(raw))
150
+ resolved[name] = expand(value, new Set([name]));
151
+ return resolved;
152
+ };
153
+ const px = (value, fallback) => {
154
+ if (!value)
155
+ return fallback;
156
+ const amount = Number(String(value).replace(/px$/, ''));
157
+ return Number.isFinite(amount) ? amount : fallback;
158
+ };
159
+ export function parseShadow(css) {
160
+ if (!css || css === 'none')
161
+ return {};
162
+ const color = /rgba?\(([^)]+)\)/.exec(css);
163
+ const lengths = css.replace(/rgba?\([^)]+\)/, '').trim().split(/\s+/)
164
+ .map(part => Number(part.replace(/px$/, '')))
165
+ .filter(part => Number.isFinite(part));
166
+ const [offsetX = 0, offsetY = 0, blur = 0] = lengths;
167
+ const channels = color?.[1].split(',').map(part => Number(part.trim())) ?? [];
168
+ const [red = 0, green = 0, blue = 0, alpha = 1] = channels;
169
+ return {
170
+ shadowColor: `#${toHex(red)}${toHex(green)}${toHex(blue)}`,
171
+ shadowOffset: { width: offsetX, height: offsetY },
172
+ shadowOpacity: alpha,
173
+ shadowRadius: blur,
174
+ elevation: Math.round(blur / 2)
175
+ };
176
+ }
177
+ /**
178
+ * Proje temasının mobil karşılığı. Frontend `resolveThemeNative(overrides, mode = 'light')` ile
179
+ * aynı: mod çağırandan gelir, temanın kendi `mode` alanı okunmaz (runtime da okumuyor).
180
+ */
181
+ export function resolveThemeNative(theme, mode = 'light') {
182
+ const resolvedTheme = resolveTheme(theme, mode);
183
+ const vars = resolveVars(resolvedTheme.cssVars);
184
+ const tokens = resolvedTheme.tokens;
185
+ const colors = {};
186
+ for (const [name, value] of Object.entries(vars)) {
187
+ if (/^(#|rgb|hsl)/.test(value))
188
+ colors[camelize(name)] = value;
189
+ }
190
+ const fontSize = px(vars['--ng-font-size'], 14);
191
+ const spacingBase = px(vars['--ng-spacing'], 10);
192
+ const spacing = { base: spacingBase };
193
+ for (const [name, value] of Object.entries(vars)) {
194
+ const level = /^--ng-spacing-([a-z]+)$/.exec(name);
195
+ if (level)
196
+ spacing[level[1]] = px(value, spacingBase);
197
+ }
198
+ const scale = {};
199
+ for (const level of Object.keys(mergeTypeScale(tokens.typeScale))) {
200
+ scale[level] = px(vars[`--ng-font-size-${level}`], fontSize);
201
+ const narrow = vars[`--ng-font-size-${level}-mobile`];
202
+ if (narrow)
203
+ scale[`${level}Mobile`] = px(narrow, fontSize);
204
+ }
205
+ return {
206
+ mode,
207
+ vars,
208
+ colors,
209
+ spacing,
210
+ radius: {
211
+ sm: px(vars['--ng-radius-sm'], 2),
212
+ md: px(vars['--ng-radius-md'], 4),
213
+ lg: px(vars['--ng-radius-lg'], 12),
214
+ pill: px(vars['--ng-radius-pill'], 999)
215
+ },
216
+ font: {
217
+ family: vars['--ng-font'] ?? tokens.fontFamily,
218
+ headingFamily: vars['--ng-font-heading'] ?? tokens.fontFamily,
219
+ size: fontSize,
220
+ weight: vars['--ng-font-weight'] ?? tokens.fontWeight,
221
+ scale
222
+ },
223
+ shadow: {
224
+ none: {},
225
+ sm: parseShadow(vars['--ng-shadow-sm']),
226
+ md: parseShadow(vars['--ng-shadow-md']),
227
+ lg: parseShadow(vars['--ng-shadow-lg'])
228
+ }
229
+ };
230
+ }
231
+ /* ---- compose-native-style.ts ---- */
232
+ const TEXT_KEYS = new Set(['color', 'fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'letterSpacing', 'lineHeight', 'textAlign', 'textDecorationLine', 'textTransform']);
233
+ const RENAMED_KEYS = { background: 'backgroundColor', textDecoration: 'textDecorationLine' };
234
+ const IGNORED_KEYS = new Set(['boxShadow', 'transition', 'cursor', 'boxSizing', 'whiteSpace', 'wordBreak', 'textOverflow', 'backdropFilter', 'filter']);
235
+ const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
236
+ const isSet = (value) => value !== undefined && value !== null && value !== '' && !(typeof value === 'string' && value.includes('{{'));
237
+ export function resolveThemeToken(value, theme) {
238
+ return value.replace(/var\((--[a-z0-9-]+)(?:\s*,\s*([^)]+))?\)/g, (whole, name, fallback) => theme.vars[name] ?? fallback?.trim() ?? whole);
239
+ }
240
+ const asText = (value, theme) => isSet(value) ? resolveThemeToken(String(value), theme) : undefined;
241
+ const composeRadius = (value, shell, theme, context) => {
242
+ if (!isSet(value))
243
+ return;
244
+ const preset = theme.radius[String(value)];
245
+ if (preset !== undefined) {
246
+ shell.borderRadius = preset;
247
+ return;
248
+ }
249
+ const parts = resolveThemeToken(String(value), theme).trim().split(/\s+/);
250
+ if (parts.length === 1) {
251
+ const radius = parsePanelLength(parts[0], context);
252
+ if (typeof radius === 'number')
253
+ shell.borderRadius = radius;
254
+ return;
255
+ }
256
+ const corners = ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'];
257
+ corners.forEach((corner, index) => {
258
+ const radius = parseLength(parts[index] ?? parts[index % parts.length], context);
259
+ if (typeof radius === 'number')
260
+ shell[corner] = radius;
261
+ });
262
+ };
263
+ const composeBorders = (surface, shell, theme, context) => {
264
+ const sides = [
265
+ ['borderTop', 'borderTopWidth', 'borderTopColor'],
266
+ ['borderRight', 'borderRightWidth', 'borderRightColor'],
267
+ ['borderBottom', 'borderBottomWidth', 'borderBottomColor'],
268
+ ['borderLeft', 'borderLeftWidth', 'borderLeftColor']
269
+ ];
270
+ let wrote = false;
271
+ for (const [field, widthKey, colorKey] of sides) {
272
+ if (!isSet(surface[field]))
273
+ continue;
274
+ const border = parseBorderShorthand(resolveThemeToken(String(surface[field]), theme), context);
275
+ if (!border)
276
+ continue;
277
+ wrote = true;
278
+ shell[widthKey] = border.width;
279
+ if (border.color)
280
+ shell[colorKey] = border.color;
281
+ if (border.style !== 'solid' && !shell.borderStyle)
282
+ shell.borderStyle = border.style;
283
+ }
284
+ if (wrote)
285
+ return;
286
+ const width = Number(surface.borderWidth);
287
+ const color = asText(surface.borderColor, theme);
288
+ const style = asText(surface.borderStyle, theme);
289
+ if (Number.isFinite(width) && isSet(surface.borderWidth)) {
290
+ shell.borderWidth = width;
291
+ if (width > 0) {
292
+ shell.borderColor = color ?? theme.colors.borderPrimary;
293
+ if (style && style !== 'solid')
294
+ shell.borderStyle = style;
295
+ }
296
+ return;
297
+ }
298
+ if (color || style) {
299
+ shell.borderWidth = 1;
300
+ shell.borderColor = color ?? theme.colors.borderPrimary;
301
+ if (style && style !== 'solid')
302
+ shell.borderStyle = style;
303
+ }
304
+ };
305
+ const composeSurface = (surface, shell, theme, context) => {
306
+ const background = asText(surface.background, theme);
307
+ if (background)
308
+ shell.backgroundColor = background;
309
+ composeRadius(surface.radius, shell, theme, context);
310
+ composeBorders(surface, shell, theme, context);
311
+ if (isSet(surface.shadow)) {
312
+ const raw = String(surface.shadow);
313
+ const key = raw;
314
+ if (!theme.shadow[key] && (raw.includes(',') || raw.includes('inset'))) {
315
+ context.warnings?.push(`shadow: RN tek katman taşır, ilk katman uygulandı (${raw})`);
316
+ }
317
+ Object.assign(shell, theme.shadow[key] ?? parseShadow(resolveThemeToken(raw, theme)));
318
+ }
319
+ const opacity = parseOpacity(surface.opacity);
320
+ if (opacity !== null)
321
+ shell.opacity = opacity;
322
+ if (isSet(surface.contentOverflow)) {
323
+ const overflow = String(surface.contentOverflow);
324
+ shell.overflow = overflow === 'visible' ? 'visible' : 'hidden';
325
+ if (overflow === 'auto')
326
+ context.warnings?.push('contentOverflow: auto — RN kaydırmayı bileşenin kendisi açar, kutu hidden yapıldı');
327
+ }
328
+ };
329
+ const composeTypography = (typography, text, theme, context) => {
330
+ const color = asText(typography.textColor, theme);
331
+ if (color)
332
+ text.color = color;
333
+ const family = parseFontFamily(asText(typography.fontFamily, theme));
334
+ if (family)
335
+ text.fontFamily = family;
336
+ const fontSize = parsePanelLength(typeof typography.fontSize === 'string' ? resolveThemeToken(typography.fontSize, theme) : typography.fontSize, context);
337
+ if (typeof fontSize === 'number')
338
+ text.fontSize = fontSize;
339
+ if (isSet(typography.fontWeight))
340
+ text.fontWeight = String(typography.fontWeight);
341
+ if (isSet(typography.fontStyle))
342
+ text.fontStyle = String(typography.fontStyle);
343
+ if (isSet(typography.textDecoration))
344
+ text.textDecorationLine = String(typography.textDecoration);
345
+ if (isSet(typography.textAlign))
346
+ text.textAlign = String(typography.textAlign);
347
+ const letterSpacing = parsePanelLength(typography.letterSpacing, context);
348
+ if (typeof letterSpacing === 'number')
349
+ text.letterSpacing = letterSpacing;
350
+ const lineHeight = parseLineHeight(typography.lineHeight, text.fontSize ?? context.base, context);
351
+ if (lineHeight !== null)
352
+ text.lineHeight = lineHeight;
353
+ };
354
+ const composeFreeStyle = (value, shell, text, theme, context) => {
355
+ if (!isPlainObject(value))
356
+ return;
357
+ for (const [rawKey, rawValue] of Object.entries(value)) {
358
+ if (!isSet(rawValue))
359
+ continue;
360
+ if (rawKey.startsWith('&') || rawKey.startsWith('--') || rawKey.startsWith(':')) {
361
+ context.warnings?.push(`${rawKey}: RN'de alt seçici yok, atlandı`);
362
+ continue;
363
+ }
364
+ const key = RENAMED_KEYS[rawKey] ?? rawKey;
365
+ if (IGNORED_KEYS.has(key))
366
+ continue;
367
+ const target = TEXT_KEYS.has(key) ? text : shell;
368
+ const resolved = typeof rawValue === 'string' ? resolveThemeToken(rawValue, theme) : rawValue;
369
+ if (typeof resolved === 'number') {
370
+ target[key] = resolved;
371
+ continue;
372
+ }
373
+ const length = key === 'fontWeight' || typeof resolved !== 'string' ? null : parseLength(resolved, { ...context, warnings: undefined });
374
+ target[key] = length ?? resolved;
375
+ }
376
+ };
377
+ export function composeNativeStyle(input, theme) {
378
+ const warnings = [];
379
+ const shell = {};
380
+ const text = {};
381
+ const context = { base: theme.font.size, root: theme.font.size, warnings };
382
+ composeFreeStyle(input.themeStyle, shell, text, theme, context);
383
+ if (isPlainObject(input.surfaceStyle))
384
+ composeSurface(input.surfaceStyle, shell, theme, context);
385
+ if (isPlainObject(input.typographyStyle))
386
+ composeTypography(input.typographyStyle, text, theme, context);
387
+ if (isPlainObject(input.hoverStyle) && Object.values(input.hoverStyle).some(isSet)) {
388
+ warnings.push('hoverStyle: dokunmatikte hover durumu yok, atlandı');
389
+ }
390
+ composeFreeStyle(input.style, shell, text, theme, context);
391
+ return { shell, text, warnings };
392
+ }
393
+ /**
394
+ * Bir düğümün mobil çizerdeki stili (`NativeRenderer` → `composeNativeStyle(props, theme)`). Çizer
395
+ * düğümün KENDİ prop'larını derler; temanın `componentStyles`/preset katmanı mobil çizerde henüz
396
+ * uygulanmıyor, bu yüzden rapora da girmez. İfadeye bağlı prop çalışma anında çözülür, burada yok.
397
+ */
398
+ export function nodeNativeStyle(node, theme) {
399
+ const props = node.props;
400
+ const group = (name) => {
401
+ const value = rawProp(props, name);
402
+ return isPlainObject(value) ? value : null;
403
+ };
404
+ return composeNativeStyle({
405
+ surfaceStyle: group('surfaceStyle'),
406
+ typographyStyle: group('typographyStyle'),
407
+ hoverStyle: group('hoverStyle'),
408
+ style: rawProp(props, 'style')
409
+ }, theme);
410
+ }
@@ -332,8 +332,8 @@ export function buildSpacingScaleVars(scale) {
332
332
  .join('\n\t');
333
333
  }
334
334
  /**
335
- * Bir seviyenin mobil karşılığının masaüstüne oranı — bugünkü breakpoint değerlerinden türer.
336
- * Kullanıcı tek çarpan ayarlar, mobil ona orantılı hareket eder.
335
+ * Bir seviyenin dar ekran karşılığının masaüstüne oranı — bugünkü breakpoint değerlerinden türer.
336
+ * Kullanıcı tek çarpan ayarlar, dar ekran ona orantılı hareket eder.
337
337
  */
338
338
  const TYPE_SCALE_MOBILE_RATIO = {
339
339
  h1: 1.7 / 2.25,
@@ -363,7 +363,7 @@ export function mergeTypeScale(overrides) {
363
363
  }
364
364
  return result;
365
365
  }
366
- /** Ölçekten `--ng-font-size-*` değişkenlerini üretir (masaüstü + mobil). */
366
+ /** Ölçekten `--ng-font-size-*` değişkenlerini üretir (masaüstü + dar ekran). */
367
367
  export function buildTypeScaleVars(scale) {
368
368
  const lines = [];
369
369
  for (const [level, factor] of Object.entries(scale)) {