@openpresentation/opf 0.12.2 → 0.13.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.
Files changed (48) hide show
  1. package/dist/audit.d.ts +9 -130
  2. package/dist/audit.js +5 -5
  3. package/dist/{catalogs-BnI7JKcn.d.ts → catalogs-ywCamd8C.d.ts} +1 -1
  4. package/dist/catalogs.d.ts +2 -2
  5. package/dist/chart-data-Cdr6rRVV.d.ts +177 -0
  6. package/dist/{chunk-6LZK2IZB.js → chunk-7N4VMAT4.js} +11 -7
  7. package/dist/{chunk-4LAM3N6S.js → chunk-7QBCWNWL.js} +424 -18
  8. package/dist/{chunk-SOSOBKQ2.js → chunk-ATGDDJPX.js} +4 -5
  9. package/dist/{chunk-V2537ENL.js → chunk-II5EYKZU.js} +9 -7
  10. package/dist/{chunk-MWPMJE6P.js → chunk-INU7ZNSB.js} +50 -2
  11. package/dist/{chunk-RUC7L5DJ.js → chunk-IS2CPNVJ.js} +1 -1
  12. package/dist/{chunk-JKVKYVWL.js → chunk-IYCJUKWD.js} +90 -471
  13. package/dist/{chunk-HOBSL7AJ.js → chunk-MPM7NRG7.js} +2396 -1408
  14. package/dist/{chunk-AUM5MXBR.js → chunk-RAYB46AB.js} +8 -6
  15. package/dist/{composition-DWbCiMwF.d.ts → composition-D7we69Tr.d.ts} +7 -0
  16. package/dist/composition.d.ts +2 -1
  17. package/dist/composition.js +1 -1
  18. package/dist/convert.d.ts +4 -1
  19. package/dist/convert.js +33 -16
  20. package/dist/data.d.ts +2 -1
  21. package/dist/data.js +3 -1
  22. package/dist/diff.d.ts +1 -1
  23. package/dist/diff.js +8 -7
  24. package/dist/docs.js +26 -20
  25. package/dist/examples.d.ts +1 -1
  26. package/dist/examples.js +102 -57
  27. package/dist/format.js +1 -1
  28. package/dist/index.d.ts +13 -10
  29. package/dist/index.js +8 -8
  30. package/dist/lint-B7azdiuE.d.ts +197 -0
  31. package/dist/lint.d.ts +7 -70
  32. package/dist/lint.js +4 -4
  33. package/dist/markdown.d.ts +7 -5
  34. package/dist/markdown.js +5 -5
  35. package/dist/pagination.d.ts +2 -1
  36. package/dist/pagination.js +4 -4
  37. package/dist/patch.js +4 -4
  38. package/dist/{presentation-DcF-MZcz.d.ts → presentation-mG-IYErQ.d.ts} +187 -14
  39. package/dist/{schemas-BSjlG8-R.d.ts → schemas-BbPonsTz.d.ts} +18 -0
  40. package/dist/schemas.d.ts +1 -1
  41. package/dist/schemas.js +1 -1
  42. package/dist/spec/catalogs/manifest.json +3 -3
  43. package/dist/spec/schemas/opf.schema.json +238 -20
  44. package/dist/types.d.ts +4 -4
  45. package/dist/{validator-DfG9yxba.d.ts → validator-CIylxoIm.d.ts} +3 -3
  46. package/dist/validator.d.ts +4 -4
  47. package/dist/validator.js +3 -3
  48. package/package.json +2 -2
@@ -358,6 +358,10 @@ Deck-level concerns that aren't part of the storyline (audience, tone, takeaway,
358
358
  ]
359
359
  ]
360
360
  },
361
+ "datasets": {
362
+ "$ref": "#/$defs/Datasets",
363
+ "description": `Optional shared data tables, keyed by id. A chart ('chart.data': { "dataset": "<id>" }) or a table ('table': { "dataset": "<id>" }) references one instead of holding its own copy; engines inline the reference before composing, rendering or exporting. A dataset nothing references is a lint warning (opf/unused-dataset). See docs/chart-table-data.md.`
364
+ },
361
365
  "assets": {
362
366
  "$ref": "#/$defs/Assets",
363
367
  "description": "Optional reusable asset registry for images, data files, videos, documents, fonts, and other resources referenced elsewhere in the deck via 'asset:<id>' strings."
@@ -2466,19 +2470,25 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
2466
2470
  "description": "Illustrative number shown in fill forms and used when a template is previewed with examples. Never written to output."
2467
2471
  },
