@appaflytech/zeyrek-ai-mcp 0.0.21 → 0.0.22

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 (51) hide show
  1. package/dist/config.js +2 -0
  2. package/dist/knowledge/componentCatalog.js +3 -3
  3. package/dist/knowledge/componentEvents.js +9 -2
  4. package/dist/knowledge/mobileComponentCatalog.js +543 -0
  5. package/dist/knowledge/mobileComponentEvents.js +98 -0
  6. package/dist/knowledge/normalize.js +52 -11
  7. package/dist/knowledge/platform/lint.js +205 -0
  8. package/dist/knowledge/platform/mobile.js +78 -0
  9. package/dist/knowledge/platform/mobileRules.js +123 -0
  10. package/dist/knowledge/platform/mobileTabs.js +108 -0
  11. package/dist/knowledge/platform/profile.js +20 -0
  12. package/dist/knowledge/platform/resolve.js +122 -0
  13. package/dist/knowledge/platform/web.js +38 -0
  14. package/dist/knowledge/rules.js +6 -8
  15. package/dist/knowledge/styleResolve.js +1 -1
  16. package/dist/knowledge/styleResolveNative.js +410 -0
  17. package/dist/knowledge/themeResolve.js +3 -3
  18. package/dist/platform/capture/actions.js +333 -0
  19. package/dist/platform/capture/browser.js +1 -0
  20. package/dist/platform/{screenshot.js → capture/capture.js} +23 -509
  21. package/dist/platform/capture/index.js +10 -0
  22. package/dist/platform/capture/readiness.js +103 -0
  23. package/dist/platform/capture/summary.js +77 -0
  24. package/dist/platform/capture/target.js +5 -0
  25. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  26. package/dist/platform/capture/targets/studio-web.js +5 -0
  27. package/dist/platform/domSummary.js +1 -1
  28. package/dist/resources/examples/mobile-app-shell-drawer.json +209 -0
  29. package/dist/resources/examples/mobile-app-shell-stack.json +123 -0
  30. package/dist/resources/examples/mobile-app-shell-tab.json +174 -0
  31. package/dist/resources/examples/mobile-list-detail-form.json +656 -0
  32. package/dist/resources/guides/component.md +6 -1
  33. package/dist/resources/guides/functions.md +1 -1
  34. package/dist/resources/guides/layout.md +11 -7
  35. package/dist/resources/guides/mobile.md +264 -0
  36. package/dist/resources/guides/page.md +55 -15
  37. package/dist/resources/guides/translation.md +1 -1
  38. package/dist/server.js +2 -0
  39. package/dist/tools/applications.js +60 -3
  40. package/dist/tools/components.js +50 -10
  41. package/dist/tools/designSchemaPatch.js +13 -4
  42. package/dist/tools/externalComponents.js +25 -4
  43. package/dist/tools/flows.js +9 -2
  44. package/dist/tools/layouts.js +41 -9
  45. package/dist/tools/mobile.js +203 -0
  46. package/dist/tools/pages.js +115 -35
  47. package/dist/tools/platformInfo.js +2 -1
  48. package/dist/tools/profileWrite.js +45 -0
  49. package/dist/tools/screenWrite.js +2 -2
  50. package/dist/tools/screens.js +9 -3
  51. package/package.json +1 -1
