goatdee-canvas 0.0.32 → 0.0.34

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.
Binary file
package/dist/index.cjs CHANGED
@@ -1169,22 +1169,308 @@ const initializeFonts = async () => {
1169
1169
  }
1170
1170
  };
1171
1171
 
1172
+ // ─────────────────────────────────────────────────────────────────────────────
1173
+ // 子类型 schema(CanvasShadow / CanvasLinearGradient)
1174
+ //
1175
+ // 通过 `satisfies SchemaFor<T>` 与 types.ts 强绑定:
1176
+ // - T 字段新增 → schema 缺少该 key → 编译报错
1177
+ // - T 字段改类型 → schema FieldType 不满足新约束 → 编译报错
1178
+ // - schema 多余 key → 编译报错(excess property check)
1179
+ // ─────────────────────────────────────────────────────────────────────────────
1180
+ const shadowSchema = {
1181
+ blur: 'number',
1182
+ color: 'string',
1183
+ offsetX: 'number',
1184
+ offsetY: 'number',
1185
+ };
1186
+ const linearGradientSchema = {
1187
+ type: 'string',
1188
+ coords: 'object',
1189
+ colorStops: 'array',
1190
+ };
1191
+ // ─────────────────────────────────────────────────────────────────────────────
1192
+ // 基础类型校验(供子类型 schema 驱动的循环内使用,避免递归调用 matchesFieldType)
1193
+ // ─────────────────────────────────────────────────────────────────────────────
1194
+ function checkPrimitive(value, ft) {
1195
+ if (ft === 'array')
1196
+ return Array.isArray(value);
1197
+ if (ft === 'object')
1198
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1199
+ return typeof value === ft;
1200
+ }
1201
+ // ─────────────────────────────────────────────────────────────────────────────
1202
+ // 复合类型守卫
1203
+ // ─────────────────────────────────────────────────────────────────────────────
1204
+ /** CanvasFill / CanvasStroke:string | CanvasLinearGradient | null */
1205
+ function isFill(value) {
1206
+ if (value === null || typeof value === 'string')
1207
+ return true;
1208
+ if (typeof value === 'object' && !Array.isArray(value)) {
1209
+ const v = value;
1210
+ return Object.entries(linearGradientSchema)
1211
+ .every(([key, ft]) => checkPrimitive(v[key], ft));
1212
+ }
1213
+ return false;
1214
+ }
1215
+ /** CanvasShadow:由 shadowSchema 驱动,与 types.ts 中 CanvasShadow 强绑定 */
1216
+ function isShadow(value) {
1217
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
1218
+ return false;
1219
+ const v = value;
1220
+ return Object.entries(shadowSchema)
1221
+ .every(([key, ft]) => checkPrimitive(v[key], ft));
1222
+ }
1223
+ /** TextStyles(unknown):null / object / array 均合法,拒绝 string / number / boolean 等原始值 */
1224
+ function isTextStyles(value) {
1225
+ return value === null || typeof value === 'object';
1226
+ }
1227
+ // ─────────────────────────────────────────────────────────────────────────────
1228
+ // 主校验函数
1229
+ // ─────────────────────────────────────────────────────────────────────────────
1230
+ /** 判断值是否符合 FieldType 规则 */
1231
+ function matchesFieldType(value, fieldType) {
1232
+ switch (fieldType) {
1233
+ case 'array': return Array.isArray(value);
1234
+ case 'object': return typeof value === 'object' && value !== null && !Array.isArray(value);
1235
+ case 'fill': return isFill(value);
1236
+ case 'shadow': return isShadow(value);
1237
+ case 'shadow-or-null': return value === null || isShadow(value);
1238
+ case 'number-or-null': return value === null || typeof value === 'number';
1239
+ case 'string-or-null': return value === null || typeof value === 'string';
1240
+ case 'text-styles': return isTextStyles(value);
1241
+ default: return typeof value === fieldType;
1242
+ }
1243
+ }
1244
+
1245
+ /**
1246
+ * 所有对象类型共享的公共字段 schema。
1247
+ * 各对象类型 schema 通过扩展此对象来复用这些字段。
1248
+ */
1249
+ const baseSchema = {
1250
+ id: 'string',
1251
+ name: 'string',
1252
+ left: 'number',
1253
+ top: 'number',
1254
+ width: 'number',
1255
+ height: 'number',
1256
+ scaleX: 'number',
1257
+ scaleY: 'number',
1258
+ angle: 'number',
1259
+ strokeWidth: 'number',
1260
+ opacity: 'number',
1261
+ visible: 'boolean',
1262
+ locked: 'boolean',
1263
+ selectable: 'boolean',
1264
+ lockMovementX: 'boolean',
1265
+ lockMovementY: 'boolean',
1266
+ lockRotation: 'boolean',
1267
+ lockScalingX: 'boolean',
1268
+ lockScalingY: 'boolean',
1269
+ lockUniScaling: 'boolean',
1270
+ };
1271
+
1272
+ const textboxSchema = {
1273
+ ...baseSchema,
1274
+ type: 'string',
1275
+ flipX: 'boolean',
1276
+ flipY: 'boolean',
1277
+ text: 'string',
1278
+ fontSize: 'number',
1279
+ lineHeight: 'number',
1280
+ fontFamily: 'string',
1281
+ fill: 'fill',
1282
+ stroke: 'fill',
1283
+ shadow: 'shadow-or-null',
1284
+ textAlign: 'string',
1285
+ charSpacing: 'number',
1286
+ underline: 'boolean',
1287
+ linethrough: 'boolean',
1288
+ bold: 'boolean',
1289
+ italic: 'boolean',
1290
+ styles: 'text-styles',
1291
+ editable: 'boolean',
1292
+ };
1293
+
1294
+ const imageSchema = {
1295
+ ...baseSchema,
1296
+ type: 'string',
1297
+ flipX: 'boolean',
1298
+ flipY: 'boolean',
1299
+ src: 'string',
1300
+ cropX: 'number',
1301
+ cropY: 'number',
1302
+ loading: 'string-or-null',
1303
+ rx: 'number',
1304
+ ry: 'number',
1305
+ backgroundColor: 'fill',
1306
+ stroke: 'fill',
1307
+ shadow: 'shadow-or-null',
1308
+ };
1309
+
1310
+ const circleSchema = {
1311
+ ...baseSchema,
1312
+ type: 'string',
1313
+ flipX: 'boolean',
1314
+ flipY: 'boolean',
1315
+ fill: 'fill',
1316
+ stroke: 'fill',
1317
+ shadow: 'shadow-or-null',
1318
+ borderDashArray: 'array',
1319
+ radius: 'number',
1320
+ };
1321
+
1322
+ const rectangleSchema = {
1323
+ ...baseSchema,
1324
+ type: 'string',
1325
+ flipX: 'boolean',
1326
+ flipY: 'boolean',
1327
+ fill: 'fill',
1328
+ stroke: 'fill',
1329
+ shadow: 'shadow-or-null',
1330
+ borderDashArray: 'array',
1331
+ rx: 'number',
1332
+ ry: 'number',
1333
+ };
1334
+
1335
+ const triangleSchema = {
1336
+ ...baseSchema,
1337
+ type: 'string',
1338
+ flipX: 'boolean',
1339
+ flipY: 'boolean',
1340
+ fill: 'fill',
1341
+ stroke: 'fill',
1342
+ shadow: 'shadow-or-null',
1343
+ borderDashArray: 'array',
1344
+ };
1345
+
1346
+ const groupSchema = {
1347
+ ...baseSchema,
1348
+ type: 'string',
1349
+ flipX: 'boolean',
1350
+ flipY: 'boolean',
1351
+ objects: 'array',
1352
+ };
1353
+
1354
+ const frameSchema = {
1355
+ ...baseSchema,
1356
+ type: 'string',
1357
+ backgroundColor: 'fill',
1358
+ stroke: 'fill',
1359
+ autoScaleBg: 'boolean',
1360
+ crossable: 'boolean',
1361
+ clipContent: 'boolean',
1362
+ showName: 'boolean',
1363
+ showSize: 'boolean',
1364
+ objects: 'array',
1365
+ };
1366
+
1367
+ const slideSchema = {
1368
+ ...baseSchema,
1369
+ type: 'string',
1370
+ backgroundColor: 'fill',
1371
+ stroke: 'fill',
1372
+ contentBackgroundColor: 'fill',
1373
+ contentStrokeWidth: 'number',
1374
+ contentStroke: 'fill',
1375
+ paddingX: 'number',
1376
+ paddingY: 'number',
1377
+ rx: 'number',
1378
+ ry: 'number',
1379
+ crossable: 'boolean',
1380
+ showName: 'boolean',
1381
+ showSize: 'boolean',
1382
+ objects: 'array',
1383
+ };
1384
+
1385
+ const placeholderSchema = {
1386
+ type: 'string',
1387
+ id: 'string',
1388
+ name: 'string',
1389
+ left: 'number',
1390
+ top: 'number',
1391
+ width: 'number',
1392
+ height: 'number',
1393
+ export: 'boolean',
1394
+ locked: 'boolean',
1395
+ lockMovementX: 'boolean',
1396
+ lockMovementY: 'boolean',
1397
+ lockRotation: 'boolean',
1398
+ lockScalingX: 'boolean',
1399
+ lockScalingY: 'boolean',
1400
+ lockUniScaling: 'boolean',
1401
+ selectable: 'boolean',
1402
+ visible: 'boolean',
1403
+ };
1404
+
1405
+ const pathSchema = {
1406
+ ...baseSchema,
1407
+ type: 'string',
1408
+ flipX: 'boolean',
1409
+ flipY: 'boolean',
1410
+ stroke: 'fill',
1411
+ points: 'array',
1412
+ };
1413
+
1414
+ /**
1415
+ * 对象类型 → schema 映射表。
1416
+ * grid / line / grid-cell 使用宽松 schema(CanvasGridLineCellLoose),透传不校验。
1417
+ *
1418
+ * 各 schema 值以 `as SchemaEntry` 断言:schema 文件用 satisfies 保证了每个字段的
1419
+ * 值都是合法的 FieldType 字面量,断言仅消除 TS 对字面量宽泛推断的误报。
1420
+ */
1421
+ const schemaMap = {
1422
+ textbox: textboxSchema,
1423
+ image: imageSchema,
1424
+ circle: circleSchema,
1425
+ rectangle: rectangleSchema,
1426
+ triangle: triangleSchema,
1427
+ group: groupSchema,
1428
+ frame: frameSchema,
1429
+ slide: slideSchema,
1430
+ placeholder: placeholderSchema,
1431
+ path: pathSchema,
1432
+ };
1433
+
1172
1434
  var utils;