2468
2472
  "format": {
2469
- "type": "string",
2470
- "description": "Display pattern used by '{{<id>}}'. A literal prefix, a numeric part of '#', '0', ',' and '.', and a literal suffix. '0' pads digits, '#' is optional, ',' groups thousands, digits after '.' fix the decimals ('0' required, '#' optional), and a '%' in the suffix scales by 100. English separators only. Absent: the shortest decimal form.",
2471
- "examples": [
2472
- "#,##0",
2473
- "0.0",
2474
- "$#,##0.00",
2475
- "0.#%",
2476
- "#,##0.0M"
2477
- ]
2473
+ "$ref": "#/$defs/NumberFormat",
2474
+ "description": "Display pattern used by '{{<id>}}' (the shared NumberFormat syntax). A literal prefix, a numeric part of '#', '0', ',' and '.', and a literal suffix. '0' pads digits, '#' is optional, ',' groups thousands, digits after '.' fix the decimals ('0' required, '#' optional), and a '%' in the suffix scales by 100. English separators only. Absent: the shortest decimal form."
2478
2475
  }
2479
2476
  },
2480
2477
  "description": "A number. '{{<id>}}' inserts it as text using 'format'; 'var:<id>' as a whole field supplies the number itself (chart values, font sizes)."
2481
2478
  },
2479
+ "NumberFormat": {
2480
+ "type": "string",
2481
+ "description": "Number display pattern, shared by NumberVariable.format, data column formats (DataColumn.format) and table cell formats (StyledTableCell.format). An optional literal prefix, a numeric part of '#', '0', ',' and '.', and an optional literal suffix. '0' pads digits, '#' is optional, ',' groups thousands, digits after '.' fix the decimals ('0' required, '#' optional), and a '%' in the prefix or suffix multiplies the value by 100. English separators only. A format applies only to number values; strings, booleans, null and rich runs display unchanged. A column or cell format that is not a valid pattern is a 'number-format-invalid' error. The PPTX export writes it as the equivalent Excel number format code.",
2482
+ "examples": [
2483
+ "#,##0",
2484
+ "0.0",
2485
+ "$#,##0.00",
2486
+ "0.#%",
2487
+ "0%",
2488
+ "#,##0.0M",
2489
+ "#,##0 units"
2490
+ ]
2491
+ },
2482
2492
  "DateVariable": {
2483
2493
  "type": "object",
2484
2494
  "required": [
@@ -4645,9 +4655,16 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
4645
4655
  },
4646
4656
  {
4647
4657
  "$ref": "#/$defs/ChartDataSource"
4658
+ },
4659
+ {
4660
+ "$ref": "#/$defs/DatasetRef"
4648
4661
  }
4649
4662
  ],
4650
- "description": "Chart data. Inline data uses a tabular columns/rows shape; renderers convert rows to chart series internally."
4663
+ "description": `Chart data. Inline data uses a tabular columns/rows shape; renderers convert rows to chart series internally. A DatasetRef ({ "dataset": "<id>" }) plots a top-level dataset instead. A ChartDataSource is not resolved by any engine (a 'chart-data-source-unresolved' warning; engines draw a placeholder); prefer inline data with a 'source'.`
4664
+ },
4665
+ "mapping": {
4666
+ "$ref": "#/$defs/ChartMapping",
4667
+ "description": "Optional series mapping by column name: which column is the category, which is the X column of a scatter chart, and which columns are plotted, in order. Absent keeps the positional rule (first column the category, then the X column for scatter, every other column a series)."
4651
4668
  },
4652
4669
  "axisTitles": {
4653
4670
  "$ref": "#/$defs/ChartAxisTitles",
@@ -4746,11 +4763,27 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
4746
4763
  },