@@ -0,0 +1,108 @@
1
+ export const MAX_MOBILE_TABS = 5;
2
+ const STYLES = new Set(['default', 'floating', 'custom']);
3
+ function record(value) {
4
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
5
+ }
6
+ function text(value) {
7
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
8
+ }
9
+ /** Öğenin sayfa id'si: `{ page }` ya da düz metin (`items: ['id']`). */
10
+ function entryPage(entry) {
11
+ const item = record(entry);
12
+ return item ? text(item.page) : text(entry);
13
+ }
14
+ /** `Application.properties.mobile.tabs` — ham değer, doğrulanmamış. */
15
+ export function readMobileTabs(properties) {
16
+ return record(record(properties)?.mobile)?.tabs;
17
+ }
18
+ /**
19
+ * Ham sekme ayarını çizilebilir biçime getirir; çizilecek çubuk yoksa `null` (yığın).
20
+ *
21
+ * - Nesne değilse, `enabled !== true` ise ya da geçerli öğe kalmadıysa → `null`.
22
+ * - `style` bilinmiyorsa `default`; `showLabels` boolean değilse `true`.
23
+ * - Öğe `{ page }` ya da düz sayfa id'sidir; sayfası yoksa düşer; `pages` verildiyse orada olmayan
24
+ * sayfa düşer; tekrar eden sayfa (ilki kalır) atılır; `MAX_MOBILE_TABS`'tan sonrası kırpılır.
25
+ * `label`/`icon` boş metinse yazılmaz.
26
+ * - Görünüm alanı yoktur: çubuk temadan çizilir; eski kayıtların `appearance`'ı okunmaz.
27
+ * - `customWidgetId` yalnız `custom` türde ve metinse yazılır.
28
+ */
29
+ export function normalizeMobileTabs(tabs, pages) {
30
+ const source = record(tabs);
31
+ if (!source || source.enabled !== true)
32
+ return null;
33
+ const known = pages ? new Set(pages.map(page => page.id)) : undefined;
34
+ const seen = new Set();
35
+ const items = [];
36
+ for (const entry of Array.isArray(source.items) ? source.items : []) {
37
+ const item = record(entry);
38
+ const page = entryPage(entry);
39
+ if (!page || seen.has(page) || (known && !known.has(page)))
40
+ continue;
41
+ seen.add(page);
42
+ const label = text(item?.label);
43
+ const icon = text(item?.icon);
44
+ items.push({ page, ...(label ? { label } : {}), ...(icon ? { icon } : {}) });
45
+ if (items.length === MAX_MOBILE_TABS)
46
+ break;
47
+ }
48
+ if (items.length === 0)
49
+ return null;
50
+ const style = (typeof source.style === 'string' && STYLES.has(source.style) ? source.style : 'default');
51
+ const customWidgetId = style === 'custom' ? text(source.customWidgetId) : undefined;
52
+ return {
53
+ enabled: true,
54
+ style,
55
+ items,
56
+ ...(customWidgetId ? { customWidgetId } : {}),
57
+ showLabels: typeof source.showLabels === 'boolean' ? source.showLabels : true
58
+ };
59
+ }
60
+ /**
61
+ * MOB-007 denetimi: ham ayarın normalizasyonda neyi kaybedeceğini ve tutarsızlıklarını söyler.
62
+ * Yazım yine platforma gider (D3); bulgular yalnız uyarıdır.
63
+ */
64
+ export function mobileTabsProblems(tabs, context = {}) {
65
+ if (tabs === undefined || tabs === null)
66
+ return [];
67
+ const source = record(tabs);
68
+ if (!source)
69
+ return [`properties.mobile.tabs nesne olmalı (gelen: ${Array.isArray(tabs) ? 'dizi' : typeof tabs}) — sekme çubuğu çizilmez, uygulama yığınla açılır`];
70
+ const problems = [];
71
+ const rawItems = Array.isArray(source.items) ? source.items : undefined;
72
+ if (source.enabled !== undefined && typeof source.enabled !== 'boolean')
73
+ problems.push(`enabled boolean olmalı (gelen: ${JSON.stringify(source.enabled)}) — true olmayan her değer sekmeleri kapatır`);
74
+ if (source.style !== undefined && !(typeof source.style === 'string' && STYLES.has(source.style)))
75
+ problems.push(`style '${String(source.style)}' tanınmıyor (default | floating | custom) — 'default' çizilir`);
76
+ if (source.items !== undefined && !rawItems)
77
+ problems.push('items dizi olmalı — hiçbir sekme çizilmez');
78
+ const items = rawItems ?? [];
79
+ const pageIds = items.map(entryPage);
80
+ if (source.enabled === true && items.length === 0)
81
+ problems.push("enabled:true ama items boş — çubuk çizilmez; en az bir sayfa ekleyin ya da enabled:false yazın");
82
+ if (items.length > MAX_MOBILE_TABS)
83
+ problems.push(`${items.length} sekme var, en çok ${MAX_MOBILE_TABS} çizilir — fazlası kırpılır (${pageIds.slice(MAX_MOBILE_TABS).filter(Boolean).join(', ')})`);
84
+ const missingPage = pageIds.filter(id => !id).length;
85
+ if (missingPage > 0)
86
+ problems.push(`${missingPage} öğede page (sayfa id'si) yok — o öğeler düşer`);
87
+ const duplicates = pageIds.filter((id, index) => id && pageIds.indexOf(id) !== index);
88
+ if (duplicates.length > 0)
89
+ problems.push(`aynı sayfa birden çok sekmede: ${[...new Set(duplicates)].join(', ')} — tekrarlar atılır`);
90
+ if (context.pages) {
91
+ const known = new Set(context.pages.map(page => page.id));
92
+ const unknown = pageIds.filter((id) => Boolean(id) && !known.has(id));
93
+ if (unknown.length > 0)
94
+ problems.push(`var olmayan sayfa: ${[...new Set(unknown)].join(', ')} — page_list ile id'leri doğrulayın; bu öğeler düşer (item.page slug değil sayfa id'sidir)`);
95
+ }
96
+ const normalized = normalizeMobileTabs(tabs, context.pages ?? null);
97
+ if (normalized && context.homepageId && normalized.items[0]?.page !== context.homepageId) {
98
+ problems.push(`açılış sayfası (${context.homepageId}) ilk sekme değil — uygulama açılış sayfasıyla başlar ama çubukta başka sekme seçili görünür; açılış sayfasını items'ın başına alın`);
99
+ }
100
+ if (normalized?.style === 'custom') {
101
+ const widgetId = normalized.customWidgetId;
102
+ if (!widgetId)
103
+ problems.push("style:'custom' ama customWidgetId yok — özel sekme çubuğu widget'ı seçilmeli (component_list kind:'widget')");
104
+ else if (context.widgetIds && !context.widgetIds.has(widgetId))
105
+ problems.push(`customWidgetId '${widgetId}' bu mobil uygulamanın widget'ı değil — çubuk yerine uyarı çizilir`);
106
+ }
107
+ return problems;
108
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Motorun kendi register ettiği adımlar (`@ng-apps/flow` `flow-engine.ts` initialize) — iki
3
+ * platformda da koşar. `end` ve `query` hiçbir barındırıcıda yoktur (normalizasyon `end`'i atar).
4
+ */
5
+ export const ENGINE_STEP_TYPES = ['start', 'setter', 'code', 'condition', 'microservice', 'validation', 'flow-trigger', 'function'];
6
+ /**
7
+ * Eski adım tipi → koşan karşılığı. `navigate` ile `redirect`'in argümanı aynıdır; web `navigate`'i
8
+ * register etmez, yeni mobil runtime onu `redirect` takma adı olarak kaydeder — iki platformda da
9
+ * `redirect` kesin üstündür.
10
+ */
11
+ export const STEP_ALIASES = Object.freeze({ navigate: 'redirect' });
12
+ /** Tipli stil grupları iki platformda da çizilir (web CSS'e, mobil RN stiline derler). */
13
+ export const TYPED_STYLE_GROUPS = ['surfaceStyle', 'typographyStyle', 'sectionStyle', 'headerStyle', 'valueStyle'];
14
+ /** Bulguyu mevcut `layoutWarnings[]` satır biçimine çevirir (`PAGE-010 (uyarı) — …` kalıbı). */
15
+ export function findingLine(finding) {
16
+ const level = finding.severity === 'info' ? 'bilgi' : 'uyarı';
17
+ const target = finding.target ? ` [${finding.target}]` : '';
18
+ const guide = finding.guide ? ` (${finding.guide})` : '';
19
+ return `${finding.code} (${level})${target} — ${finding.message}${guide}`;
20
+ }
@@ -0,0 +1,122 @@
1
+ import { mobileProfile } from './mobile.js';
2
+ import { webProfile } from './web.js';
3
+ export const APPLICATION_TYPE_TTL_MS = 10 * 60_000;
4
+ const caches = new WeakMap();
5
+ function cacheOf(platform) {
6
+ let cache = caches.get(platform);
7
+ if (!cache) {
8
+ cache = new Map();
9
+ caches.set(platform, cache);
10
+ }
11
+ return cache;
12
+ }
13
+ /** Backend `ApplicationType` JSON'da açıklama metniyle ('web' | 'mobile') döner; eski sayısal biçim de okunur. */
14
+ export function readApplicationPlatform(value) {
15
+ if (value === 1 || (typeof value === 'string' && value.toLowerCase() === 'mobile'))
16
+ return 'mobile';
17
+ if (value === 0 || (typeof value === 'string' && value.toLowerCase() === 'web'))
18
+ return 'web';
19
+ return undefined;
20
+ }
21
+ export function profileFor(platform) {
22
+ return platform === 'mobile' ? mobileProfile : webProfile;
23
+ }
24
+ function factsOf(record) {
25
+ const platform = readApplicationPlatform(record.type);
26
+ const id = typeof record.id === 'string' ? record.id : undefined;
27
+ if (!platform || !id)
28
+ return undefined;
29
+ const homepage = record.homepage && typeof record.homepage === 'object' ? record.homepage.id : undefined;
30
+ const homepageId = typeof record.homepageId === 'string' ? record.homepageId : typeof homepage === 'string' ? homepage : undefined;
31
+ const properties = record.properties && typeof record.properties === 'object' && !Array.isArray(record.properties)
32
+ ? record.properties
33
+ : undefined;
34
+ return {
35
+ id,
36
+ platform,
37
+ ...(typeof record.slug === 'string' ? { slug: record.slug } : {}),
38
+ ...(typeof record.name === 'string' ? { name: record.name } : {}),
39
+ ...(typeof record.projectSlug === 'string' ? { projectSlug: record.projectSlug } : {}),
40
+ ...(homepageId ? { homepageId } : {}),
41
+ ...(properties ? { properties } : {})
42
+ };
43
+ }
44
+ function fresh(entry) {
45
+ return entry !== undefined && Date.now() - entry.at < APPLICATION_TYPE_TTL_MS;
46
+ }
47
+ export async function resolveApplication(platform, applicationId) {
48
+ const cache = cacheOf(platform);
49
+ const cached = cache.get(applicationId);
50
+ if (fresh(cached))
51
+ return cached.facts;
52
+ try {
53
+ const payload = await platform.org('GET', `/applications/${applicationId}`);
54
+ const facts = factsOf(((payload?.data ?? payload) ?? {}));
55
+ if (facts)
56
+ cache.set(applicationId, { facts, at: Date.now() });
57
+ return facts;
58
+ }
59
+ catch {
60
+ return undefined;
61
+ }
62
+ }
63
+ export async function resolveProfile(platform, applicationId) {
64
+ const application = await resolveApplication(platform, applicationId);
65
+ return application
66
+ ? { profile: profileFor(application.platform), known: true, application }
67
+ : { profile: webProfile, known: false };
68
+ }
69
+ const SLUG_LOOKUP_PAGE = 50;
70
+ /**
71
+ * `page_screenshot` ve `page_runtime_get` uygulamayı slug ile tanır. Org listesi `searchTerm` ile
72
+ * daraltılır ve slug + proje slug'ı BİREBİR eşleşen kayıt alınır (arama ILIKE'tır, `shop` `shop-2`'yi
73
+ * de getirir). B1'den beri liste tip filtresi verilmeyince iki tipi de döner. Aynı slug'ı çok proje
74
+ * kullanıyorsa (`mobile`, `admin`) aranan kayıt ilk sayfanın dışında kalabilir: sayfa doluysa ve
75
+ * eşleşme yoksa arama proje kimliğiyle tekrarlanır — (proje, slug) tekildir.
76
+ */
77
+ export async function resolveProfileBySlug(platform, projectSlug, applicationSlug) {
78
+ const cache = cacheOf(platform);
79
+ const key = `slug:${projectSlug}/${applicationSlug}`;
80
+ const cached = cache.get(key);
81
+ if (fresh(cached))
82
+ return { profile: profileFor(cached.facts.platform), known: true, application: cached.facts };
83
+ try {
84
+ const search = async (query) => {
85
+ const payload = await platform.org('GET', '/applications', { query: { searchTerm: applicationSlug, pageLength: SLUG_LOOKUP_PAGE, ...query } });
86
+ return Array.isArray(payload?.data) ? payload.data : [];
87
+ };
88
+ const matchIn = (rows) => rows.find(row => row.slug === applicationSlug && (row.projectSlug === undefined || row.projectSlug === projectSlug));
89
+ const rows = await search({});
90
+ let match = matchIn(rows);
91
+ if (!match && rows.length >= SLUG_LOOKUP_PAGE) {
92
+ const project = await platform.org('GET', `/p/${projectSlug}`);
93
+ const projectId = (project?.data ?? project)?.id;
94
+ if (typeof projectId === 'string')
95
+ match = matchIn(await search({ projectId }));
96
+ }
97
+ const facts = match ? factsOf(match) : undefined;
98
+ if (!facts)
99
+ return { profile: webProfile, known: false };
100
+ cache.set(key, { facts, at: Date.now() });
101
+ return { profile: profileFor(facts.platform), known: true, application: facts };
102
+ }
103
+ catch {
104
+ return { profile: webProfile, known: false };
105
+ }
106
+ }
107
+ /** Yazma araçları uygulamayı değiştirdiğinde (tip, slug, properties) kaydı düşürür. */
108
+ export function forgetApplication(platform, applicationId) {
109
+ const cache = cacheOf(platform);
110
+ if (applicationId === undefined) {
111
+ cache.clear();
112
+ return;
113
+ }
114
+ for (const [key, entry] of cache) {
115
+ if (key === applicationId || entry.facts.id === applicationId)
116
+ cache.delete(key);
117
+ }
118
+ }
119
+ /** Yanıta eklenen alan: tip biliniyorsa `applicationType`, bilinmiyorsa hiçbir şey. */
120
+ export function applicationTypeField(resolved) {
121
+ return resolved.known ? { applicationType: resolved.profile.platform } : {};
122
+ }
@@ -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) {
@@ -2389,7 +2387,7 @@ export const policyRules = [
2389
2387
  id: 'FLOW-023',
2390
2388
  module: 'flow',
2391
2389
  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",
2390
+ 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
2391
  check: definition => {
2394
2392
  const steps = flowAllSteps(definition);
2395
2393
  // 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;