1173
1435
  (function (utils) {
1436
+ /**
1437
+ * 对属性对象进行安全校验:
1438
+ * - null / undefined / 非对象:原样返回
1439
+ * - 已知对象类型(textbox / image / circle 等):按 schema 逐字段校验类型,
1440
+ * 类型不符的字段打印警告并丢弃,合法字段保留
1441
+ * - 未知类型(grid / line / grid-cell 等):透传不校验
1442
+ */
1174
1443
  function safeProperties(properties) {
1175
- // console.log('safeProperties', properties);
1176
- return properties;
1444
+ if (properties === null || properties === undefined)
1445
+ return properties;
1446
+ if (typeof properties !== 'object' || Array.isArray(properties))
1447
+ return properties;
1448
+ const schema = schemaMap[properties.type];
1449
+ if (!schema)
1450
+ return properties;
1451
+ const result = {};
1452
+ for (const [key, fieldType] of Object.entries(schema)) {
1453
+ if (!(key in properties))
1454
+ continue;
1455
+ const value = properties[key];
1456
+ if (matchesFieldType(value, fieldType)) {
1457
+ result[key] = (key === 'objects' && Array.isArray(value))
1458
+ ? value.map(item => safeProperties(item))
1459
+ : value;
1460
+ }
1461
+ else {
1462
+ console.warn(`[safeProperties] 属性 "${key}" 类型不符:期望 ${fieldType},实际 ${Array.isArray(value) ? 'array' : typeof value},已忽略`);
1463
+ }
1464
+ }
1465
+ return result;
1177
1466
  }
1178
1467
  utils.safeProperties = safeProperties;
1179
1468
  function safeAddOptions(options) {
1180
1469
  if (!options)
1181
1470
  return options;
1182
1471
  // 如果包含 points 属性,则过滤掉该属性
1183
- if (options.points) {
1184
- delete options.points;
1185
- }
1186
- console.log('safeAddOptions', options);
1187
- return options;
1472
+ const { points: _points, ...rest } = options;
1473
+ return rest;
1188
1474
  }
1189
1475
  utils.safeAddOptions = safeAddOptions;
1190
1476
  /**
@@ -1195,19 +1481,13 @@ var utils;
1195
1481
  */
1196
1482
  function debounce(fn, delay) {
1197
1483
  let timer = null;
1198
- let lastCallTime = 0;
1199
1484
  return function (...args) {
1200
- const now = Date.now();
1201
- lastCallTime = now;
1202
1485
  if (timer !== null) {
1203
1486
  clearTimeout(timer);
1204
1487
  }
1205
1488
  timer = window.setTimeout(() => {
1206
- // 只有在延迟时间后没有新的调用时才执行
1207
- if (Date.now() - lastCallTime >= delay) {
1208
- fn.apply(this, args);
1209
- timer = null;
1210
- }
1489
+ fn.apply(this, args);
1490
+ timer = null;
1211
1491
  }, delay);
1212
1492
  };
1213
1493
  }
@@ -3763,8 +4043,15 @@ const setObjectProperties = async (id, properties) => {
3763
4043
  if (properties.src && typeof properties.src === "string") {
3764
4044
  await manager.registerImageIfNeeded(properties.src);
3765
4045
  }
3766
- // 安全处理 properties
3767
- const safeProperties = utils.safeProperties(properties);
4046
+ // 如果调用方没有传 type,从对象数据中补充,确保 safeProperties 能找到对应 schema
4047
+ let propsForValidation = properties;
4048
+ if (!properties.type) {
4049
+ const obj = getObject(id);
4050
+ if (obj === null || obj === void 0 ? void 0 : obj.type) {
4051
+ propsForValidation = { ...properties, type: obj.type };
4052
+ }
4053
+ }
4054
+ const safeProperties = utils.safeProperties(propsForValidation);
3768
4055
  canvas.setObjectProperties(id, safeProperties);
3769
4056
  }
3770
4057
  catch (error) {
@@ -3779,8 +4066,8 @@ const setObjectProperties = async (id, properties) => {
3779
4066
  const setCanvasProperties = async (properties) => {
3780
4067
  try {
3781
4068
  const canvas = manager.getCanvas();
3782
- const safeProperties = utils.safeProperties(properties);
3783
- canvas.setCanvasProperties(safeProperties);
4069
+ // const safeProperties = utils.safeProperties(properties);
4070
+ canvas.setCanvasProperties(properties);
3784
4071
  }
3785
4072
  catch (error) {
3786
4073
  console.warn("Canvas operation(setCanvasProperties) failed:", error);