4747
4764
  "Table": {
4748
4765
  "type": "object",
4749
- "description": "Table content. Columns are optional; rows are the only required field.",
4766
+ "description": "Table content, inline or dataset-backed. An inline table has 'rows' (required) and optional 'columns'. A dataset-backed table has 'dataset' (required) and optional 'fields', and no 'rows' or 'columns': it takes its headers, rows and column formats from the dataset (per-cell styles need an inline table).",
4750
4767
  "additionalProperties": false,
4751
- "required": [
4752
- "rows"
4753
- ],
4768
+ "if": {
4769
+ "required": [
4770
+ "dataset"
4771
+ ]
4772
+ },
4773
+ "then": {
4774
+ "properties": {
4775
+ "rows": false,
4776
+ "columns": false
4777
+ }
4778
+ },
4779
+ "else": {
4780
+ "required": [
4781
+ "rows"
4782
+ ],
4783
+ "properties": {
4784
+ "fields": false
4785
+ }
4786
+ },
4754
4787
  "properties": {
4755
4788
  "columns": {
4756
4789
  "type": "array",
@@ -4768,12 +4801,15 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
4768
4801
  {
4769
4802
  "$ref": "#/$defs/StyledTableCell"
4770
4803
  },
4804
+ {
4805
+ "$ref": "#/$defs/DataColumn"
4806
+ },
4771
4807
  {
4772
4808
  "type": "null"
4773
4809
  }
4774
4810
  ]
4775
4811
  },
4776
- "description": "Optional column labels. Labels may be strings, rich runs or styled cell objects. Null is an empty label or a placeholder covered by a preceding column span.",
4812
+ "description": `Optional column labels. Labels may be strings, rich runs, styled cell objects or DataColumn objects ({ "name", "format" }). A header's 'format' (DataColumn or StyledTableCell) is the column's number format; a body cell's own format wins. Null is an empty label or a placeholder covered by a preceding column span.`,
4777
4813
  "examples": [
4778
4814
  [
4779
4815
  "Region",
@@ -4808,12 +4844,20 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
4808
4844
  ]
4809
4845
  ]
4810
4846
  ]
4847
+ },
4848
+ "dataset": {
4849
+ "$ref": "#/$defs/DatasetId",
4850
+ "description": "Id of a top-level dataset that supplies this table's headers, rows and column formats. Excludes 'rows' and 'columns'. An unknown id is a 'dataset-unknown' error."
4851
+ },
4852
+ "fields": {
4853
+ "$ref": "#/$defs/DatasetFields",
4854
+ "description": "Dataset tables only: the dataset columns to show, by name and in order. Absent shows every column. An unknown name is a 'dataset-field-unknown' error."
4811
4855
  }
4812
4856
  }
4813
4857
  },
4814
4858
  "ChartData": {
4815
4859
  "type": "object",
4816
- "description": "Inline tabular data driving a chart. The first column usually supplies category/x-axis labels; subsequent columns are plotted measures unless a chart type or renderer maps them differently.",
4860
+ "description": "Inline tabular data driving a chart. The first column usually supplies category/x-axis labels; subsequent columns are plotted measures unless 'chart.mapping' or a chart type maps them differently. Value cells are numbers; a string is read only in strict decimal syntax ('12', '-3.5', '1e6'), and anything else ('12%', '$5', '(5)', '1,234') is a gap and a 'chart-value-not-numeric' warning.",
4817
4861
  "additionalProperties": false,
4818
4862
  "required": [
4819
4863
  "columns",
@@ -4823,10 +4867,17 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
4823
4867
  "columns": {
4824
4868
  "type": "array",
4825
4869
  "items": {
4826
- "type": "string"
4870
+ "oneOf": [
4871
+ {
4872
+ "type": "string"
4873
+ },
4874
+ {
4875
+ "$ref": "#/$defs/DataColumn"
4876
+ }
4877
+ ]
4827
4878
  },
4828
4879
  "minItems": 1,
4829
- "description": "Ordered column labels for the chart data table.",
4880
+ "description": 'Ordered column labels for the chart data table. A label is a string or a DataColumn ({ "name", "format" }) whose number format the data labels, value axis and exported workbook use.',
4830
4881
  "examples": [
4831
4882
  [
4832
4883
  "Quarter",
@@ -4836,6 +4887,17 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
4836
4887
  "Quarter",
4837
4888
  "Revenue",
4838
4889
  "Costs"
4890
+ ],
4891
+ [
4892
+ "Quarter",
4893
+ {
4894
+ "name": "Revenue",
4895
+ "format": "$#,##0.0"
4896
+ },
4897
+ {
4898
+ "name": "Margin",
4899
+ "format": "0%"
4900
+ }
4839
4901
  ]
4840
4902
  ]
4841
4903
  },
@@ -4869,9 +4931,349 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
4869
4931
  ]
4870
4932
  ]
4871
4933
  ]
