@upsoftware_tech/svarium 1.0.7 → 1.0.9

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.
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <BlockFormLoginMethodVerification :verification-methods="verificationMethods" :session="session" />
2
+ <BlockFormLoginMethodVerification
3
+ :verification-methods="verificationMethods"
4
+ :session="session"
5
+ :type="type"
6
+ />
3
7
  </template>
4
8
 
5
9
  <script setup lang="ts">
@@ -9,11 +13,13 @@ import { AuthLayout, BlockFormLoginMethodVerification } from '@upsoftware_tech/s
9
13
  interface Props {
10
14
  verificationMethods?: VerificationMethod[] | Record<string, VerificationMethod>;
11
15
  session: string;
16
+ type?: string;
12
17
  }
13
18
 
14
19
  withDefaults(defineProps<Props>(), {
15
20
  verificationMethods: () => [],
16
21
  session: '',
22
+ type: 'login',
17
23
  });
18
24
 
19
25
  defineOptions({
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <component :is="resolvedLayout">
3
+ <BlockPageRegister v-bind="props" />
4
+ </component>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue';
9
+ import { AuthLayout, BlockPageRegister, PanelLayout } from '@upsoftware_tech/svarium';
10
+
11
+ interface RegisterFieldOption {
12
+ value: string | number | boolean;
13
+ label: string;
14
+ }
15
+
16
+ interface RegisterField {
17
+ name: string;
18
+ type?: string;
19
+ label?: string;
20
+ placeholder?: string;
21
+ hint?: string;
22
+ required?: boolean;
23
+ autocomplete?: string;
24
+ value?: string | number | boolean;
25
+ options?: RegisterFieldOption[];
26
+ }
27
+
28
+ interface Props {
29
+ title?: string;
30
+ subtitle?: string;
31
+ loginLabel?: string;
32
+ loginLinkLabel?: string;
33
+ loginLink?: string;
34
+ submitLabel?: string;
35
+ action?: string;
36
+ layout?: string;
37
+ schema?: Record<string, any>[];
38
+ fields?: RegisterField[];
39
+ }
40
+
41
+ const props = withDefaults(defineProps<Props>(), {
42
+ title: 'Create account',
43
+ subtitle: 'Fill in the form to create your account',
44
+ loginLabel: 'Already have an account?',
45
+ loginLinkLabel: 'Sign in',
46
+ loginLink: 'panel.auth.login',
47
+ submitLabel: 'Create account',
48
+ action: 'panel.auth.register.set',
49
+ layout: 'AuthLayout',
50
+ fields: () => [
51
+ { name: 'email', type: 'email', label: 'Email address', required: true, autocomplete: 'email' },
52
+ { name: 'password', type: 'password', label: 'Password', required: true, autocomplete: 'new-password' },
53
+ {
54
+ name: 'password_confirmation',
55
+ type: 'password',
56
+ label: 'Confirm password',
57
+ required: true,
58
+ autocomplete: 'new-password',
59
+ },
60
+ ],
61
+ });
62
+
63
+ const resolvedLayout = computed(() => {
64
+ const key = (props.layout ?? 'AuthLayout').trim();
65
+
66
+ if (key === 'PanelLayout') {
67
+ return PanelLayout;
68
+ }
69
+
70
+ if (key === 'AuthLayout') {
71
+ return AuthLayout;
72
+ }
73
+
74
+ return key || AuthLayout;
75
+ });
76
+ </script>
@@ -1,6 +1,10 @@
1
1
  <template>
2
+ <Head>
3
+ <link v-if="seo?.canonical" rel="canonical" :href="seo.canonical" />
4
+ <meta v-if="seo?.robots" name="robots" :content="seo.robots" />
5
+ </Head>
2
6
  <pre class="hidden">{{ tree }}</pre>
3
- <main class="flex bg-slate-100 dark:bg-slate-950 h-screen">
7
+ <main class="bg-slate-100 dark:bg-slate-950">
4
8
  <template v-if="Array.isArray(tree)">
5
9
  <component
6
10
  :is="renderNode(node)"
@@ -17,7 +21,7 @@
17
21
  </template>
18
22
 
19
23
  <script setup lang="ts">
20
- import { usePage } from '@inertiajs/vue3';
24
+ import { Head, usePage } from '@inertiajs/vue3';
21
25
  import { computed, h, isRef, ref, resolveComponent, watch } from 'vue';
22
26
  import { Toaster } from 'vue-sonner';
23
27
  import { toast } from 'vue-sonner';
@@ -64,11 +68,44 @@ function updateModelValue(modelContext: any, value: any): void {
64
68
  emit('update:modelValue', value);
65
69
  }
66
70
 
71
+ function shouldRenderVueIf(value: any): boolean {
72
+ if (typeof value === 'boolean') {
73
+ return value;
74
+ }
75
+
76
+ if (typeof value === 'number') {
77
+ return value !== 0;
78
+ }
79
+
80
+ if (typeof value === 'string') {
81
+ const normalized = value.trim().toLowerCase();
82
+
83
+ if (normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined') {
84
+ return false;
85
+ }
86
+
87
+ return true;
88
+ }
89
+
90
+ if (value === null || value === undefined) {
91
+ return false;
92
+ }
93
+
94
+ return Boolean(value);
95
+ }
96
+
67
97
  function renderNode(node: any, modelContext?: any) {
68
98
  if (!node || typeof node !== 'object') {
69
99
  return null;
70
100
  }
71
101
 
102
+ const rawNodeProps = node.props && typeof node.props === 'object' ? node.props : {};
103
+ if (Object.prototype.hasOwnProperty.call(rawNodeProps, 'vIf')) {
104
+ if (!shouldRenderVueIf(rawNodeProps.vIf)) {
105
+ return null;
106
+ }
107
+ }
108
+
72
109
  const componentName = node.component ?? node.type;
73
110
 
74
111
  if (!componentName) {
@@ -78,10 +115,29 @@ function renderNode(node: any, modelContext?: any) {
78
115
 
79
116
  const component = resolveComponent(componentName);
80
117
  const slots: Record<string, (...args: any[]) => any> = {};
81
-
82
- if (Array.isArray(node.children) && node.children.length) {
83
- slots.default = () => node.children.map((child: any) => renderNode(child, modelContext));
84
- }
118
+ const nodeProps = { ...(node.props ?? {}) };
119
+ delete nodeProps.vIf;
120
+ const slotPlacementRaw =
121
+ nodeProps.__slotPlacement && typeof nodeProps.__slotPlacement === 'object'
122
+ ? nodeProps.__slotPlacement
123
+ : {};
124
+
125
+ delete nodeProps.__slotPlacement;
126
+
127
+ const resolveSlotContext = (slotProps: any = {}, fallbackModelContext: any = modelContext) => {
128
+ const hasSlotModel =
129
+ Object.prototype.hasOwnProperty.call(slotProps, 'modelValue') ||
130
+ Object.prototype.hasOwnProperty.call(slotProps, 'tab');
131
+
132
+ return hasSlotModel || typeof slotProps?.updateModelValue === 'function'
133
+ ? {
134
+ value: slotProps?.modelValue ?? slotProps?.tab ?? resolveModelValue(fallbackModelContext),
135
+ update: slotProps?.updateModelValue,
136
+ }
137
+ : (slotProps?.model ?? fallbackModelContext);
138
+ };
139
+
140
+ const slotContentMap: Record<string, any[]> = {};
85
141
 
86
142
  if (node.slots && !Array.isArray(node.slots)) {
87
143
  for (const [name, content] of Object.entries(node.slots)) {
@@ -89,28 +145,180 @@ function renderNode(node: any, modelContext?: any) {
89
145
  continue;
90
146
  }
91
147
 
148
+ slotContentMap[name] = content;
92
149
  slots[name] = (slotProps: any = {}) => {
93
- const hasSlotModel =
94
- Object.prototype.hasOwnProperty.call(slotProps, 'modelValue') ||
95
- Object.prototype.hasOwnProperty.call(slotProps, 'tab');
96
-
97
- const slotModelContext =
98
- hasSlotModel || typeof slotProps?.updateModelValue === 'function'
99
- ? {
100
- value: slotProps?.modelValue ?? slotProps?.tab ?? resolveModelValue(modelContext),
101
- update: slotProps?.updateModelValue,
102
- }
103
- : (slotProps?.model ?? modelContext);
104
-
150
+ const slotModelContext = resolveSlotContext(slotProps);
105
151
  return content.map((child: any) => renderNode(child, slotModelContext));
106
152
  };
107
153
  }
108
154
  }
109
155
 
156
+ const renderDefaultChildren = (slotProps: any = {}) => {
157
+ if (!Array.isArray(node.children) || node.children.length === 0) {
158
+ return [];
159
+ }
160
+
161
+ const slotModelContext = resolveSlotContext(slotProps);
162
+ return node.children.map((child: any) => renderNode(child, slotModelContext));
163
+ };
164
+
165
+ type PlacedSlot = {
166
+ slotName: string;
167
+ anchor: 'header' | 'footer';
168
+ position: 'before' | 'after';
169
+ priority: number | null;
170
+ order: number;
171
+ };
172
+
173
+ const placedSlots: PlacedSlot[] = [];
174
+ let placementFallbackOrder = 0;
175
+
176
+ for (const [slotName, config] of Object.entries(slotPlacementRaw as Record<string, any>)) {
177
+ if (!Array.isArray(slotContentMap[slotName])) {
178
+ continue;
179
+ }
180
+
181
+ const anchor = String(config?.anchor ?? 'header').toLowerCase();
182
+ const position = String(config?.position ?? 'after').toLowerCase();
183
+ const normalizedAnchor: 'header' | 'footer' = anchor === 'footer' ? 'footer' : 'header';
184
+ const normalizedPosition: 'before' | 'after' = position === 'before' ? 'before' : 'after';
185
+
186
+ let normalizedPriority: number | null = null;
187
+ const rawPriority = config?.priority;
188
+ if (typeof rawPriority === 'number' && Number.isFinite(rawPriority)) {
189
+ normalizedPriority = rawPriority;
190
+ } else if (typeof rawPriority === 'string' && rawPriority.trim() !== '' && Number.isFinite(Number(rawPriority))) {
191
+ normalizedPriority = Number(rawPriority);
192
+ }
193
+
194
+ const rawOrder = config?.order;
195
+ let normalizedOrder = placementFallbackOrder;
196
+ if (typeof rawOrder === 'number' && Number.isFinite(rawOrder)) {
197
+ normalizedOrder = rawOrder;
198
+ } else if (typeof rawOrder === 'string' && rawOrder.trim() !== '' && Number.isFinite(Number(rawOrder))) {
199
+ normalizedOrder = Number(rawOrder);
200
+ }
201
+ placementFallbackOrder += 1;
202
+
203
+ placedSlots.push({
204
+ slotName,
205
+ anchor: normalizedAnchor,
206
+ position: normalizedPosition,
207
+ priority: normalizedPriority,
208
+ order: normalizedOrder,
209
+ });
210
+ }
211
+
212
+ const sortPlacedSlots = (items: PlacedSlot[]): string[] => {
213
+ return [...items]
214
+ .sort((a, b) => {
215
+ const aHasPriority = a.priority !== null;
216
+ const bHasPriority = b.priority !== null;
217
+
218
+ if (aHasPriority && bHasPriority) {
219
+ if (a.priority !== b.priority) {
220
+ return (a.priority as number) - (b.priority as number);
221
+ }
222
+
223
+ return a.order - b.order;
224
+ }
225
+
226
+ if (aHasPriority !== bHasPriority) {
227
+ return aHasPriority ? -1 : 1;
228
+ }
229
+
230
+ return a.order - b.order;
231
+ })
232
+ .map((item) => item.slotName);
233
+ };
234
+
235
+ const beforeHeader = sortPlacedSlots(
236
+ placedSlots.filter((item) => item.anchor === 'header' && item.position === 'before'),
237
+ );
238
+ const afterHeader = sortPlacedSlots(
239
+ placedSlots.filter((item) => item.anchor === 'header' && item.position === 'after'),
240
+ );
241
+ const beforeFooter = sortPlacedSlots(
242
+ placedSlots.filter((item) => item.anchor === 'footer' && item.position === 'before'),
243
+ );
244
+ const afterFooter = sortPlacedSlots(
245
+ placedSlots.filter((item) => item.anchor === 'footer' && item.position === 'after'),
246
+ );
247
+
248
+ const placedSlotNames = new Set(placedSlots.map((item) => item.slotName));
249
+
250
+ if (placedSlotNames.size > 0) {
251
+ for (const slotName of placedSlotNames) {
252
+ delete slots[slotName];
253
+ }
254
+ }
255
+
256
+ const renderPlacedSlotNames = (slotNames: string[], slotProps: any = {}) => {
257
+ const slotModelContext = resolveSlotContext(slotProps);
258
+ const rendered: any[] = [];
259
+
260
+ for (const slotName of slotNames) {
261
+ const content = slotContentMap[slotName];
262
+ if (!Array.isArray(content) || content.length === 0) {
263
+ continue;
264
+ }
265
+
266
+ rendered.push(...content.map((child: any) => renderNode(child, slotModelContext)));
267
+ }
268
+
269
+ return rendered;
270
+ };
271
+
272
+ const hasMainHeader = !placedSlotNames.has('header') && Array.isArray(slotContentMap.header) && slotContentMap.header.length > 0;
273
+ const hasMainFooter = !placedSlotNames.has('footer') && Array.isArray(slotContentMap.footer) && slotContentMap.footer.length > 0;
274
+ const hasMainBody = !placedSlotNames.has('body') && Array.isArray(slotContentMap.body) && slotContentMap.body.length > 0;
275
+
276
+ const headerSlotOrder = [...beforeHeader, ...(hasMainHeader ? ['header'] : []), ...afterHeader];
277
+ const footerSlotOrder = [...beforeFooter, ...(hasMainFooter ? ['footer'] : []), ...afterFooter];
278
+
279
+ const hasStructuredSlots =
280
+ placedSlots.length > 0 || hasMainHeader || hasMainFooter || hasMainBody;
281
+
282
+ if (hasStructuredSlots) {
283
+ slots.header = (slotProps: any = {}) => renderPlacedSlotNames(headerSlotOrder, slotProps);
284
+ slots.footer = (slotProps: any = {}) => renderPlacedSlotNames(footerSlotOrder, slotProps);
285
+ slots.body = (slotProps: any = {}) => {
286
+ if (hasMainBody) {
287
+ return renderPlacedSlotNames(['body'], slotProps);
288
+ }
289
+
290
+ return renderDefaultChildren(slotProps);
291
+ };
292
+ if (placedSlots.length > 0) {
293
+ slots.default = (slotProps: any = {}) => {
294
+ const rendered: any[] = [];
295
+
296
+ rendered.push(...renderPlacedSlotNames(headerSlotOrder, slotProps));
297
+ if (hasMainBody) {
298
+ rendered.push(...renderPlacedSlotNames(['body'], slotProps));
299
+ } else {
300
+ rendered.push(...renderDefaultChildren(slotProps));
301
+ }
302
+ rendered.push(...renderPlacedSlotNames(footerSlotOrder, slotProps));
303
+
304
+ return rendered;
305
+ };
306
+ } else if (Array.isArray(node.children) && node.children.length) {
307
+ slots.default = (slotProps: any = {}) => renderDefaultChildren(slotProps);
308
+ }
309
+ } else if (Array.isArray(node.children) && node.children.length) {
310
+ slots.default = (slotProps: any = {}) => renderDefaultChildren(slotProps);
311
+ }
312
+
313
+ const normalizedProps = normalizeNodeProps(nodeProps);
314
+ if (Object.prototype.hasOwnProperty.call(normalizedProps, 'appearance')) {
315
+ delete normalizedProps.appearance;
316
+ }
317
+
110
318
  return h(
111
319
  component,
112
320
  {
113
- ...normalizeNodeProps(node.props ?? {}),
321
+ ...normalizedProps,
114
322
  modelValue: resolveModelValue(modelContext),
115
323
  'onUpdate:modelValue': (value: any) => {
116
324
  updateModelValue(modelContext, value);
@@ -130,6 +338,10 @@ type FlashMessages = {
130
338
 
131
339
  const page = usePage();
132
340
  const lastFlashFingerprint = ref('');
341
+ const seo = computed(() => {
342
+ const currentPage = page as unknown as { props?: { seo?: Record<string, any> } };
343
+ return currentPage.props?.seo ?? {};
344
+ });
133
345
 
134
346
  const flash = computed<FlashMessages>(() => {
135
347
  const currentPage = page as unknown as { flash?: unknown; props?: { flash?: unknown } };