includio-cms 0.26.0 → 0.27.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/API.md +42 -2
- package/CHANGELOG.md +65 -0
- package/DOCS.md +1 -1
- package/ROADMAP.md +8 -0
- package/dist/admin/auth-client.d.ts +42 -42
- package/dist/admin/client/admin/admin-layout.svelte +12 -2
- package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -1
- package/dist/admin/client/collection/data-table.svelte +0 -39
- package/dist/admin/client/collection/data-table.svelte.d.ts +0 -2
- package/dist/admin/client/shop/coupon-schema.d.ts +1 -1
- package/dist/admin/client/shop/refund-dialog.svelte +37 -1
- package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte +107 -0
- package/dist/admin/components/fields/field-renderer.svelte +6 -1
- package/dist/admin/components/fields/icon-field.svelte +86 -0
- package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
- package/dist/admin/components/fields/object-field.svelte +27 -7
- package/dist/admin/components/fields/shop-field.svelte +210 -20
- package/dist/admin/components/layout/layout-tabs.svelte +1 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte +109 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
- package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
- package/dist/admin/helpers/build-icon-set-map.js +16 -0
- package/dist/admin/helpers/index.d.ts +2 -0
- package/dist/admin/helpers/index.js +2 -0
- package/dist/admin/remote/shop.remote.d.ts +58 -24
- package/dist/admin/remote/shop.remote.js +61 -6
- package/dist/admin/state/icon-sets.svelte.d.ts +9 -0
- package/dist/admin/state/icon-sets.svelte.js +20 -0
- package/dist/cli/scaffold/admin.js +2 -2
- package/dist/components/ui/checkbox/checkbox.svelte +3 -3
- package/dist/core/cms.d.ts +11 -2
- package/dist/core/cms.js +29 -0
- package/dist/core/fields/fieldSchemaToTs.js +7 -0
- package/dist/core/server/generator/fields.d.ts +2 -0
- package/dist/core/server/generator/fields.js +34 -1
- package/dist/core/server/generator/generator.js +2 -1
- package/dist/db-postgres/schema/shop/order.d.ts +37 -1
- package/dist/db-postgres/schema/shop/order.js +3 -1
- package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
- package/dist/db-postgres/schema/shop/payment.js +4 -1
- package/dist/db-postgres/schema/shop/product.d.ts +20 -0
- package/dist/db-postgres/schema/shop/product.js +3 -1
- package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
- package/dist/db-postgres/schema/shop/productVariant.js +22 -0
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/shop/cart/types.d.ts +1 -0
- package/dist/shop/client/index.d.ts +54 -0
- package/dist/shop/client/index.js +5 -1
- package/dist/shop/expiry.d.ts +35 -0
- package/dist/shop/expiry.js +68 -0
- package/dist/shop/http/balance-handler.d.ts +20 -0
- package/dist/shop/http/balance-handler.js +91 -0
- package/dist/shop/http/cart-handler.js +19 -0
- package/dist/shop/http/checkout-handler.js +19 -1
- package/dist/shop/http/index.d.ts +2 -0
- package/dist/shop/http/index.js +2 -0
- package/dist/shop/http/upcoming-handler.d.ts +16 -0
- package/dist/shop/http/upcoming-handler.js +65 -0
- package/dist/shop/http/webhook-handler.js +46 -9
- package/dist/shop/index.d.ts +4 -1
- package/dist/shop/index.js +7 -1
- package/dist/shop/server/balance-payment.d.ts +40 -0
- package/dist/shop/server/balance-payment.js +140 -0
- package/dist/shop/server/cart-hydrate.js +2 -0
- package/dist/shop/server/init.d.ts +14 -0
- package/dist/shop/server/init.js +35 -0
- package/dist/shop/server/orders.d.ts +34 -0
- package/dist/shop/server/orders.js +141 -2
- package/dist/shop/server/payment-policy.d.ts +35 -0
- package/dist/shop/server/payment-policy.js +55 -0
- package/dist/shop/server/payments.d.ts +29 -0
- package/dist/shop/server/payments.js +64 -0
- package/dist/shop/server/populate.d.ts +1 -1
- package/dist/shop/server/refund.d.ts +17 -12
- package/dist/shop/server/refund.js +96 -13
- package/dist/shop/server/shop-data.d.ts +4 -1
- package/dist/shop/server/shop-data.js +24 -2
- package/dist/shop/template.d.ts +13 -0
- package/dist/shop/template.js +98 -0
- package/dist/shop/types.d.ts +142 -1
- package/dist/shop/variant-attributes.d.ts +28 -0
- package/dist/shop/variant-attributes.js +69 -0
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +2 -0
- package/dist/types/cms.d.ts +4 -3
- package/dist/types/cms.schema.d.ts +1 -1
- package/dist/types/cms.schema.js +9 -0
- package/dist/types/fields.d.ts +21 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/plugins.d.ts +40 -0
- package/dist/types/plugins.js +4 -1
- package/dist/updates/0.26.1/index.d.ts +2 -0
- package/dist/updates/0.26.1/index.js +19 -0
- package/dist/updates/0.27.0/index.d.ts +2 -0
- package/dist/updates/0.27.0/index.js +50 -0
- package/dist/updates/index.js +5 -1
- package/package.json +1 -1
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
|
@@ -2,7 +2,10 @@ import { asc, eq, inArray } from 'drizzle-orm';
|
|
|
2
2
|
import { shopProductsTable, shopProductVariantsTable } from '../../db-postgres/schema/shop/index.js';
|
|
3
3
|
import { entriesTable } from '../../db-postgres/schema/entry.js';
|
|
4
4
|
import { entryVersionsTable } from '../../db-postgres/schema/entryVersion.js';
|
|
5
|
+
import { getCMS } from '../../core/cms.js';
|
|
5
6
|
import { getShopDb } from './db.js';
|
|
7
|
+
import { validateVariantAttributes } from '../variant-attributes.js';
|
|
8
|
+
import { validatePaymentPolicy } from './payment-policy.js';
|
|
6
9
|
const MAX_PLN = 1e9;
|
|
7
10
|
function validateShopData(input) {
|
|
8
11
|
if (!Number.isFinite(input.basePrice) || input.basePrice < 0 || input.basePrice > MAX_PLN) {
|
|
@@ -11,6 +14,8 @@ function validateShopData(input) {
|
|
|
11
14
|
if (!Number.isInteger(input.vatRate) || input.vatRate < 0 || input.vatRate > 100) {
|
|
12
15
|
throw new Error('vatRate must be an integer between 0 and 100.');
|
|
13
16
|
}
|
|
17
|
+
if (input.paymentPolicy != null)
|
|
18
|
+
validatePaymentPolicy(input.paymentPolicy);
|
|
14
19
|
}
|
|
15
20
|
function mapShopRow(r) {
|
|
16
21
|
return { ...r, basePrice: Number(r.basePrice) };
|
|
@@ -40,13 +45,18 @@ export async function upsertShopData(entryId, input, variants) {
|
|
|
40
45
|
let productId;
|
|
41
46
|
const basePriceSql = String(input.basePrice);
|
|
42
47
|
if (existing) {
|
|
48
|
+
// `paymentPolicy === undefined` (caller didn't include the key) preserves
|
|
49
|
+
// the existing policy — admin clients that don't surface the field
|
|
50
|
+
// must not silently wipe it. Explicit `null` clears.
|
|
51
|
+
const nextPolicy = input.paymentPolicy === undefined ? existing.paymentPolicy : input.paymentPolicy;
|
|
43
52
|
const [updated] = await db
|
|
44
53
|
.update(shopProductsTable)
|
|
45
54
|
.set({
|
|
46
55
|
basePrice: basePriceSql,
|
|
47
56
|
vatRate: input.vatRate,
|
|
48
57
|
isActive: input.isActive ?? existing.isActive,
|
|
49
|
-
sortOrder: input.sortOrder ?? existing.sortOrder,
|
|
58
|
+
sortOrder: input.sortOrder ?? existing.sortOrder ?? null,
|
|
59
|
+
paymentPolicy: nextPolicy,
|
|
50
60
|
updatedAt: new Date()
|
|
51
61
|
})
|
|
52
62
|
.where(eq(shopProductsTable.entryId, entryId))
|
|
@@ -61,12 +71,24 @@ export async function upsertShopData(entryId, input, variants) {
|
|
|
61
71
|
basePrice: basePriceSql,
|
|
62
72
|
vatRate: input.vatRate,
|
|
63
73
|
isActive: input.isActive ?? true,
|
|
64
|
-
sortOrder: input.sortOrder
|
|
74
|
+
sortOrder: input.sortOrder ?? null,
|
|
75
|
+
paymentPolicy: input.paymentPolicy ?? null
|
|
65
76
|
})
|
|
66
77
|
.returning();
|
|
67
78
|
productId = created.id;
|
|
68
79
|
}
|
|
69
80
|
if (variants !== undefined) {
|
|
81
|
+
// Validate against the schema declared in defineShop({ variantAttributes }).
|
|
82
|
+
// Missing/extra keys, type mismatches → InvalidVariantAttributesError
|
|
83
|
+
// (code INVALID_VARIANT_ATTRIBUTES). Skipped if no shop config or empty
|
|
84
|
+
// schema (legacy untyped behavior preserved).
|
|
85
|
+
const shopConfig = getCMS().shopConfig;
|
|
86
|
+
const attrSchema = shopConfig?.variantAttributes ?? {};
|
|
87
|
+
if (Object.keys(attrSchema).length > 0) {
|
|
88
|
+
for (const v of variants) {
|
|
89
|
+
validateVariantAttributes(v.attributes, attrSchema);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
70
92
|
const submittedIds = new Set(variants.filter((v) => v.id).map((v) => v.id));
|
|
71
93
|
const currentVariants = existing?.variants ?? [];
|
|
72
94
|
for (const v of currentVariants) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String interpolation engine used by `defineShop({ variantLabel.template })`
|
|
3
|
+
* to render variant names from typed `variantAttributes`.
|
|
4
|
+
*
|
|
5
|
+
* Syntax: `{key}` or `{key|filter}` or `{key|filter:arg}`.
|
|
6
|
+
* Supported filters: `date` (long|medium|short), `currency` (PLN by default),
|
|
7
|
+
* `uppercase`. Unknown filter → value passes through unchanged. Unknown key
|
|
8
|
+
* → empty string + `console.warn` in dev. Malformed template (unclosed brace)
|
|
9
|
+
* → raw template + warn.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export declare function interpolateTemplate(template: string, vars: Record<string, unknown>, locale: string): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String interpolation engine used by `defineShop({ variantLabel.template })`
|
|
3
|
+
* to render variant names from typed `variantAttributes`.
|
|
4
|
+
*
|
|
5
|
+
* Syntax: `{key}` or `{key|filter}` or `{key|filter:arg}`.
|
|
6
|
+
* Supported filters: `date` (long|medium|short), `currency` (PLN by default),
|
|
7
|
+
* `uppercase`. Unknown filter → value passes through unchanged. Unknown key
|
|
8
|
+
* → empty string + `console.warn` in dev. Malformed template (unclosed brace)
|
|
9
|
+
* → raw template + warn.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export function interpolateTemplate(template, vars, locale) {
|
|
14
|
+
if (!hasBalancedBraces(template)) {
|
|
15
|
+
console.warn(`[interpolateTemplate] Malformed template (unclosed brace): ${template}`);
|
|
16
|
+
return template;
|
|
17
|
+
}
|
|
18
|
+
return template.replace(/\{([^}]+)\}/g, (_match, body) => {
|
|
19
|
+
const { key, filter, arg } = parsePlaceholder(body);
|
|
20
|
+
if (!(key in vars)) {
|
|
21
|
+
console.warn(`[interpolateTemplate] Unknown key "${key}" in template: ${template}`);
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
const raw = vars[key];
|
|
25
|
+
if (raw === null || raw === undefined)
|
|
26
|
+
return '';
|
|
27
|
+
if (!filter)
|
|
28
|
+
return String(raw);
|
|
29
|
+
return applyFilter(raw, filter, arg, locale);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** @internal */
|
|
33
|
+
function parsePlaceholder(body) {
|
|
34
|
+
const [keyPart, filterPart] = body.split('|', 2);
|
|
35
|
+
const key = keyPart.trim();
|
|
36
|
+
if (!filterPart)
|
|
37
|
+
return { key, filter: null, arg: null };
|
|
38
|
+
const [filterName, ...argParts] = filterPart.split(':');
|
|
39
|
+
return {
|
|
40
|
+
key,
|
|
41
|
+
filter: filterName.trim(),
|
|
42
|
+
arg: argParts.length > 0 ? argParts.join(':').trim() : null
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** @internal */
|
|
46
|
+
function applyFilter(value, filter, arg, locale) {
|
|
47
|
+
switch (filter) {
|
|
48
|
+
case 'date':
|
|
49
|
+
return formatDate(value, arg ?? 'medium', locale);
|
|
50
|
+
case 'currency':
|
|
51
|
+
return formatCurrency(value, arg ?? 'PLN', locale);
|
|
52
|
+
case 'uppercase':
|
|
53
|
+
return String(value).toUpperCase();
|
|
54
|
+
default:
|
|
55
|
+
return String(value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** @internal */
|
|
59
|
+
function formatDate(value, style, locale) {
|
|
60
|
+
const date = new Date(String(value));
|
|
61
|
+
if (Number.isNaN(date.getTime()))
|
|
62
|
+
return String(value);
|
|
63
|
+
const dateStyle = ['long', 'medium', 'short'].includes(style)
|
|
64
|
+
? style
|
|
65
|
+
: 'medium';
|
|
66
|
+
try {
|
|
67
|
+
return new Intl.DateTimeFormat(locale, { dateStyle }).format(date);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return String(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** @internal */
|
|
74
|
+
function formatCurrency(value, currency, locale) {
|
|
75
|
+
const num = typeof value === 'number' ? value : Number(value);
|
|
76
|
+
if (!Number.isFinite(num))
|
|
77
|
+
return String(value);
|
|
78
|
+
try {
|
|
79
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(num);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return String(value);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** @internal */
|
|
86
|
+
function hasBalancedBraces(template) {
|
|
87
|
+
let depth = 0;
|
|
88
|
+
for (const ch of template) {
|
|
89
|
+
if (ch === '{')
|
|
90
|
+
depth++;
|
|
91
|
+
else if (ch === '}') {
|
|
92
|
+
depth--;
|
|
93
|
+
if (depth < 0)
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return depth === 0;
|
|
98
|
+
}
|
package/dist/shop/types.d.ts
CHANGED
|
@@ -147,6 +147,124 @@ export interface CouponRef {
|
|
|
147
147
|
value: number;
|
|
148
148
|
discountAmount: number;
|
|
149
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Schema descriptor for one product-variant attribute (city, startsAt, …).
|
|
152
|
+
* Drives Zod validation, ts-gen typings, GIN indexes, and admin renderers.
|
|
153
|
+
* @public
|
|
154
|
+
*/
|
|
155
|
+
export type VariantAttribute = VariantAttributeText | VariantAttributeNumber | VariantAttributeDatetime | VariantAttributeSelect | VariantAttributeBoolean | VariantAttributeImage | VariantAttributeEntry | VariantAttributeSlug;
|
|
156
|
+
interface VariantAttributeBase {
|
|
157
|
+
label: I18nText;
|
|
158
|
+
required?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Generate a `GIN ((attributes->'<key>'))` index on the variants table.
|
|
161
|
+
* Enable for keys filtered on storefront (`city`, `startsAt`, …).
|
|
162
|
+
*/
|
|
163
|
+
indexable?: boolean;
|
|
164
|
+
}
|
|
165
|
+
/** @public */
|
|
166
|
+
export interface VariantAttributeText extends VariantAttributeBase {
|
|
167
|
+
type: 'text';
|
|
168
|
+
}
|
|
169
|
+
/** @public */
|
|
170
|
+
export interface VariantAttributeNumber extends VariantAttributeBase {
|
|
171
|
+
type: 'number';
|
|
172
|
+
}
|
|
173
|
+
/** @public — value stored as ISO-8601 string. */
|
|
174
|
+
export interface VariantAttributeDatetime extends VariantAttributeBase {
|
|
175
|
+
type: 'datetime';
|
|
176
|
+
}
|
|
177
|
+
/** @public */
|
|
178
|
+
export interface VariantAttributeSelect extends VariantAttributeBase {
|
|
179
|
+
type: 'select';
|
|
180
|
+
options: {
|
|
181
|
+
value: string;
|
|
182
|
+
label: I18nText;
|
|
183
|
+
}[];
|
|
184
|
+
}
|
|
185
|
+
/** @public */
|
|
186
|
+
export interface VariantAttributeBoolean extends VariantAttributeBase {
|
|
187
|
+
type: 'boolean';
|
|
188
|
+
}
|
|
189
|
+
/** @public — value = media file id (UUID). */
|
|
190
|
+
export interface VariantAttributeImage extends VariantAttributeBase {
|
|
191
|
+
type: 'image';
|
|
192
|
+
}
|
|
193
|
+
/** @public — value = entry id (UUID) from given collection. */
|
|
194
|
+
export interface VariantAttributeEntry extends VariantAttributeBase {
|
|
195
|
+
type: 'entry';
|
|
196
|
+
entryCollection: string;
|
|
197
|
+
}
|
|
198
|
+
/** @public — value matches slug regex `^[a-z0-9-]+$`. */
|
|
199
|
+
export interface VariantAttributeSlug extends VariantAttributeBase {
|
|
200
|
+
type: 'slug';
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Template used to auto-generate `variant.name` from `variantAttributes`.
|
|
204
|
+
* Syntax: `{key|filter:arg}`. Filters: `date` (long|medium|short),
|
|
205
|
+
* `currency` (currency code), `uppercase`. Admin pre-fills the name input
|
|
206
|
+
* with the interpolated string; once the editor types into the name field,
|
|
207
|
+
* pre-fill stops for that variant (per-session dirty flag).
|
|
208
|
+
* @public
|
|
209
|
+
*/
|
|
210
|
+
export interface VariantLabelConfig {
|
|
211
|
+
template: string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Per-product payment policy. `full` (default) charges the full order total
|
|
215
|
+
* immediately; `deposit` charges only a deposit at checkout and leaves a
|
|
216
|
+
* remaining balance owed by the customer, redeemable via a signed balance
|
|
217
|
+
* link sent by the admin. Set on `ShopFieldData.paymentPolicy` per product
|
|
218
|
+
* entry — not globally on `defineShop`.
|
|
219
|
+
* @public
|
|
220
|
+
*/
|
|
221
|
+
export type PaymentPolicy = {
|
|
222
|
+
type: 'full';
|
|
223
|
+
} | {
|
|
224
|
+
type: 'deposit';
|
|
225
|
+
depositAmount: DepositAmount;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Deposit amount specifier. `percent` charges `floor(base * value / 100)` of
|
|
229
|
+
* the line total; `amount` charges a fixed minor-unit value (clamped to the
|
|
230
|
+
* line total). Validation: `value > 0` always; `percent` ≤ 100; `amount` in
|
|
231
|
+
* the same minor-unit as `order.totalGross` (grosze for PLN).
|
|
232
|
+
* @public
|
|
233
|
+
*/
|
|
234
|
+
export type DepositAmount = {
|
|
235
|
+
type: 'percent';
|
|
236
|
+
value: number;
|
|
237
|
+
} | {
|
|
238
|
+
type: 'amount';
|
|
239
|
+
value: number;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Persisted partial-payment summary on `order.partialPayment` when the order
|
|
243
|
+
* was placed under a deposit policy. Amounts are in minor currency units
|
|
244
|
+
* (grosze for PLN). `paidAt` is the ISO timestamp when the deposit cleared
|
|
245
|
+
* (or `null` while still pending). On balance payment, `paidAmount` is
|
|
246
|
+
* bumped to the full order total and `paidAt` is refreshed.
|
|
247
|
+
* @public
|
|
248
|
+
*/
|
|
249
|
+
export interface PartialPayment {
|
|
250
|
+
kind: 'deposit';
|
|
251
|
+
paidAmount: number;
|
|
252
|
+
balanceAmount: number;
|
|
253
|
+
paidAt: string | null;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Opt-in storefront filter that hides variants whose datetime attribute
|
|
257
|
+
* (`source`, e.g. `startsAt`) has already passed. `offsetDays` shifts the
|
|
258
|
+
* cut-off: `0` = expire exactly when the source datetime hits now, `1` = one
|
|
259
|
+
* day after (grace period), `-1` = one day before. Admin still shows expired
|
|
260
|
+
* variants with a "Zakończony" badge; storefront `listUpcoming` filters them
|
|
261
|
+
* out and cart/checkout reject them with `VARIANT_EXPIRED`.
|
|
262
|
+
* @public
|
|
263
|
+
*/
|
|
264
|
+
export interface VariantExpiryConfig {
|
|
265
|
+
source: string;
|
|
266
|
+
offsetDays: number;
|
|
267
|
+
}
|
|
150
268
|
export interface ShopConfig {
|
|
151
269
|
currency: Currency;
|
|
152
270
|
vatRates: number[];
|
|
@@ -168,11 +286,34 @@ export interface ShopConfig {
|
|
|
168
286
|
* automatic admin notifications. Omitted = those alerts are no-ops.
|
|
169
287
|
*/
|
|
170
288
|
adminEmail?: string;
|
|
289
|
+
/**
|
|
290
|
+
* Typed variant attribute schema. Each key declares one attribute (city,
|
|
291
|
+
* startsAt, …) — drives Zod validation, ts-gen typings, GIN indexes, and
|
|
292
|
+
* admin variant form renderer. Omitted = legacy untyped string map.
|
|
293
|
+
* @public
|
|
294
|
+
*/
|
|
295
|
+
variantAttributes?: Record<string, VariantAttribute>;
|
|
296
|
+
/**
|
|
297
|
+
* Template used by admin to auto-prefill `variant.name` from typed
|
|
298
|
+
* `variantAttributes`. Omitted = no pre-fill (editor enters name manually).
|
|
299
|
+
* @public
|
|
300
|
+
*/
|
|
301
|
+
variantLabel?: VariantLabelConfig;
|
|
302
|
+
/**
|
|
303
|
+
* Opt-in storefront filter for time-bound variants (events, courses).
|
|
304
|
+
* Omitted = no filtering (legacy behavior — every variant always listed).
|
|
305
|
+
* @public
|
|
306
|
+
*/
|
|
307
|
+
variantExpiry?: VariantExpiryConfig;
|
|
171
308
|
}
|
|
172
|
-
export interface ResolvedShopConfig extends ShopConfig {
|
|
309
|
+
export interface ResolvedShopConfig extends Omit<ShopConfig, 'variantLabel' | 'variantExpiry'> {
|
|
173
310
|
features: Required<ShopFeatures>;
|
|
174
311
|
rateLimit: Required<ShopRateLimit>;
|
|
175
312
|
carriers: CarrierAdapter[];
|
|
176
313
|
consents: ConsentConfig[];
|
|
177
314
|
orderViewUrl: string;
|
|
315
|
+
variantAttributes: Record<string, VariantAttribute>;
|
|
316
|
+
variantLabel: VariantLabelConfig | null;
|
|
317
|
+
variantExpiry: VariantExpiryConfig | null;
|
|
178
318
|
}
|
|
319
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { VariantAttribute } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build a Zod object schema from a `defineShop({ variantAttributes })` map.
|
|
5
|
+
* Required attributes are mandatory keys; optional ones use `.optional()`.
|
|
6
|
+
* Empty map → `z.object({})`.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildVariantAttributesSchema(attrs: Record<string, VariantAttribute>): z.ZodObject<Record<string, z.ZodType>>;
|
|
10
|
+
/**
|
|
11
|
+
* @public
|
|
12
|
+
* Thrown by `upsertShopData` (and `validateVariantAttributes`) when a
|
|
13
|
+
* `variant.attributes` payload fails the schema derived from
|
|
14
|
+
* `defineShop({ variantAttributes })`. Carries the raw Zod issues.
|
|
15
|
+
*/
|
|
16
|
+
export declare class InvalidVariantAttributesError extends Error {
|
|
17
|
+
readonly code = "INVALID_VARIANT_ATTRIBUTES";
|
|
18
|
+
readonly issues: z.ZodIssue[];
|
|
19
|
+
constructor(issues: z.ZodIssue[]);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate a variant attributes payload against a shop config.
|
|
23
|
+
* Throws `InvalidVariantAttributesError` (code `INVALID_VARIANT_ATTRIBUTES`) on
|
|
24
|
+
* failure. Returns the parsed (and coerced where applicable) value on success.
|
|
25
|
+
* `null` / `undefined` payloads are treated as `{}`.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare function validateVariantAttributes(value: unknown, attrs: Record<string, VariantAttribute>): Record<string, unknown>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const SLUG_REGEX = /^[a-z0-9-]+$/;
|
|
3
|
+
function buildLeafSchema(attr) {
|
|
4
|
+
switch (attr.type) {
|
|
5
|
+
case 'text':
|
|
6
|
+
return attr.required ? z.string().min(1) : z.string();
|
|
7
|
+
case 'number':
|
|
8
|
+
return z.number().finite();
|
|
9
|
+
case 'datetime':
|
|
10
|
+
// Accept full ISO 8601: UTC (`...Z`) AND offset forms (`+02:00`).
|
|
11
|
+
// Admin DatetimeField produces `Z` via toISOString(); imports/seeds
|
|
12
|
+
// may use local offsets. Both are valid ISO 8601.
|
|
13
|
+
return z.string().datetime({ offset: true });
|
|
14
|
+
case 'select':
|
|
15
|
+
return z.enum(attr.options.map((o) => o.value));
|
|
16
|
+
case 'boolean':
|
|
17
|
+
return z.boolean();
|
|
18
|
+
case 'image':
|
|
19
|
+
case 'entry':
|
|
20
|
+
return z.string().uuid();
|
|
21
|
+
case 'slug':
|
|
22
|
+
return z.string().regex(SLUG_REGEX);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build a Zod object schema from a `defineShop({ variantAttributes })` map.
|
|
27
|
+
* Required attributes are mandatory keys; optional ones use `.optional()`.
|
|
28
|
+
* Empty map → `z.object({})`.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export function buildVariantAttributesSchema(attrs) {
|
|
32
|
+
const shape = {};
|
|
33
|
+
for (const [key, attr] of Object.entries(attrs)) {
|
|
34
|
+
const leaf = buildLeafSchema(attr);
|
|
35
|
+
shape[key] = attr.required ? leaf : leaf.optional();
|
|
36
|
+
}
|
|
37
|
+
return z.object(shape);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @public
|
|
41
|
+
* Thrown by `upsertShopData` (and `validateVariantAttributes`) when a
|
|
42
|
+
* `variant.attributes` payload fails the schema derived from
|
|
43
|
+
* `defineShop({ variantAttributes })`. Carries the raw Zod issues.
|
|
44
|
+
*/
|
|
45
|
+
export class InvalidVariantAttributesError extends Error {
|
|
46
|
+
code = 'INVALID_VARIANT_ATTRIBUTES';
|
|
47
|
+
issues;
|
|
48
|
+
constructor(issues) {
|
|
49
|
+
super(`Invalid variant attributes: ${issues.map((i) => i.message).join('; ')}`);
|
|
50
|
+
this.name = 'InvalidVariantAttributesError';
|
|
51
|
+
this.issues = issues;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate a variant attributes payload against a shop config.
|
|
56
|
+
* Throws `InvalidVariantAttributesError` (code `INVALID_VARIANT_ATTRIBUTES`) on
|
|
57
|
+
* failure. Returns the parsed (and coerced where applicable) value on success.
|
|
58
|
+
* `null` / `undefined` payloads are treated as `{}`.
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export function validateVariantAttributes(value, attrs) {
|
|
62
|
+
const schema = buildVariantAttributesSchema(attrs);
|
|
63
|
+
const input = value ?? {};
|
|
64
|
+
const result = schema.safeParse(input);
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
throw new InvalidVariantAttributesError(result.error.issues);
|
|
67
|
+
}
|
|
68
|
+
return result.data;
|
|
69
|
+
}
|
|
@@ -7,3 +7,4 @@ export { createConsentLog } from '../../core/server/consentLogs/operations/creat
|
|
|
7
7
|
export { getPreviewEntry } from './preview.js';
|
|
8
8
|
export { createRestApiHandler } from '../../admin/api/rest/handler.js';
|
|
9
9
|
export { generateApiKey } from '../../admin/api/rest/middleware/generateApiKey.js';
|
|
10
|
+
export { createAdminApiHandler } from '../../admin/api/handler.js';
|
|
@@ -8,3 +8,5 @@ export { getPreviewEntry } from './preview.js';
|
|
|
8
8
|
// Folded from `./admin/api/rest/handler` (dropped as separate export in 0.20.0)
|
|
9
9
|
export { createRestApiHandler } from '../../admin/api/rest/handler.js';
|
|
10
10
|
export { generateApiKey } from '../../admin/api/rest/middleware/generateApiKey.js';
|
|
11
|
+
// Folded from `./admin/api/handler` (dropped as separate export in 0.26.1)
|
|
12
|
+
export { createAdminApiHandler } from '../../admin/api/handler.js';
|
package/dist/types/cms.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { FilesAdapter } from './adapters/files.js';
|
|
|
3
3
|
import type { CollectionConfig, CollectionConfigWithType } from './collections.js';
|
|
4
4
|
import type { Language } from './languages.js';
|
|
5
5
|
import type { SingleConfig, SingleConfigWithType } from './singles.js';
|
|
6
|
-
import type { CustomFieldDefinition,
|
|
6
|
+
import type { CustomFieldDefinition, IconSetPlugin, Plugin } from './plugins.js';
|
|
7
7
|
import type { FormConfig } from './forms.js';
|
|
8
8
|
import type { AIAdapter } from './adapters/ai.js';
|
|
9
9
|
import type { EmailAdapter } from './adapters/email.js';
|
|
@@ -77,7 +77,7 @@ export interface CMSConfig {
|
|
|
77
77
|
files: FilesAdapter;
|
|
78
78
|
email?: EmailAdapter;
|
|
79
79
|
auth?: AuthConfig;
|
|
80
|
-
plugins?:
|
|
80
|
+
plugins?: Plugin[];
|
|
81
81
|
ai?: AIAdapter;
|
|
82
82
|
media?: MediaConfig;
|
|
83
83
|
apiKeys?: ApiKeyConfig[];
|
|
@@ -95,8 +95,9 @@ export interface ICMS {
|
|
|
95
95
|
filesAdapter: FilesAdapter;
|
|
96
96
|
emailAdapter: EmailAdapter | null;
|
|
97
97
|
authConfig: AuthConfig | null;
|
|
98
|
-
plugins:
|
|
98
|
+
plugins: Plugin[];
|
|
99
99
|
customFields: Map<string, CustomFieldDefinition>;
|
|
100
|
+
iconSets: Map<string, IconSetPlugin>;
|
|
100
101
|
aiAdapter: AIAdapter | null;
|
|
101
102
|
mediaConfig: MediaConfig;
|
|
102
103
|
apiKeys: ApiKeyConfig[];
|
|
@@ -445,7 +445,7 @@ export declare const _internal: {
|
|
|
445
445
|
shop: z.ZodOptional<z.ZodUnknown>;
|
|
446
446
|
cmp: z.ZodOptional<z.ZodUnknown>;
|
|
447
447
|
}, z.core.$loose>;
|
|
448
|
-
FIELD_TYPES: readonly ["text", "content", "number", "boolean", "date", "datetime", "file", "media", "select", "radio", "checkboxes", "relation", "object", "array", "blocks", "slug", "seo", "shop", "url", "custom"];
|
|
448
|
+
FIELD_TYPES: readonly ["text", "content", "number", "boolean", "date", "datetime", "file", "media", "select", "radio", "checkboxes", "relation", "object", "array", "blocks", "slug", "seo", "shop", "url", "icon", "custom"];
|
|
449
449
|
FORM_FIELD_TYPES: readonly ["text", "email", "textarea", "checkbox", "select", "file"];
|
|
450
450
|
getLangCode: typeof getLangCode;
|
|
451
451
|
};
|
package/dist/types/cms.schema.js
CHANGED
|
@@ -51,6 +51,7 @@ const FIELD_TYPES = [
|
|
|
51
51
|
'seo',
|
|
52
52
|
'shop',
|
|
53
53
|
'url',
|
|
54
|
+
'icon',
|
|
54
55
|
'custom'
|
|
55
56
|
];
|
|
56
57
|
const baseFieldShape = {
|
|
@@ -269,6 +270,14 @@ const fieldSchema = z.lazy(() => z.discriminatedUnion('type', [
|
|
|
269
270
|
type: z.literal('custom'),
|
|
270
271
|
fieldType: z.string().min(1, { message: 'custom.fieldType is required' }),
|
|
271
272
|
config: z.record(z.string(), z.unknown()).optional()
|
|
273
|
+
})
|
|
274
|
+
.passthrough(),
|
|
275
|
+
// icon — set provided by IconSetPlugin (registered in plugins[])
|
|
276
|
+
z
|
|
277
|
+
.object({
|
|
278
|
+
...baseFieldShape,
|
|
279
|
+
type: z.literal('icon'),
|
|
280
|
+
set: z.string().optional()
|
|
272
281
|
})
|
|
273
282
|
.passthrough()
|
|
274
283
|
]));
|
package/dist/types/fields.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { FormatEnum } from 'sharp';
|
|
|
2
2
|
import type { ImageStyle, MediaFile, VideoStyle } from './media.js';
|
|
3
3
|
import type { Localized } from './languages.js';
|
|
4
4
|
import type { StructuredContentDoc } from './structured-content.js';
|
|
5
|
-
export type FieldType = 'text' | 'content' | 'number' | 'boolean' | 'date' | 'datetime' | 'file' | 'media' | 'select' | 'radio' | 'checkboxes' | 'relation' | 'object' | 'array' | 'blocks' | 'slug' | 'seo' | 'shop' | 'url' | 'custom';
|
|
5
|
+
export type FieldType = 'text' | 'content' | 'number' | 'boolean' | 'date' | 'datetime' | 'file' | 'media' | 'select' | 'radio' | 'checkboxes' | 'relation' | 'object' | 'array' | 'blocks' | 'slug' | 'seo' | 'shop' | 'url' | 'icon' | 'custom';
|
|
6
6
|
export interface FieldCondition {
|
|
7
7
|
field: string;
|
|
8
8
|
equals?: string | string[];
|
|
@@ -234,6 +234,11 @@ export interface ShopFieldData {
|
|
|
234
234
|
basePrice: number;
|
|
235
235
|
vatRate: number;
|
|
236
236
|
isActive: boolean;
|
|
237
|
+
/**
|
|
238
|
+
* Per-product payment policy. Omitted = `{ type: 'full' }` (legacy behavior).
|
|
239
|
+
* @public
|
|
240
|
+
*/
|
|
241
|
+
paymentPolicy?: import('../shop/types.js').PaymentPolicy;
|
|
237
242
|
variants?: Array<{
|
|
238
243
|
id?: string;
|
|
239
244
|
sku?: string | null;
|
|
@@ -264,4 +269,18 @@ export interface CustomField extends BaseField {
|
|
|
264
269
|
/** Plugin-specific configuration, opaque to core */
|
|
265
270
|
config?: Record<string, unknown>;
|
|
266
271
|
}
|
|
267
|
-
|
|
272
|
+
/**
|
|
273
|
+
* @public
|
|
274
|
+
* Icon picker field. Stores a string key referencing an icon registered by an
|
|
275
|
+
* {@link import('./plugins.js').IconSetPlugin}. Admin UI renders a kafelek
|
|
276
|
+
* (collapsed) and opens a searchable grid dialog. The set of icons (Svelte
|
|
277
|
+
* components + labels) is provided by a plugin at admin bootstrap — NOT inline
|
|
278
|
+
* in `cms.config.ts` (which is server-only / Zod-validated).
|
|
279
|
+
*/
|
|
280
|
+
export interface IconField extends BaseField {
|
|
281
|
+
type: 'icon';
|
|
282
|
+
/** Plugin slug to source icons from when multiple icon-set plugins registered. Defaults to the first one. */
|
|
283
|
+
set?: string;
|
|
284
|
+
defaultValue?: string;
|
|
285
|
+
}
|
|
286
|
+
export type Field = TextField | ContentField | NumberField | BooleanField | DateField | DateTimeField | FileField | MediaField | SelectField | RadioField | CheckboxesField | RelationField | ObjectField | ArrayField | BlocksField | SlugField | SeoField | ShopField | UrlField | IconField | CustomField;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { type SingleConfig } from './singles.js';
|
|
|
10
10
|
export { type FormConfig, type FormSubmission } from './forms.js';
|
|
11
11
|
export { type FormField, type FormFieldType, type FormBaseField, type FormTextField, type FormEmailField, type FormTextareaField, type FormCheckboxField, type FormSelectField } from './formFields.js';
|
|
12
12
|
export { type CMSConfig, type ApiKeyConfig, type AuthConfig, type TypographyConfig } from './cms.js';
|
|
13
|
-
export { type PluginConfig, type CustomFieldDefinition } from './plugins.js';
|
|
13
|
+
export { type PluginConfig, type CustomFieldDefinition, type IconSetPlugin, type IconDefinition, type Plugin, isIconSetPlugin } from './plugins.js';
|
|
14
14
|
export { type Language, type Localized } from './languages.js';
|
|
15
15
|
export { type Layout, type LayoutNode, type LayoutPreset, type LayoutNodeType, type ColumnRatio, type SectionNode, type ColumnsNode, type CardNode, type AccordionNode, type StackNode, type TabsNode, type TabNode } from './layout.js';
|
|
16
16
|
export { type CmsContext } from './cms-context.js';
|
package/dist/types/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export {} from './singles.js';
|
|
|
10
10
|
export {} from './forms.js';
|
|
11
11
|
export {} from './formFields.js';
|
|
12
12
|
export {} from './cms.js';
|
|
13
|
-
export {} from './plugins.js';
|
|
13
|
+
export { isIconSetPlugin } from './plugins.js';
|
|
14
14
|
export {} from './languages.js';
|
|
15
15
|
export {} from './layout.js';
|
|
16
16
|
export {} from './cms-context.js';
|
package/dist/types/plugins.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Component } from 'svelte';
|
|
|
2
2
|
import type { z } from 'zod';
|
|
3
3
|
import type { RawEntry } from './entries.js';
|
|
4
4
|
import type { CustomField } from './fields.js';
|
|
5
|
+
import type { Localized } from './languages.js';
|
|
5
6
|
import type { PopulateCtx } from '../core/server/entries/operations/resolveEntry.js';
|
|
6
7
|
/**
|
|
7
8
|
* Defines a custom field type contributed by a plugin.
|
|
@@ -40,3 +41,42 @@ export interface PluginConfig {
|
|
|
40
41
|
afterDelete?: (id: string) => Promise<void>;
|
|
41
42
|
};
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* @public
|
|
46
|
+
* Single icon entry contributed by an {@link IconSetPlugin}. Carries a Svelte
|
|
47
|
+
* component, a localized label and optional search keywords. Components are
|
|
48
|
+
* eagerly imported (tree-shakable) — for small sets (10–50 icons) lazy import
|
|
49
|
+
* adds overhead without benefit.
|
|
50
|
+
*/
|
|
51
|
+
export interface IconDefinition {
|
|
52
|
+
/** Svelte icon component (e.g. `Brain` from `@lucide/svelte`). */
|
|
53
|
+
component: Component;
|
|
54
|
+
/** Localized display label shown in collapsed state and in the picker grid. */
|
|
55
|
+
label: Localized;
|
|
56
|
+
/** Extra search keywords (non-localized). Search matches key + label + keywords. */
|
|
57
|
+
keywords?: string[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* @public
|
|
61
|
+
* Icon set plugin — registers a named set of icons consumable by the `icon`
|
|
62
|
+
* field type. Registered alongside other plugins in `cms.config.ts`'s
|
|
63
|
+
* `plugins:` array. Because Svelte components do NOT serialize to the client,
|
|
64
|
+
* the application's admin layout must wire the same plugin instance to
|
|
65
|
+
* `AdminLayout` via `buildIconSetMap(...)` — see admin/helpers.
|
|
66
|
+
*/
|
|
67
|
+
export interface IconSetPlugin {
|
|
68
|
+
/** Unique slug — referenced by `IconField.set` when multiple sets exist. */
|
|
69
|
+
slug: string;
|
|
70
|
+
type: 'icon-set';
|
|
71
|
+
icons: Record<string, IconDefinition>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @public
|
|
75
|
+
* Union of all plugin kinds accepted in `cms.config.ts → plugins:`. New
|
|
76
|
+
* plugin kinds must be added here. Type discriminated by:
|
|
77
|
+
* - {@link IconSetPlugin} → `type === 'icon-set'`
|
|
78
|
+
* - {@link PluginConfig} → everything else (no `type` field).
|
|
79
|
+
*/
|
|
80
|
+
export type Plugin = PluginConfig | IconSetPlugin;
|
|
81
|
+
/** Type guard for {@link IconSetPlugin}. */
|
|
82
|
+
export declare function isIconSetPlugin(p: Plugin): p is IconSetPlugin;
|
package/dist/types/plugins.js
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.26.1',
|
|
3
|
+
date: '2026-05-28',
|
|
4
|
+
description: 'Nowy typ pola `icon` + IconSetPlugin (zestaw ikon dostarczany przez aplikację, dialog z gridem + search). Scaffolder fix + DataTable polish: scaffold admin używa publicznych entry points; pierwsza kolumna tabeli bez sticky-first; checkbox visual wraca do 16 px z transparent hit-area 24×24 (WCAG 2.5.5).',
|
|
5
|
+
features: [
|
|
6
|
+
'Nowy typ pola `icon` (`IconField` @public, `src/lib/types/fields.ts`) — kompaktowy kafelek 96×96 w formularzu (ikona + nazwa, X do wyczyszczenia) otwiera dialog z gridem responsive 3–6 kolumn, wyszukiwarką (filtruje po `key`, lokalizowanym `label`, opcjonalnych `keywords`) i przyciskami Anuluj / OK. Klik na kafelku w dialogu zaznacza, OK zatwierdza; dwuklik = szybki zapis. Stan "missing" (wartość w DB nieobecna w bibliotece) pokazuje placeholder + przyjazne ostrzeżenie zamiast resetować wartość — zgodne z ToV "informuj, nie strasz". i18n PL/EN, `role="listbox"`/`role="option"`/`aria-selected`, LiveRegion z liczbą wyników.',
|
|
7
|
+
'`IconSetPlugin` / `IconDefinition` (`@public`, `src/lib/types/plugins.ts`) — nowy rodzaj pluginu rejestrujący nazwany zestaw ikon: `{ slug, type: \'icon-set\', icons: Record<key, { component, label: Localized, keywords?: string[] }> }`. Komponenty Svelte (eager import, tree-shakable — np. `Brain` z `@lucide/svelte`) ŻYJĄ w pluginie, bo `cms.config.ts` jest server-only i Zod-validated. `Plugin = PluginConfig | IconSetPlugin` + `isIconSetPlugin` type guard.',
|
|
8
|
+
'`buildIconSetMap(...plugins)` (`includio-cms/admin`) + nowy prop `iconSets?: Map<string, IconSetPlugin>` na `AdminLayout` — konsumencki `+layout.svelte` admina przekazuje plugin instancje na klienta (funkcji komponentów nie da się serializować przez SvelteKit data). `getIconSets()` / `resolveIconSet(slug?)` w `includio-cms/admin` — runtime lookup używany przez `icon-field.svelte` przez Svelte context.',
|
|
9
|
+
'`CMS.iconSets: Map<slug, IconSetPlugin>` agregowane przy starcie z `cms.config.plugins` przez `isIconSetPlugin` guard; duplikat slug → throw. Generator TS mapuje `icon` → `string` w wygenerowanym `types.ts`; Zod runtime → `z.string()` (analogicznie do `slug` field).'
|
|
10
|
+
],
|
|
11
|
+
fixes: [
|
|
12
|
+
'`scaffold admin` generuje `src/routes/admin/api/[...path]/+server.ts` z `import { createAdminApiHandler } from \'includio-cms/sveltekit/server\'`. Wcześniej generowany import `includio-cms/admin/api/handler` rzucał Vite `Missing "./admin/api/handler" specifier in "includio-cms" package` przy starcie dev/build — `admin/api/handler` nie ma w `exports` (v1.0 frozen 18 ścieżek).',
|
|
13
|
+
'`scaffold admin` generuje `src/routes/admin/api/rest/[...restPath]/+server.ts` z `import { createRestApiHandler } from \'includio-cms/sveltekit/server\'` (analogicznie — był broken w 0.20.0+ ale scaffolder nie zaktualizowany).',
|
|
14
|
+
'`includio-cms/sveltekit/server` re-eksportuje teraz `createAdminApiHandler` (obok istniejącego `createRestApiHandler`).',
|
|
15
|
+
'`DataTable`: revert sticky-first column (z 0.26.0 / S10c) — pierwsza kolumna scrolluje razem z resztą, bez wyróżnienia kolorystycznego. Usunięty prop `stickyFirstColumn` i powiązany CSS.',
|
|
16
|
+
'`Checkbox` primitive: visual wraca do 16×16 px (z 24×24 w 0.26.0 / S10b), klikalny obszar utrzymany na ≥24×24 przez transparent `::after` pseudo-element (`after:-inset-1`) — WCAG 2.5.5 target-size AA dalej spełniony, lepszy wygląd zgodny z proporcjami pól tekstowych.'
|
|
17
|
+
],
|
|
18
|
+
breakingChanges: []
|
|
19
|
+
};
|