4934
+ },
4935
+ "source": {
4936
+ "$ref": "#/$defs/DataSourceRef",
4937
+ "description": "Optional provenance: where this inline data came from. Engines never read, fetch or refresh it; they keep it through editing, export and re-import."
4938
+ }
4939
+ }
4940
+ },
4941
+ "DataColumn": {
4942
+ "type": "object",
4943
+ "additionalProperties": false,
4944
+ "required": [
4945
+ "name"
4946
+ ],
4947
+ "description": 'A named data column with an optional number format. Anywhere a chart or dataset column is a string it may be a DataColumn; the string form is { "name": value } with no format. A table column header may also be a DataColumn.',
4948
+ "properties": {
4949
+ "name": {
4950
+ "type": "string",
4951
+ "description": "Column label, used as the series name, the table header and the name 'fields' and 'chart.mapping' address.",
4952
+ "examples": [
4953
+ "Revenue",
4954
+ "Margin"
4955
+ ]
4956
+ },
4957
+ "format": {
4958
+ "$ref": "#/$defs/NumberFormat",
4959
+ "description": "Number format of the column's number values (data labels, value axis, table cells, exported workbook). Absent: the General form."
4960
+ }
4961
+ },
4962
+ "examples": [
4963
+ {
4964
+ "name": "Revenue",
4965
+ "format": "$#,##0.0"
4966
+ },
4967
+ {
4968
+ "name": "Margin",
4969
+ "format": "0%"
4970
+ }
4971
+ ]
4972
+ },
4973
+ "DataSourceRef": {
4974
+ "type": "object",
4975
+ "additionalProperties": false,
4976
+ "required": [
4977
+ "src"
4978
+ ],
4979
+ "description": "Provenance of inline data or a dataset: the file, sheet and range it came from and when. Engines never read, fetch or refresh it; they keep it through editing, export and re-import. Re-import the data to update it.",
4980
+ "properties": {
4981
+ "src": {
4982
+ "type": "string",
4983
+ "description": "Where the data came from: an 'asset:<id>' reference, a URL, or a relative or local path.",
4984
+ "examples": [
4985
+ "./data/revenue.csv",
4986
+ "asset:revenue",
4987
+ "https://cdn.acme.com/data/revenue.csv"
4988
+ ]
4989
+ },
4990
+ "sheet": {
4991
+ "type": "string",
4992
+ "description": "Sheet or table name inside a spreadsheet source.",
4993
+ "examples": [
4994
+ "Sheet1"
4995
+ ]
4996
+ },
4997
+ "range": {
4998
+ "type": "string",
4999
+ "description": "A1-style range inside a spreadsheet source.",
5000
+ "examples": [
5001
+ "A1:D8"
5002
+ ]
5003
+ },
5004
+ "fields": {
5005
+ "type": "array",
5006
+ "items": {
5007
+ "type": "string"
5008
+ },
5009
+ "description": "The source fields the data was taken from, in order.",
5010
+ "examples": [
5011
+ [
5012
+ "Quarter",
5013
+ "Revenue"
5014
+ ]
5015
+ ]
5016
+ },
5017
+ "retrieved": {
5018
+ "type": "string",
5019
+ "anyOf": [
5020
+ {
5021
+ "format": "date"
5022
+ },
5023
+ {
5024
+ "format": "date-time"
5025
+ }
5026
+ ],
5027
+ "description": "When the data was taken from the source: an ISO 8601 date or date-time.",
5028
+ "examples": [
5029
+ "2026-10-05",
5030
+ "2026-10-05T09:30:00Z"
5031
+ ]
5032
+ },
5033
+ "description": {
5034
+ "type": "string",
5035
+ "description": "Free-form note about the source.",
5036
+ "examples": [
5037
+ "Finance export, unaudited"
5038
+ ]
5039
+ }
5040
+ },
5041
+ "examples": [
5042
+ {
5043
+ "src": "./data/revenue.csv",
5044
+ "retrieved": "2026-10-05"
5045
+ }
5046
+ ]
5047
+ },
5048
+ "DatasetId": {
5049
+ "type": "string",
5050
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$",
5051
+ "description": "Id of an entry in the top-level datasets map (the assets id pattern).",
5052
+ "examples": [
5053
+ "revenue",
5054
+ "pipeline-2026"
5055
+ ]
5056
+ },
5057
+ "DatasetFields": {
5058
+ "type": "array",
5059
+ "items": {
5060
+ "type": "string"
5061
+ },
5062
+ "minItems": 1,
5063
+ "uniqueItems": true,
5064
+ "description": "Dataset column names to use, in order, each at most once. Selects and orders the dataset's columns; an unknown name is a 'dataset-field-unknown' error.",
5065
+ "examples": [
5066
+ [
5067
+ "Quarter",
5068
+ "Revenue"
5069
+ ]
5070
+ ]
5071
+ },
5072
+ "Datasets": {
5073
+ "type": "object",
5074
+ "description": `Shared data tables keyed by id (the assets id pattern). Charts reference one with 'chart.data': { "dataset": "<id>" } and tables with 'table': { "dataset": "<id>" }.`,
5075
+ "propertyNames": {
5076
+ "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$"
5077
+ },
5078
+ "additionalProperties": {
5079
+ "$ref": "#/$defs/Dataset"
5080
+ },
5081
+ "examples": [
5082
+ {
5083
+ "revenue": {
5084
+ "title": "Revenue by quarter",
5085
+ "columns": [
5086
+ "Quarter",
5087
+ {
5088
+ "name": "Revenue",
5089
+ "format": "$#,##0.0"
5090
+ },
5091
+ "Costs"
5092
+ ],
5093
+ "rows": [
5094
+ [
5095
+ "Q1",
5096
+ 12,
5097
+ 8
5098
+ ],
5099
+ [
5100
+ "Q2",
5101
+ 18,
5102
+ 11
5103
+ ],
5104
+ [
5105
+ "Q3",
5106
+ 24,
5107
+ 15
5108
+ ]
5109
+ ],
5110
+ "source": {
5111
+ "src": "./data/revenue.csv",
5112
+ "retrieved": "2026-10-05"
5113
+ }
5114
+ }
5115
+ }
5116
+ ]
5117
+ },
5118
+ "Dataset": {
5119
+ "type": "object",
5120
+ "additionalProperties": false,
5121
+ "required": [
5122
+ "columns",
5123
+ "rows"
5124
+ ],
5125
+ "description": "One shared data table: named columns (strings or DataColumn objects with a number format) and rows of scalar cells. Column names are unique ('data-column-duplicate' error). Rich text and styling belong in an inline table.",
5126
+ "properties": {
5127
+ "title": {
5128
+ "type": "string",
5129
+ "description": "Human name of the dataset, shown by editors.",
5130
+ "examples": [
5131
+ "Revenue by quarter"
5132
+ ]
5133
+ },
5134
+ "description": {
5135
+ "type": "string",
5136
+ "description": "What the dataset holds.",
5137
+ "examples": [
5138
+ "Quarterly revenue and costs, in $M."
5139
+ ]
5140
+ },
5141
+ "columns": {
5142
+ "type": "array",
5143
+ "items": {
5144
+ "oneOf": [
5145
+ {
5146
+ "type": "string"
5147
+ },
5148
+ {
5149
+ "$ref": "#/$defs/DataColumn"
5150
+ }
5151
+ ]
5152
+ },
5153
+ "minItems": 1,
5154
+ "description": "Ordered, uniquely named columns.",
5155
+ "examples": [
5156
+ [
5157
+ "Quarter",
5158
+ {
5159
+ "name": "Revenue",
5160
+ "format": "$#,##0.0"
5161
+ },
5162
+ "Costs"
5163
+ ]
5164
+ ]
5165
+ },
5166
+ "rows": {
5167
+ "type": "array",
5168
+ "items": {
5169
+ "type": "array",
5170
+ "items": {
5171
+ "$ref": "#/$defs/ChartDataCell"
5172
+ }
5173
+ },
5174
+ "description": "Rows of scalar cells (string, number, boolean, null); each row aligns by index with columns.",
5175
+ "examples": [
5176
+ [
5177
+ [
5178
+ "Q1",
5179
+ 12,
5180
+ 8
5181
+ ],
5182
+ [
5183
+ "Q2",
5184
+ 18,
5185
+ 11
5186
+ ]
5187
+ ]
5188
+ ]
5189
+ },
5190
+ "source": {
5191
+ "$ref": "#/$defs/DataSourceRef",
5192
+ "description": "Optional provenance. Engines never read, fetch or refresh it."
4872
5193
  }
4873
5194
  }
4874
5195
  },
