@genspectrum/dashboard-components 0.10.1 → 0.10.3

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 (79) hide show
  1. package/README.md +19 -19
  2. package/custom-elements.json +72 -56
  3. package/dist/assets/{mutationOverTimeWorker-CvZg52rf.js.map → mutationOverTimeWorker-CNg_ztNp.js.map} +1 -1
  4. package/dist/components.d.ts +19 -28
  5. package/dist/components.js +343 -96
  6. package/dist/components.js.map +1 -1
  7. package/dist/{utilEntrypoint-g4DsyhU7.js → dateRangeOption-DjtcAEWq.js} +46 -2
  8. package/dist/dateRangeOption-DjtcAEWq.js.map +1 -0
  9. package/dist/style.css +11 -5
  10. package/dist/util.d.ts +112 -21
  11. package/dist/util.js +3 -2
  12. package/package.json +2 -2
  13. package/src/preact/aggregatedData/aggregate.stories.tsx +14 -0
  14. package/src/preact/aggregatedData/aggregate.tsx +17 -15
  15. package/src/preact/components/color-scale-selector.tsx +7 -3
  16. package/src/preact/components/error-boundary.stories.tsx +24 -3
  17. package/src/preact/components/error-boundary.tsx +38 -5
  18. package/src/preact/components/error-display.tsx +62 -6
  19. package/src/preact/components/tabs.tsx +2 -2
  20. package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +8 -2
  21. package/src/preact/dateRangeSelector/computeInitialValues.ts +6 -0
  22. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +16 -2
  23. package/src/preact/dateRangeSelector/date-range-selector.tsx +20 -15
  24. package/src/preact/dateRangeSelector/dateRangeOption.ts +10 -5
  25. package/src/preact/lineageFilter/lineage-filter.stories.tsx +18 -4
  26. package/src/preact/lineageFilter/lineage-filter.tsx +15 -10
  27. package/src/preact/locationFilter/location-filter.stories.tsx +14 -0
  28. package/src/preact/locationFilter/location-filter.tsx +15 -10
  29. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +17 -18
  30. package/src/preact/mutationComparison/mutation-comparison.tsx +18 -12
  31. package/src/preact/mutationFilter/mutation-filter.tsx +26 -13
  32. package/src/preact/mutations/mutations.tsx +16 -12
  33. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +1326 -9341
  34. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +615 -4920
  35. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +2203 -17624
  36. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +16 -8
  37. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +14 -0
  38. package/src/preact/mutationsOverTime/mutations-over-time.tsx +19 -17
  39. package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +14 -0
  40. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +22 -14
  41. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +14 -0
  42. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +28 -19
  43. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +14 -0
  44. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +18 -15
  45. package/src/preact/shared/charts/confideceInterval.ts +10 -8
  46. package/src/preact/shared/charts/getYAxisMax.ts +10 -5
  47. package/src/preact/shared/stories/expectInvalidAttributesErrorMessage.ts +13 -0
  48. package/src/preact/statistic/statistics.tsx +10 -8
  49. package/src/preact/textInput/text-input.stories.tsx +14 -0
  50. package/src/preact/textInput/text-input.tsx +16 -11
  51. package/src/preact/webWorkers/useWebWorker.ts +8 -4
  52. package/src/query/queryAggregateData.ts +2 -1
  53. package/src/query/queryMutationsOverTime.spec.ts +12 -27
  54. package/src/query/queryMutationsOverTime.ts +2 -6
  55. package/src/types.ts +31 -7
  56. package/src/utilEntrypoint.ts +15 -0
  57. package/src/utils/map2d.spec.ts +10 -10
  58. package/src/utils/map2d.ts +10 -10
  59. package/src/web-components/app.stories.ts +17 -2
  60. package/src/web-components/app.ts +17 -5
  61. package/src/web-components/input/gs-date-range-selector.stories.ts +2 -2
  62. package/src/web-components/input/gs-date-range-selector.tsx +3 -3
  63. package/src/web-components/input/gs-lineage-filter.tsx +1 -1
  64. package/src/web-components/input/gs-location-filter.tsx +2 -2
  65. package/src/web-components/input/gs-mutation-filter.stories.ts +2 -0
  66. package/src/web-components/input/gs-text-input.tsx +2 -2
  67. package/src/web-components/introduction.mdx +4 -4
  68. package/src/web-components/visualization/data_visualization_statistical_analysis.mdx +3 -3
  69. package/src/web-components/visualization/gs-aggregate.tsx +2 -2
  70. package/src/web-components/visualization/gs-mutations-over-time.tsx +1 -3
  71. package/src/web-components/visualization/gs-mutations.tsx +1 -3
  72. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +1 -3
  73. package/src/web-components/visualization/gs-prevalence-over-time.tsx +3 -6
  74. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +1 -5
  75. package/standalone-bundle/assets/{mutationOverTimeWorker-CypX_PYM.js.map → mutationOverTimeWorker-cIyshfj_.js.map} +1 -1
  76. package/standalone-bundle/dashboard-components.js +8800 -8577
  77. package/standalone-bundle/dashboard-components.js.map +1 -1
  78. package/standalone-bundle/style.css +1 -1
  79. package/dist/utilEntrypoint-g4DsyhU7.js.map +0 -1