5196
+ "DatasetRef": {
5197
+ "type": "object",
5198
+ "additionalProperties": false,
5199
+ "required": [
5200
+ "dataset"
5201
+ ],
5202
+ "description": "Chart data taken from a top-level dataset. Engines inline it before plotting; 'chart.mapping' names columns after the 'fields' selection.",
5203
+ "properties": {
5204
+ "dataset": {
5205
+ "$ref": "#/$defs/DatasetId",
5206
+ "description": "Id of the dataset. An unknown id is a 'dataset-unknown' error."
5207
+ },
5208
+ "fields": {
5209
+ "$ref": "#/$defs/DatasetFields",
5210
+ "description": "The dataset columns to use, by name and in order. Absent uses every column."
5211
+ }
5212
+ },
5213
+ "examples": [
5214
+ {
5215
+ "dataset": "revenue"
5216
+ },
5217
+ {
5218
+ "dataset": "revenue",
5219
+ "fields": [
5220
+ "Quarter",
5221
+ "Revenue"
5222
+ ]
5223
+ }
5224
+ ]
5225
+ },
5226
+ "ChartMapping": {
5227
+ "type": "object",
5228
+ "additionalProperties": false,
5229
+ "description": "Series mapping by column name (after any 'fields' selection). Mapping only selects and orders columns; pie, doughnut and other single-series constructs still plot one series. An unknown name is a 'chart-mapping-unknown-column' error; a series that repeats the category or X column, and an X column on a chart type without an X axis, are dropped with a 'chart-mapping-adapted' warning.",
5230
+ "properties": {
5231
+ "category": {
5232
+ "type": "string",
5233
+ "description": "The label (category) column. Default: the first column.",
5234
+ "examples": [
5235
+ "Quarter"
5236
+ ]
5237
+ },
5238
+ "x": {
5239
+ "type": "string",
5240
+ "description": "The X column of an XY (scatter) chart. Default: the second column (the first, when the category is the second column), and only when there are three or more columns: with two, the second column is the one series, plotted against row numbers. An X that leaves no series is plotted as the series with a 'chart-mapping-adapted' warning.",
5241
+ "examples": [
5242
+ "Spend"
5243
+ ]
5244
+ },
5245
+ "series": {
5246
+ "type": "array",
5247
+ "items": {
5248
+ "type": "string"
5249
+ },
5250
+ "minItems": 1,
5251
+ "description": "The plotted columns, in order. Default: every column that is not the category or the X column.",
5252
+ "examples": [
5253
+ [
5254
+ "Revenue",
5255
+ "Costs"
5256
+ ]
5257
+ ]
5258
+ }
5259
+ },
5260
+ "examples": [
5261
+ {
5262
+ "category": "Quarter",
5263
+ "series": [
5264
+ "Costs",
5265
+ "Revenue"
5266
+ ]
5267
+ },
5268
+ {
5269
+ "category": "Region",
5270
+ "x": "Spend",
5271
+ "series": [
5272
+ "Revenue"
5273
+ ]
5274
+ }
5275
+ ]
5276
+ },
4875
5277
  "ChartDataSource": {
4876
5278
  "type": "object",
4877
5279
  "description": "Chart data sourced from an asset reference, URL, data URI, relative path, or local path such as CSV, TSV, JSON, or XLSX. The source is interpreted as a table; optional columns select or order fields from that table.",
@@ -5000,6 +5402,10 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
5000
5402
  "type": "integer",
5001
5403
  "minimum": 1,
5002
5404
  "description": "Number of grid rows covered, starting at this cell. Covered positions must contain null. Header cells cannot span into body rows. Default 1."
5405
+ },
5406
+ "format": {
5407
+ "$ref": "#/$defs/NumberFormat",
5408
+ "description": "Number format. On a body cell it formats that cell's number value (and wins over the column's format); on a header cell (in 'columns') it is the column's format, the same as a DataColumn header. Strings, booleans, null and rich runs display unchanged."
5003
5409
  }
5004
5410
  },
5005
5411
  "description": "A cell with explicit visual style or merged geometry. Its position remains its array column index; use null placeholders for every covered grid position."
@@ -1,3 +1,5 @@
1
+ import { chartNumber } from './chunk-MPM7NRG7.js';
2
+
1
3
  // src/data.ts
2
4
  var OPFDataImportError = class extends Error {
3
5
  constructor(message) {
@@ -108,11 +110,8 @@ function parseTabularData(input, options = {}) {
108
110
  return data;
109
111
  }
110
112
  function measure(value, location) {
111
- if (typeof value === "number" && Number.isFinite(value)) return value;
112
- if (typeof value === "string" && /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(value.trim())) {
113
- const number = Number(value.trim());
114
- if (Number.isFinite(number) && (!Number.isInteger(number) || Number.isSafeInteger(number))) return number;
115
- }
113
+ const number = chartNumber(value);
114
+ if (number !== null) return number;
116
115
  return fail(`${location}: expected a numeric chart value; found ${JSON.stringify(value)}. Clean the value or select another series.`);
117
116
  }
118
117
  function createDataContent(input, options) {
@@ -1,4 +1,4 @@
1
- import { validatePresentation } from './chunk-JKVKYVWL.js';
1
+ import { validatePresentation } from './chunk-IYCJUKWD.js';
2
2
 
3
3
  // src/convert/shared.ts
4
4
  var CONTENT_KEYS = ["text", "items", "bullets", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"];
@@ -35,15 +35,17 @@ function canonical(value) {
35
35
  }
36
36
  var isSlideLike = (payload) => SLIDE_ONLY_KEYS.some((key) => own(payload, key));
37
37
  var contentKeysOf = (payload) => CONTENT_KEYS.filter((key) => payload[key] !== void 0);
38
- function assertValidOutput(value, as = "block") {
38
+ function assertValidOutput(value, as = "block", context = {}) {
39
39
  const slides = as === "slides" ? value : as === "slide" ? [value] : [{ blocks: [value] }];
40
- const result = validatePresentation({ slides });
41
- if (!result.valid) {
42
- const first = result.errors[0];
43
- throw new OPFConversionError("invalid-output", `The converted content is not valid OPF: ${first?.message ?? "unknown error"}${first?.path ? ` (${first.path})` : ""}.`, { issues: result.errors });
40
+ const datasets = isRecord(context.datasets) ? context.datasets : void 0;
41
+ const result = validatePresentation({ ...datasets ? { datasets } : {}, slides });
42
+ const errors = datasets ? result.errors : result.errors.filter((issue) => issue.params.code !== "dataset-unknown" && issue.params.code !== "dataset-field-unknown");
43
+ if (errors.length) {
44
+ const first = errors[0];
45
+ throw new OPFConversionError("invalid-output", `The converted content is not valid OPF: ${first?.message ?? "unknown error"}${first?.path ? ` (${first.path})` : ""}.`, { issues: errors });
44
46
  }
45
47
  }
46
- var assertValidOwner = (owner) => assertValidOutput(owner, isSlideLike(owner) ? "slide" : "block");
48
+ var assertValidOwner = (owner, context = {}) => assertValidOutput(owner, isSlideLike(owner) ? "slide" : "block", context);
47
49
  var FORMATTING = ["bold", "italic", "underline", "strikethrough", "color", "fontSize", "fontFamily", "link", "superscript", "subscript"];
48
50
  var isPlainRun = (run) => typeof run === "string" || isRecord(run) && !FORMATTING.some((key) => run[key] !== void 0);
49
51
  var runText = (run) => typeof run === "string" ? run : run.text ?? "";
@@ -1,8 +1,10 @@
1
- import { validatePresentation, unusedReferenceWarnings, validationDefinition, validateCatalogRecord } from './chunk-JKVKYVWL.js';
1
+ import { validatePresentation, unusedReferenceWarnings, validationDefinition, validateCatalogRecord } from './chunk-IYCJUKWD.js';
2
+ import { unusedDatasets, isRecord, visitContentPayloads, chartNumberFixesByCell, pathFor } from './chunk-MPM7NRG7.js';
2
3
  import { catalogKinds, catalogEntries, catalogSchemaNames } from './chunk-E34DJ5LL.js';
3
- import { schemas, schemaEntries } from './chunk-4LAM3N6S.js';
4
+ import { schemas, schemaEntries } from './chunk-7QBCWNWL.js';
4
5
  import { parseTree, printParseErrorCode, getNodePath } from 'jsonc-parser';
5
6
 
7
+ var DATA_WARNING_CODES = /* @__PURE__ */ new Set(["chart-value-not-numeric", "chart-data-source-unresolved", "chart-mapping-adapted"]);
6
8
  var own = (value, key) => Object.hasOwn(value, key);
7
9
  var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8
10
  var primitive = (value) => value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
@@ -365,6 +367,20 @@ function distance(a, b) {
365
367
  }
366
368
  return row[b.length] ?? Math.max(a.length, b.length);
367
369
  }
370
+ function chartNumberFixPaths(document) {
371
+ const byCell = /* @__PURE__ */ new Map();
372
+ if (!isRecord(document) || !Array.isArray(document.slides)) return byCell;
373
+ const payload = (node, path) => {
374
+ if (isRecord(node.chart))
375
+ for (const [cell, fix] of chartNumberFixesByCell(node.chart, document, { path: pathFor(path, "chart") })) byCell.set(cell, fix);
376
+ };
377
+ document.slides.forEach((slide, index) => {
378
+ if (!isRecord(slide)) return;
379
+ payload(slide, `/slides/${index}`);
380
+ visitContentPayloads(slide, `/slides/${index}`, payload);
381
+ });
382
+ return byCell;
383
+ }
368
384
  function lintPresentation(document, options = {}) {
369
385
  optionsChecked(options);
370
386
  const validation = validatePresentation(document), diagnostics = validation.errors.map(
@@ -448,7 +464,28 @@ function lintPresentation(document, options = {}) {
448
464
  current = typeof source === "string" && source.startsWith("asset:") ? source.slice(6) : void 0;
449
465
  }
450
466
  }
467
+ const numberFixes = validation.warnings.some((issue) => issue.params.code === "chart-value-not-numeric") ? chartNumberFixPaths(document) : /* @__PURE__ */ new Map();
451
468
  for (const issue of validation.warnings) {
469
+ if (typeof issue.params.code === "string" && DATA_WARNING_CODES.has(issue.params.code)) {
470
+ const fix = issue.params.code === "chart-value-not-numeric" ? numberFixes.get(issue.path) : void 0;
471
+ if (fix) {
472
+ diagnostics.push({
473
+ ...schemaDiagnostic(issue),
474
+ ruleId: "opf/chart-value-not-numeric",
475
+ severity: "warning",
476
+ help: `Every text value of column ${JSON.stringify(fix.name)} is written in one display style. Store the numbers and give the column the format ${JSON.stringify(fix.format)}, which shows the same text: apply fixes[0] (core suggestChartNumberFix). Until then the value is plotted as a gap.`,
477
+ fixes: [{ id: "store-chart-numbers", label: `Store ${JSON.stringify(fix.name)} as numbers with the format ${JSON.stringify(fix.format)}`, kind: "patch", safe: false, patch: fix.patches }]
478
+ });
479
+ continue;
480
+ }
481
+ diagnostics.push({
482
+ ...schemaDiagnostic(issue),
483
+ ruleId: `opf/${issue.params.code}`,
484
+ severity: "warning",
485
+ help: issue.params.code === "chart-value-not-numeric" ? 'Write chart values as numbers (or strict decimal strings such as "12.5" or "1e6"); put currency, percent and units in the column format ({ "name": "Revenue", "format": "$#,##0" }). The value is plotted as a gap.' : issue.params.code === "chart-data-source-unresolved" ? "No engine loads chart data sources. Import the data inline (columns and rows, recording the origin in data.source) or reference a top-level dataset." : "The mapping entry is ignored. Remove it, or name a different column."
486
+ });
487
+ continue;
488
+ }
452
489
  const kind = issue.params.kind, id = issue.params.id, replacedBy = issue.params.replacedBy;
453
490
  if (typeof replacedBy === "string" && typeof id === "string") {
454
491
  const target = catalogs.get(kind)?.get(replacedBy);
@@ -529,6 +566,17 @@ function lintPresentation(document, options = {}) {
529
566
  lookup: ["opf", "schema", "presentation", "/$defs/Reference"],
530
567
  validation: issue
531
568
  });
569
+ for (const id of unusedDatasets(document))
570
+ diagnostics.push({
571
+ ruleId: "opf/unused-dataset",
572
+ severity: "warning",
573
+ scope: "document",
574
+ path: `/datasets/${id.replaceAll("~", "~0").replaceAll("/", "~1")}`,
575
+ message: `Dataset ${JSON.stringify(id)} is never referenced; reference it from a chart ("data": { "dataset": ${JSON.stringify(id)} }) or a table ({ "dataset": ${JSON.stringify(id)} }), or remove it.`,
576
+ help: "Reference the dataset from chart.data or a table so it is drawn, or remove the entry. Nothing is drawn for an unreferenced dataset.",
577
+ definition: schemas.presentation.$id + "#/$defs/Dataset",
578
+ lookup: ["opf", "schema", "presentation", "/$defs/Dataset"]
579
+ });
532
580
  return report(diagnostics, validation.valid, "not-applicable");
533
581
  }
534
582
  function lintSource(source, options = {}) {
@@ -1,4 +1,4 @@
1
- import { validatePresentation } from './chunk-JKVKYVWL.js';
1
+ import { validatePresentation } from './chunk-IYCJUKWD.js';
2
2
 
3
3
  // src/patch.ts
4
4
  var PatchError = class extends Error {