@@ -1,7 +1,44 @@
1
+ import z from "zod";
2
+ const lapisFilterSchema = z.record(z.union([z.string(), z.number(), z.null(), z.boolean()]));
3
+ const namedLapisFilterSchema = z.object({
4
+ lapisFilter: lapisFilterSchema,
5
+ displayName: z.string()
6
+ });
7
+ const temporalGranularitySchema = z.union([
8
+ z.literal("day"),
9
+ z.literal("week"),
10
+ z.literal("month"),
11
+ z.literal("year")
12
+ ]);
13
+ const sequenceTypeSchema = z.union([z.literal("nucleotide"), z.literal("amino acid")]);
14
+ const views = {
15
+ table: "table",
16
+ venn: "venn",
17
+ grid: "grid",
18
+ insertions: "insertions",
19
+ bar: "bar",
20
+ line: "line",
21
+ bubble: "bubble"
22
+ };
23
+ const mutationComparisonViewSchema = z.union([z.literal(views.table), z.literal(views.venn)]);
1
24
  const toYYYYMMDD = (date) => {
2
25
  const options = { year: "numeric", month: "2-digit", day: "2-digit" };
3
26
  return date.toLocaleDateString("en-CA", options);
4
27
  };
28
+ const dateRangeOptionSchema = z.object({
29
+ /** The label of the date range option that will be shown to the user */
30
+ label: z.string(),
31
+ /**
32
+ * The start date of the date range in the format `YYYY-MM-DD`.
33
+ * If not set, the date range selector will default to the `earliestDate` property.
34
+ */
35
+ dateFrom: z.string().date().optional(),
36
+ /**
37
+ * The end date of the date range in the format `YYYY-MM-DD`.
38
+ * If not set, the date range selector will default to the current date.
39
+ */
40
+ dateTo: z.string().date().optional()
41
+ });
5
42
  class DateRangeOptionChangedEvent extends CustomEvent {
6
43
  constructor(detail) {
7
44
  super("gs-date-range-option-changed", {
@@ -55,7 +92,14 @@ const dateRangeOptionPresets = {
55
92
  };
56
93
  export {
57
94
  DateRangeOptionChangedEvent as D,
95
+ dateRangeOptionSchema as a,
96
+ toYYYYMMDD as b,
58
97
  dateRangeOptionPresets as d,
59
- toYYYYMMDD as t
98
+ lapisFilterSchema as l,
99
+ mutationComparisonViewSchema as m,
100
+ namedLapisFilterSchema as n,
101
+ sequenceTypeSchema as s,
102
+ temporalGranularitySchema as t,
103
+ views as v
60
104
  };
61
- //# sourceMappingURL=utilEntrypoint-g4DsyhU7.js.map
105
+ //# sourceMappingURL=dateRangeOption-DjtcAEWq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dateRangeOption-DjtcAEWq.js","sources":["../src/types.ts","../src/preact/dateRangeSelector/dateConversion.ts","../src/preact/dateRangeSelector/dateRangeOption.ts"],"sourcesContent":["import z from 'zod';\n\nimport {\n type Deletion,\n type DeletionClass,\n type Insertion,\n type InsertionClass,\n type Substitution,\n type SubstitutionClass,\n} from './utils/mutations';\n\nexport const lapisFilterSchema = z.record(z.union([z.string(), z.number(), z.null(), z.boolean()]));\nexport type LapisFilter = z.infer<typeof lapisFilterSchema>;\n\nexport const namedLapisFilterSchema = z.object({\n lapisFilter: lapisFilterSchema,\n displayName: z.string(),\n});\nexport type NamedLapisFilter = z.infer<typeof namedLapisFilterSchema>;\n\nexport const temporalGranularitySchema = z.union([\n z.literal('day'),\n z.literal('week'),\n z.literal('month'),\n z.literal('year'),\n]);\nexport type TemporalGranularity = z.infer<typeof temporalGranularitySchema>;\n\nexport const sequenceTypeSchema = z.union([z.literal('nucleotide'), z.literal('amino acid')]);\nexport type SequenceType = z.infer<typeof sequenceTypeSchema>;\n\nexport type SubstitutionOrDeletion = 'substitution' | 'deletion';\n\nexport type MutationType = SubstitutionOrDeletion | 'insertion';\n\nexport type SubstitutionEntry<T extends Substitution = SubstitutionClass> = {\n type: 'substitution';\n mutation: T;\n count: number;\n proportion: number;\n};\n\nexport type DeletionEntry<T extends Deletion = DeletionClass> = {\n type: 'deletion';\n mutation: T;\n count: number;\n proportion: number;\n};\n\nexport type InsertionEntry<T extends Insertion = InsertionClass> = { type: 'insertion'; mutation: T; count: number };\n\nexport type SubstitutionOrDeletionEntry<\n S extends Substitution = SubstitutionClass,\n D extends Deletion = DeletionClass,\n> = SubstitutionEntry<S> | DeletionEntry<D>;\n\nexport type MutationEntry = SubstitutionEntry | DeletionEntry | InsertionEntry;\n\nexport const views = {\n table: 'table',\n venn: 'venn',\n grid: 'grid',\n insertions: 'insertions',\n bar: 'bar',\n line: 'line',\n bubble: 'bubble',\n} as const;\n\nexport const mutationComparisonViewSchema = z.union([z.literal(views.table), z.literal(views.venn)]);\nexport type MutationComparisonView = z.infer<typeof mutationComparisonViewSchema>;\n","export const toYYYYMMDD = (date: Date) => {\n const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: '2-digit', day: '2-digit' };\n return date.toLocaleDateString('en-CA', options);\n};\n","import z from 'zod';\n\nimport { toYYYYMMDD } from './dateConversion';\n\n/**\n * A date range option that can be used in the `gs-date-range-selector` component.\n */\nexport const dateRangeOptionSchema = z.object({\n /** The label of the date range option that will be shown to the user */\n label: z.string(),\n /**\n * The start date of the date range in the format `YYYY-MM-DD`.\n * If not set, the date range selector will default to the `earliestDate` property.\n */\n dateFrom: z.string().date().optional(),\n /**\n * The end date of the date range in the format `YYYY-MM-DD`.\n * If not set, the date range selector will default to the current date.\n */\n dateTo: z.string().date().optional(),\n});\n\nexport type DateRangeOption = z.infer<typeof dateRangeOptionSchema>;\n\nexport type DateRangeSelectOption = string | { dateFrom: string; dateTo: string };\n\nexport class DateRangeOptionChangedEvent extends CustomEvent<DateRangeSelectOption> {\n constructor(detail: DateRangeSelectOption) {\n super('gs-date-range-option-changed', {\n detail,\n bubbles: true,\n composed: true,\n });\n }\n}\n\nconst today = new Date();\n\nconst twoWeeksAgo = new Date();\ntwoWeeksAgo.setDate(today.getDate() - 14);\n\nconst lastMonth = new Date(today);\nlastMonth.setMonth(today.getMonth() - 1);\n\nconst last2Months = new Date(today);\nlast2Months.setMonth(today.getMonth() - 2);\n\nconst last3Months = new Date(today);\nlast3Months.setMonth(today.getMonth() - 3);\n\nconst last6Months = new Date(today);\nlast6Months.setMonth(today.getMonth() - 6);\n\nconst lastYear = new Date(today);\nlastYear.setFullYear(today.getFullYear() - 1);\n\n/**\n * Presets for the `gs-date-range-selector` component that can be used as `dateRangeOptions`.\n */\nexport const dateRangeOptionPresets = {\n last2Weeks: {\n label: 'Last 2 weeks',\n dateFrom: toYYYYMMDD(twoWeeksAgo),\n },\n lastMonth: {\n label: 'Last month',\n dateFrom: toYYYYMMDD(lastMonth),\n },\n last2Months: {\n label: 'Last 2 months',\n dateFrom: toYYYYMMDD(last2Months),\n },\n last3Months: {\n label: 'Last 3 months',\n dateFrom: toYYYYMMDD(last3Months),\n },\n last6Months: {\n label: 'Last 6 months',\n dateFrom: toYYYYMMDD(last6Months),\n },\n lastYear: {\n label: 'Last year',\n dateFrom: toYYYYMMDD(lastYear),\n },\n allTimes: {\n label: 'All times',\n },\n} satisfies Record<string, DateRangeOption>;\n"],"names":[],"mappings":";AAWO,MAAM,oBAAoB,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,GAAG,EAAE,KAAK,GAAG,EAAE,QAAS,CAAA,CAAC,CAAC;AAGrF,MAAA,yBAAyB,EAAE,OAAO;AAAA,EAC3C,aAAa;AAAA,EACb,aAAa,EAAE,OAAO;AAC1B,CAAC;AAGY,MAAA,4BAA4B,EAAE,MAAM;AAAA,EAC7C,EAAE,QAAQ,KAAK;AAAA,EACf,EAAE,QAAQ,MAAM;AAAA,EAChB,EAAE,QAAQ,OAAO;AAAA,EACjB,EAAE,QAAQ,MAAM;AACpB,CAAC;AAGM,MAAM,qBAAqB,EAAE,MAAM,CAAC,EAAE,QAAQ,YAAY,GAAG,EAAE,QAAQ,YAAY,CAAC,CAAC;AA8BrF,MAAM,QAAQ;AAAA,EACjB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,MAAM;AAAA,EACN,QAAQ;AACZ;AAEO,MAAM,+BAA+B,EAAE,MAAM,CAAC,EAAE,QAAQ,MAAM,KAAK,GAAG,EAAE,QAAQ,MAAM,IAAI,CAAC,CAAC;ACpEtF,MAAA,aAAa,CAAC,SAAe;AACtC,QAAM,UAAsC,EAAE,MAAM,WAAW,OAAO,WAAW,KAAK;AAC/E,SAAA,KAAK,mBAAmB,SAAS,OAAO;AACnD;ACIa,MAAA,wBAAwB,EAAE,OAAO;AAAA;AAAA,EAE1C,OAAO,EAAE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhB,UAAU,EAAE,OAAS,EAAA,KAAA,EAAO,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKrC,QAAQ,EAAE,OAAS,EAAA,KAAA,EAAO,SAAS;AACvC,CAAC;AAMM,MAAM,oCAAoC,YAAmC;AAAA,EAChF,YAAY,QAA+B;AACvC,UAAM,gCAAgC;AAAA,MAClC;AAAA,MACA,SAAS;AAAA,MACT,UAAU;AAAA,IAAA,CACb;AAAA,EACL;AACJ;AAEA,MAAM,4BAAY;AAElB,MAAM,kCAAkB;AACxB,YAAY,QAAQ,MAAM,QAAQ,IAAI,EAAE;AAExC,MAAM,YAAY,IAAI,KAAK,KAAK;AAChC,UAAU,SAAS,MAAM,SAAS,IAAI,CAAC;AAEvC,MAAM,cAAc,IAAI,KAAK,KAAK;AAClC,YAAY,SAAS,MAAM,SAAS,IAAI,CAAC;AAEzC,MAAM,cAAc,IAAI,KAAK,KAAK;AAClC,YAAY,SAAS,MAAM,SAAS,IAAI,CAAC;AAEzC,MAAM,cAAc,IAAI,KAAK,KAAK;AAClC,YAAY,SAAS,MAAM,SAAS,IAAI,CAAC;AAEzC,MAAM,WAAW,IAAI,KAAK,KAAK;AAC/B,SAAS,YAAY,MAAM,YAAY,IAAI,CAAC;AAKrC,MAAM,yBAAyB;AAAA,EAClC,YAAY;AAAA,IACR,OAAO;AAAA,IACP,UAAU,WAAW,WAAW;AAAA,EACpC;AAAA,EACA,WAAW;AAAA,IACP,OAAO;AAAA,IACP,UAAU,WAAW,SAAS;AAAA,EAClC;AAAA,EACA,aAAa;AAAA,IACT,OAAO;AAAA,IACP,UAAU,WAAW,WAAW;AAAA,EACpC;AAAA,EACA,aAAa;AAAA,IACT,OAAO;AAAA,IACP,UAAU,WAAW,WAAW;AAAA,EACpC;AAAA,EACA,aAAa;AAAA,IACT,OAAO;AAAA,IACP,UAAU,WAAW,WAAW;AAAA,EACpC;AAAA,EACA,UAAU;AAAA,IACN,OAAO;AAAA,IACP,UAAU,WAAW,QAAQ;AAAA,EACjC;AAAA,EACA,UAAU;AAAA,IACN,OAAO;AAAA,EACX;AACJ;"}
package/dist/style.css CHANGED
@@ -482,7 +482,7 @@ input[type="range"] {
482
482
  --tw-contain-paint: ;
483
483
  --tw-contain-style: ;
484
484
  }/*
485
- ! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com
485
+ ! tailwindcss v3.4.16 | MIT License | https://tailwindcss.com
486
486
  *//*
487
487
  1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
488
488
  2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
@@ -1981,12 +1981,12 @@ input.tab:checked + .tab-content,
1981
1981
  mask-repeat: no-repeat;
1982
1982
  -webkit-mask-position: center;
1983
1983
  mask-position: center;
1984
- -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
1985
- mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
1984
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
1985
+ mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
1986
1986
  }
1987
1987
  .loading-spinner {
1988
- -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
1989
- mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
1988
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
1989
+ mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
1990
1990
  }
1991
1991
  .loading-md {
1992
1992
  width: 1.5rem;
@@ -2083,6 +2083,9 @@ input.tab:checked + .tab-content,
2083
2083
  margin-right: calc(0.5rem * var(--tw-space-x-reverse));
2084
2084
  margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
2085
2085
  }
2086
+ .modal-action:where([dir="rtl"], [dir="rtl"] *) > :not([hidden]) ~ :not([hidden]) {
2087
+ --tw-space-x-reverse: 1;
2088
+ }
2086
2089
  @keyframes modal-pop {
2087
2090
 
2088
2091
  0% {
@@ -3148,6 +3151,9 @@ input.tab:checked + .tab-content,
3148
3151
  .list-inside {
3149
3152
  list-style-position: inside;
3150
3153
  }
3154
+ .list-outside {
3155
+ list-style-position: outside;
3156
+ }
3151
3157
  .list-disc {
3152
3158
  list-style-type: disc;
3153
3159
  }
package/dist/util.d.ts CHANGED
@@ -1,20 +1,14 @@
1
- /**
2
- * A date range option that can be used in the `gs-date-range-selector` component.
3
- */
4
- export declare type DateRangeOption = {
5
- /** The label of the date range option that will be shown to the user */
6
- label: string;
7
- /**
8
- * The start date of the date range in the format `YYYY-MM-DD`.
9
- * If not set, the date range selector will default to the `earliestDate` property.
10
- */
11
- dateFrom?: string;
12
- /**
13
- * The end date of the date range in the format `YYYY-MM-DD`.
14
- * If not set, the date range selector will default to the current date.
15
- */
16
- dateTo?: string;
17
- };
1
+ import { default as default_2 } from 'zod';
2
+
3
+ export declare type AxisMax = default_2.infer<typeof axisMaxSchema>;
4
+
5
+ declare const axisMaxSchema: default_2.ZodUnion<[default_2.ZodLiteral<"maxInData">, default_2.ZodLiteral<"limitTo1">, default_2.ZodNumber]>;
6
+
7
+ export declare type ConfidenceIntervalMethod = default_2.infer<typeof confidenceIntervalMethodSchema>;
8
+
9
+ declare const confidenceIntervalMethodSchema: default_2.ZodUnion<[default_2.ZodLiteral<"wilson">, default_2.ZodLiteral<"none">]>;
10
+
11
+ export declare type DateRangeOption = default_2.infer<typeof dateRangeOptionSchema>;
18
12
 
19
13
  export declare class DateRangeOptionChangedEvent extends CustomEvent<DateRangeSelectOption> {
20
14
  constructor(detail: DateRangeSelectOption);
@@ -53,11 +47,108 @@ export declare const dateRangeOptionPresets: {
53
47
  };
54
48
  };
55
49
 
50
+ /**
51
+ * A date range option that can be used in the `gs-date-range-selector` component.
52
+ */
53
+ declare const dateRangeOptionSchema: default_2.ZodObject<{
54
+ /** The label of the date range option that will be shown to the user */
55
+ label: default_2.ZodString;
56
+ /**
57
+ * The start date of the date range in the format `YYYY-MM-DD`.
58
+ * If not set, the date range selector will default to the `earliestDate` property.
59
+ */
60
+ dateFrom: default_2.ZodOptional<default_2.ZodString>;
61
+ /**
62
+ * The end date of the date range in the format `YYYY-MM-DD`.
63
+ * If not set, the date range selector will default to the current date.
64
+ */
65
+ dateTo: default_2.ZodOptional<default_2.ZodString>;
66
+ }, "strip", default_2.ZodTypeAny, {
67
+ label: string;
68
+ dateFrom?: string | undefined;
69
+ dateTo?: string | undefined;
70
+ }, {
71
+ label: string;
72
+ dateFrom?: string | undefined;
73
+ dateTo?: string | undefined;
74
+ }>;
75
+
56
76
  export declare type DateRangeSelectOption = string | {
57
77
  dateFrom: string;
58
78
  dateTo: string;
59
79
  };
60
80
 
81
+ export declare type LapisFilter = default_2.infer<typeof lapisFilterSchema>;
82
+
83
+ declare const lapisFilterSchema: default_2.ZodRecord<default_2.ZodString, default_2.ZodUnion<[default_2.ZodString, default_2.ZodNumber, default_2.ZodNull, default_2.ZodBoolean]>>;
84
+
85
+ export declare type MutationComparisonView = default_2.infer<typeof mutationComparisonViewSchema>;
86
+
87
+ declare const mutationComparisonViewSchema: default_2.ZodUnion<[default_2.ZodLiteral<"table">, default_2.ZodLiteral<"venn">]>;
88
+
89
+ export declare type NamedLapisFilter = default_2.infer<typeof namedLapisFilterSchema>;
90
+
91
+ declare const namedLapisFilterSchema: default_2.ZodObject<{
92
+ lapisFilter: default_2.ZodRecord<default_2.ZodString, default_2.ZodUnion<[default_2.ZodString, default_2.ZodNumber, default_2.ZodNull, default_2.ZodBoolean]>>;
93
+ displayName: default_2.ZodString;
94
+ }, "strip", default_2.ZodTypeAny, {
95
+ lapisFilter: Record<string, string | number | boolean | null>;
96
+ displayName: string;
97
+ }, {
98
+ lapisFilter: Record<string, string | number | boolean | null>;
99
+ displayName: string;
100
+ }>;
101
+
102
+ export declare type SelectedMutationFilterStrings = default_2.infer<typeof selectedMutationFilterStringsSchema>;
103
+
104
+ declare const selectedMutationFilterStringsSchema: default_2.ZodObject<{
105
+ nucleotideMutations: default_2.ZodArray<default_2.ZodString, "many">;
106
+ aminoAcidMutations: default_2.ZodArray<default_2.ZodString, "many">;
107
+ nucleotideInsertions: default_2.ZodArray<default_2.ZodString, "many">;
108
+ aminoAcidInsertions: default_2.ZodArray<default_2.ZodString, "many">;
109
+ }, "strip", default_2.ZodTypeAny, {
110
+ nucleotideMutations: string[];
111
+ aminoAcidMutations: string[];
112
+ nucleotideInsertions: string[];
113
+ aminoAcidInsertions: string[];
114
+ }, {
115
+ nucleotideMutations: string[];
116
+ aminoAcidMutations: string[];
117
+ nucleotideInsertions: string[];
118
+ aminoAcidInsertions: string[];
119
+ }>;
120
+
121
+ export declare type SequenceType = default_2.infer<typeof sequenceTypeSchema>;
122
+
123
+ declare const sequenceTypeSchema: default_2.ZodUnion<[default_2.ZodLiteral<"nucleotide">, default_2.ZodLiteral<"amino acid">]>;
124
+
125
+ export declare type TemporalGranularity = default_2.infer<typeof temporalGranularitySchema>;
126
+
127
+ declare const temporalGranularitySchema: default_2.ZodUnion<[default_2.ZodLiteral<"day">, default_2.ZodLiteral<"week">, default_2.ZodLiteral<"month">, default_2.ZodLiteral<"year">]>;
128
+
129
+ export declare const views: {
130
+ readonly table: "table";
131
+ readonly venn: "venn";
132
+ readonly grid: "grid";
133
+ readonly insertions: "insertions";
134
+ readonly bar: "bar";
135
+ readonly line: "line";
136
+ readonly bubble: "bubble";
137
+ };
138
+
139
+ export declare type YAxisMaxConfig = default_2.infer<typeof yAxisMaxConfigSchema>;
140
+
141
+ declare const yAxisMaxConfigSchema: default_2.ZodObject<{
142
+ linear: default_2.ZodOptional<default_2.ZodUnion<[default_2.ZodLiteral<"maxInData">, default_2.ZodLiteral<"limitTo1">, default_2.ZodNumber]>>;
143
+ logarithmic: default_2.ZodOptional<default_2.ZodUnion<[default_2.ZodLiteral<"maxInData">, default_2.ZodLiteral<"limitTo1">, default_2.ZodNumber]>>;
144
+ }, "strip", default_2.ZodTypeAny, {
145
+ linear?: number | "maxInData" | "limitTo1" | undefined;
146
+ logarithmic?: number | "maxInData" | "limitTo1" | undefined;
147
+ }, {
148
+ linear?: number | "maxInData" | "limitTo1" | undefined;
149
+ logarithmic?: number | "maxInData" | "limitTo1" | undefined;
150
+ }>;
151
+
61
152
  export { }
62
153
 
63
154
 
@@ -150,7 +241,7 @@ declare global {
150
241
 
151
242
  declare global {
152
243
  interface HTMLElementTagNameMap {
153
- 'gs-aggregate-component': AggregateComponent;
244
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
154
245
  }
155
246
  }
156
247
 
@@ -158,7 +249,7 @@ declare global {
158
249
  declare global {
159
250
  namespace JSX {
160
251
  interface IntrinsicElements {
161
- 'gs-aggregate-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
252
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
162
253
  }
163
254
  }
164
255
  }
@@ -166,7 +257,7 @@ declare global {
166
257
 
167
258
  declare global {
168
259
  interface HTMLElementTagNameMap {
169
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
260
+ 'gs-aggregate': AggregateComponent;
170
261
  }
171
262
  }
172
263
 
@@ -174,7 +265,7 @@ declare global {
174
265
  declare global {
175
266
  namespace JSX {
176
267
  interface IntrinsicElements {
177
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
268
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
178
269
  }
179
270
  }
180
271
  }
package/dist/util.js CHANGED
@@ -1,6 +1,7 @@
1
- import { D, d } from "./utilEntrypoint-g4DsyhU7.js";
1
+ import { D, d, v } from "./dateRangeOption-DjtcAEWq.js";
2
2
  export {
3
3
  D as DateRangeOptionChangedEvent,
4
- d as dateRangeOptionPresets
4
+ d as dateRangeOptionPresets,
5
+ v as views
5
6
  };
6
7
  //# sourceMappingURL=util.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -107,7 +107,7 @@
107
107
  "@storybook/preact": "^8.0.9",
108
108
  "@storybook/preact-vite": "^8.0.9",
109
109
  "@storybook/test": "^8.0.0",
110
- "@storybook/test-runner": "^0.19.0",
110
+ "@storybook/test-runner": "^0.20.1",
111
111
  "@storybook/types": "^8.0.9",
112
112
  "@storybook/web-components": "^8.0.9",
113
113
  "@storybook/web-components-vite": "^8.0.9",
@@ -5,6 +5,7 @@ import aggregatedData from './__mockData__/aggregated.json';
5
5
  import { Aggregate, type AggregateProps } from './aggregate';
6
6
  import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
7
7
  import { LapisUrlContext } from '../LapisUrlContext';
8
+ import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectInvalidAttributesErrorMessage';
8
9
 
9
10
  const meta: Meta<AggregateProps> = {
10
11
  title: 'Visualization/Aggregate',
@@ -94,3 +95,16 @@ export const FailsLoadingData: StoryObj<AggregateProps> = {
94
95
  });
95
96
  },
96
97
  };
98
+
99
+ export const WithEmptyFieldString: StoryObj<AggregateProps> = {
100
+ ...Default,
101
+ args: {
102
+ ...Default.args,
103
+ fields: [''],
104
+ },
105
+ play: async ({ canvasElement, step }) => {
106
+ step('expect error message', async () => {
107
+ await expectInvalidAttributesErrorMessage(canvasElement, 'String must contain at least 1 character(s)');
108
+ });
109
+ },
110
+ };
@@ -1,9 +1,10 @@
1
1
  import { type FunctionComponent } from 'preact';
2
2
  import { useContext } from 'preact/hooks';
3
+ import z from 'zod';
3
4
 
4
5
  import { AggregateTable } from './aggregate-table';
5
6
  import { type AggregateData, queryAggregateData } from '../../query/queryAggregateData';
6
- import { type LapisFilter } from '../../types';
7
+ import { lapisFilterSchema, views } from '../../types';
7
8
  import { LapisUrlContext } from '../LapisUrlContext';
8
9
  import { CsvDownloadButton } from '../components/csv-download-button';
9
10
  import { ErrorBoundary } from '../components/error-boundary';
@@ -15,26 +16,27 @@ import { ResizeContainer } from '../components/resize-container';
15
16
  import Tabs from '../components/tabs';
16
17
  import { useQuery } from '../useQuery';
17
18
 
18
- export type View = 'table';
19
- export type InitialSort = { field: string; direction: 'ascending' | 'descending' };
20
-
21
- export type AggregateProps = {
22
- width: string;
23
- height: string;
24
- filter: LapisFilter;
25
- fields: string[];
26
- views: View[];
27
- initialSortField: string;
28
- initialSortDirection: 'ascending' | 'descending';
29
- pageSize: boolean | number;
30
- };
19
+ const viewSchema = z.literal(views.table);
20
+ export type View = z.infer<typeof viewSchema>;
21
+
22
+ const aggregatePropsSchema = z.object({
23
+ filter: lapisFilterSchema,
24
+ fields: z.array(z.string().min(1)),
25
+ views: z.array(viewSchema),
26
+ initialSortField: z.string(),
27
+ initialSortDirection: z.union([z.literal('ascending'), z.literal('descending')]),
28
+ pageSize: z.union([z.boolean(), z.number()]),
29
+ width: z.string(),
30
+ height: z.string(),
31
+ });
32
+ export type AggregateProps = z.infer<typeof aggregatePropsSchema>;
31
33
 
32
34
  export const Aggregate: FunctionComponent<AggregateProps> = (componentProps) => {
33
35
  const { width, height } = componentProps;
34
36
  const size = { height, width };
35
37
 
36
38
  return (
37
- <ErrorBoundary size={size}>
39
+ <ErrorBoundary size={size} schema={aggregatePropsSchema} componentProps={componentProps}>
38
40
  <ResizeContainer size={size}>
39
41
  <AggregateInner {...componentProps} />
40
42
  </ResizeContainer>
@@ -54,7 +54,11 @@ export const ColorScaleSelector: FunctionComponent<ColorScaleSelectorProps> = ({
54
54
  );
55
55
  };
56
56
 
57
- export const getColorWithingScale = (value: number, colorScale: ColorScale) => {
57
+ export const getColorWithingScale = (value: number | undefined, colorScale: ColorScale) => {
58
+ if (value === undefined) {
59
+ return 'lightgrey';
60
+ }
61
+
58
62
  if (colorScale.min === colorScale.max) {
59
63
  return singleGraphColorRGBByName(colorScale.color, 0);
60
64
  }
@@ -66,8 +70,8 @@ export const getColorWithingScale = (value: number, colorScale: ColorScale) => {
66
70
  return singleGraphColorRGBByName(colorScale.color, alpha);
67
71
  };
68
72
 
69
- export const getTextColorForScale = (value: number, colorScale: ColorScale) => {
70
- if (colorScale.min === colorScale.max) {
73
+ export const getTextColorForScale = (value: number | undefined, colorScale: ColorScale) => {
74
+ if (value === undefined || colorScale.min === colorScale.max) {
71
75
  return 'black';
72
76
  }
73
77
 
@@ -1,5 +1,6 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/preact';
2
2
  import { expect, waitFor, within } from '@storybook/test';
3
+ import z from 'zod';
3
4
 
4
5
  import { ErrorBoundary } from './error-boundary';
5
6
  import { UserFacingError } from './error-display';
@@ -21,9 +22,15 @@ const meta: Meta = {
21
22
 
22
23
  export default meta;
23
24
 
25
+ const someSchema = z.object({
26
+ test: z.string().min(1),
27
+ });
28
+ const someValidProps = { test: 'someValue' };
29
+ const someInvalidProps = { test: '' };
30
+
24
31
  export const ErrorBoundaryWithoutErrorStory: StoryObj = {
25
32
  render: (args) => (
26
- <ErrorBoundary size={args.size}>
33
+ <ErrorBoundary size={args.size} schema={someSchema} componentProps={someValidProps}>
27
34
  <div>Some content</div>
28
35
  </ErrorBoundary>
29
36
  ),
@@ -36,7 +43,7 @@ export const ErrorBoundaryWithoutErrorStory: StoryObj = {
36
43
 
37
44
  export const ErrorBoundaryWithErrorStory: StoryObj = {
38
45
  render: (args) => (
39
- <ErrorBoundary size={args.size}>
46
+ <ErrorBoundary size={args.size} schema={someSchema} componentProps={someValidProps}>
40
47
  <ContentThatThrowsError error={() => new Error('Some error')} />
41
48
  </ErrorBoundary>
42
49
  ),
@@ -48,9 +55,23 @@ export const ErrorBoundaryWithErrorStory: StoryObj = {
48
55
  },
49
56
  };
50
57
 
58
+ export const ErrorBoundaryWithParsingErrorStory: StoryObj = {
59
+ render: (args) => (
60
+ <ErrorBoundary size={args.size} schema={someSchema} componentProps={someInvalidProps}>
61
+ <ContentThatThrowsError error={() => new Error('Some error')} />
62
+ </ErrorBoundary>
63
+ ),
64
+ play: async ({ canvasElement }) => {
65
+ const canvas = within(canvasElement);
66
+ const content = canvas.queryByText('Some content.', { exact: false });
67
+ await waitFor(() => expect(content).not.toBeInTheDocument());
68
+ await waitFor(() => expect(canvas.getByText('Error - Invalid component attributes')).toBeInTheDocument());
69
+ },
70
+ };
71
+
51
72
  export const ErrorBoundaryWithUserFacingErrorStory: StoryObj = {
52
73
  render: (args) => (
53
- <ErrorBoundary size={args.size}>
74
+ <ErrorBoundary size={args.size} schema={someSchema} componentProps={someValidProps}>
54
75
  <ContentThatThrowsError error={() => new UserFacingError('Error Headline', 'Some error')} />
55
76
  </ErrorBoundary>
56
77
  ),
@@ -1,16 +1,26 @@
1
- import type { FunctionComponent } from 'preact';
2
- import { useErrorBoundary } from 'preact/hooks';
1
+ import { type RenderableProps } from 'preact';
2
+ import { useErrorBoundary, useMemo } from 'preact/hooks';
3
+ import { type ZodSchema } from 'zod';
3
4
 
4
- import { ErrorDisplay, type ErrorDisplayProps } from './error-display';
5
+ import { ErrorDisplay, type ErrorDisplayProps, InvalidPropsError } from './error-display';
5
6
  import { ResizeContainer, type Size } from './resize-container';
6
7
 
7
- type ErrorBoundaryProps = {
8
+ type ErrorBoundaryProps<T> = {
8
9
  size: Size;
10
+ componentProps: T;
11
+ schema: ZodSchema<T>;
9
12
  layout?: ErrorDisplayProps['layout'];
10
13
  };
11
14
 
12
- export const ErrorBoundary: FunctionComponent<ErrorBoundaryProps> = ({ size, layout, children }) => {
15
+ export const ErrorBoundary = <T extends Record<string, unknown>>({
16
+ size,
17
+ layout,
18
+ componentProps,
19
+ schema,
20
+ children,
21
+ }: RenderableProps<ErrorBoundaryProps<T>>) => {
13
22
  const [internalError, resetError] = useErrorBoundary();
23
+ const componentPropsParseError = useCheckComponentProps(schema, componentProps);
14
24
 
15
25
  if (internalError) {
16
26
  return (
@@ -20,5 +30,28 @@ export const ErrorBoundary: FunctionComponent<ErrorBoundaryProps> = ({ size, lay
20
30
  );
21
31
  }
22
32
 
33
+ if (componentPropsParseError !== undefined) {
34
+ return (
35
+ <ResizeContainer size={size}>
36
+ <ErrorDisplay error={componentPropsParseError} layout={layout} />
37
+ </ResizeContainer>
38
+ );
39
+ }
40
+
23
41
  return <>{children}</>;
24
42
  };
43
+
44
+ function useCheckComponentProps<T extends Record<string, unknown>>(schema: ZodSchema<T>, componentProps: T) {
45
+ return useMemo(() => {
46
+ if (schema === undefined || componentProps === undefined) {
47
+ return undefined;
48
+ }
49
+
50
+ const parseResult = schema.safeParse(componentProps);
51
+ if (parseResult.success) {
52
+ return undefined;
53
+ }
54
+
55
+ return new InvalidPropsError(parseResult.error, componentProps);
56
+ }, [componentProps, schema]);
57
+ }