uhuu-components 0.3.11 → 0.4.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/index.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import type {
3
3
  ComponentType,
4
4
  CSSProperties,
5
+ HTMLAttributes,
5
6
  ImgHTMLAttributes,
6
7
  Key,
7
8
  MouseEventHandler,
@@ -33,6 +34,9 @@ export interface StaticFlowItemLines {
33
34
  lines: Array<{ top: number; bottom: number; start: number; hyphen?: boolean }>;
34
35
  end: number;
35
36
  justify: boolean;
37
+ /** The text's computed CSS `orphans` / `widows`, where the browser reports them; the item's meta wins. */
38
+ orphans?: number;
39
+ widows?: number;
36
40
  }
37
41
 
38
42
  /** One piece of an item split between the lines of its `Static.FlowText`. */
@@ -184,8 +188,11 @@ export interface StaticFlowMeasureApi {
184
188
  getEffectiveScale: (element: HTMLElement) => number;
185
189
  /** Border-box height plus vertical margins, in layout space. */
186
190
  getOuterHeight: (element: HTMLElement, scale?: number) => number;
191
+ /** @internal Library measurement detail; retained for compatibility. */
187
192
  readItemMeta: (element: HTMLElement) => StaticFlowItemMeta;
193
+ /** @internal Library measurement detail; retained for compatibility. */
188
194
  readHeaderGroupKey: (element: HTMLElement) => string | undefined;
195
+ /** @internal Library measurement detail; retained for compatibility. */
189
196
  readHeaderGroupRepeats: (itemElements: HTMLElement[]) => Record<string, boolean>;
190
197
  /** `unwrapped`: heights from `readUnwrappedHeights`, used for the headers it covers. */
191
198
  readHeaderGroupHeights: (
@@ -197,21 +204,44 @@ export interface StaticFlowMeasureApi {
197
204
  * Heights of the items and group headers an `asChild` Flow renders as direct children of its
198
205
  * root, from positions: margins collapse without a wrapper, so each item is the distance from
199
206
  * the bottom of the element before it (the area's top for the first) to its own bottom.
207
+ * @internal Library measurement detail; retained for compatibility.
200
208
  */
201
209
  readUnwrappedHeights: (area: HTMLElement, flow: HTMLElement, scale?: number) => Map<HTMLElement, number>;
202
210
  /** Every measured leaf under `root`, in document order. */
203
211
  readFlowItemElements: (root: HTMLElement) => HTMLElement[];
212
+ /**
213
+ * The first of `items` that does not lie below the item before it in `area` (beside it, in
214
+ * another column), or `null`: such a flow cannot be planned (`unstacked-flow-items`).
215
+ * @internal Library measurement detail; retained for compatibility.
216
+ */
217
+ readUnstackedItem: (area: HTMLElement, flow: HTMLElement, items: HTMLElement[]) => HTMLElement | null;
218
+ /**
219
+ * A CSS length with an absolute unit (`"20mm"`, `"12px"`) or a number of px, in CSS px;
220
+ * `undefined` for anything else. What `breakReserve` is read with.
221
+ * @internal Library measurement detail; retained for compatibility.
222
+ */
223
+ parseAbsoluteLength: (value: number | string | undefined) => number | undefined;
224
+ /** @internal Library measurement detail; retained for compatibility. */
204
225
  parseKeepWithNext: (value: string | undefined) => boolean | number;
226
+ /** @internal Library measurement detail; retained for compatibility. */
205
227
  serializeKeepWithNext: (value: StaticFlowItemMeta['keepWithNext']) => string | undefined;
206
- /** `orphans` / `widows` attribute values: a positive whole number, else `undefined`. */
228
+ /**
229
+ * `orphans` / `widows` attribute values: a positive whole number, else `undefined`.
230
+ * @internal Library measurement detail; retained for compatibility.
231
+ */
207
232
  parseLineCount: (value: string | undefined) => number | undefined;
233
+ /** @internal Library measurement detail; retained for compatibility. */
208
234
  serializeLineCount: (value: number | undefined) => string | undefined;
209
- /** The `Static.FlowText` elements of `item` itself (not those of a Flow nested in it). */
235
+ /**
236
+ * The `Static.FlowText` elements of `item` itself (not those of a Flow nested in it).
237
+ * @internal Library measurement detail; retained for compatibility.
238
+ */
210
239
  readItemFlowTexts: (item: HTMLElement) => HTMLElement[];
211
240
  /**
212
241
  * The lines of the one `Static.FlowText` in a measured item, for `planFlowChunks({ lines })`, or
213
242
  * `undefined` when it has none or cannot split (`report` says why). `height` is the item's
214
243
  * measured height (`getOuterHeight`, or `readUnwrappedHeights` for `asChild`).
244
+ * @internal Library measurement detail; retained for compatibility.
215
245
  */
216
246
  readFlowTextLines: (
217
247
  item: HTMLElement,
@@ -219,7 +249,10 @@ export interface StaticFlowMeasureApi {
219
249
  scale?: number,
220
250
  report?: (issue: StaticFlowTextIssue, element: HTMLElement) => void
221
251
  ) => StaticFlowItemLines | undefined;
222
- /** FNV-1a. For opaque change-detection tokens compared only for equality. */
252
+ /**
253
+ * FNV-1a. For opaque change-detection tokens compared only for equality.
254
+ * @internal Library measurement detail; retained for compatibility.
255
+ */
223
256
  hashString: (value: string) => string;
224
257
  }
225
258
 
@@ -243,6 +276,12 @@ export interface StaticFlowPlanInput {
243
276
  * Without `lines` the plan is item-level. `planFlowColumnChunks` does not split.
244
277
  */
245
278
  lines?: Array<StaticFlowItemLines | undefined>;
279
+ /**
280
+ * Room kept free at the bottom of every page the flow breaks from, in px; the page it ends on
281
+ * keeps its full region, so a flow that fits one page plans as without it. 0 by default; a
282
+ * reserve of a page or more is ignored. `planFlowColumnChunks` does not take it.
283
+ */
284
+ breakReserve?: number;
246
285
  }
247
286
 
248
287
  /** Input for `Static.planFlowColumnChunks`, using globally indexed leaf measurements. */
@@ -620,8 +659,10 @@ export interface PageEditorItem {
620
659
  }
621
660
 
622
661
  export interface PageEditorState {
623
- items?: PageEditorItem[];
624
- [key: string]: unknown;
662
+ key: string;
663
+ items: PageItem[];
664
+ totalPages: number;
665
+ updatedAt: string;
625
666
  }
626
667
 
627
668
  /**
@@ -629,7 +670,7 @@ export interface PageEditorState {
629
670
  * page-option values) and the sheet being rendered. A `hasFlow` page renders one sheet per flow
630
671
  * chunk; its continuation sheets share the page's `id`.
631
672
  */
632
- export interface PageEditorPage {
673
+ export interface PageEditorPage extends StandalonePage {
633
674
  id: string;
634
675
  componentKey?: string;
635
676
  templateId?: string;
@@ -647,6 +688,22 @@ export interface PageEditorPage {
647
688
  [key: string]: unknown;
648
689
  }
649
690
 
691
+ /** Dialog paths and the resolved integration for one logical page. */
692
+ export interface PageDataBinding {
693
+ payload: any;
694
+ pageId: string;
695
+ pagePayload?: any;
696
+ parentGroupId?: string;
697
+ integration: { instanceId: string | null; data?: any; path: (subPath?: string) => string | null };
698
+ paths: {
699
+ integration: (subPath?: string) => string | null;
700
+ page: (subPath?: string) => string | null;
701
+ group: (subPath?: string) => string | null;
702
+ document: (subPath?: string) => string | null;
703
+ };
704
+ defaults: { imageGalleryPath?: string | null };
705
+ }
706
+
650
707
  /** The props PageEditor passes to a page component. */
651
708
  export interface PageComponentProps {
652
709
  payload?: Record<string, unknown>;
@@ -662,15 +719,15 @@ export interface PageComponentProps {
662
719
  /** Sheets in the flow-expanded document. */
663
720
  totalPages?: number;
664
721
  page?: PageEditorPage;
665
- parentGroup?: PageEditorItem;
722
+ parentGroup?: PageGroup;
666
723
  /** Dialog path helpers and defaults (docs/page-data-binding.md). */
667
- dataBinding?: Record<string, unknown>;
724
+ dataBinding?: PageDataBinding;
668
725
  /** Set while the page is a panel of a perfect-binding cover spread. */
669
726
  spread?: SpreadInfo;
670
727
  }
671
728
 
672
729
  export interface PageFilterConfig {
673
- mode?: "all" | "cover" | "text" | "custom" | string;
730
+ mode?: "all" | "cover" | "text" | "custom";
674
731
  coverPageCount?: number;
675
732
  ranges?: Array<{ start: number; end: number }>;
676
733
  [key: string]: unknown;
@@ -713,35 +770,160 @@ export interface PageManagementOptions {
713
770
  rename?: boolean;
714
771
  }
715
772
 
773
+ /** A page or group the shell supplies to page-option callbacks. */
774
+ export type PageItem = StandalonePage | PageGroup;
775
+
776
+ export interface StandalonePage {
777
+ kind: "page";
778
+ id: string;
779
+ componentKey?: string;
780
+ dataKey?: string;
781
+ templateId?: string;
782
+ label?: LocalizedText;
783
+ className?: string;
784
+ hasFlow?: boolean;
785
+ repeatable?: boolean;
786
+ maxInstances?: number | null;
787
+ integration?: "required" | "optional" | "none" | { mode?: "required" | "optional" | "none" };
788
+ pageNum?: number;
789
+ component?: ComponentType<any>;
790
+ [key: string]: any;
791
+ }
792
+
793
+ export interface PageGroup {
794
+ kind: "group";
795
+ id: string;
796
+ templateId?: string;
797
+ repeatable?: boolean;
798
+ maxInstances?: number | null;
799
+ integration?: StandalonePage["integration"];
800
+ label?: LocalizedText;
801
+ pages: Array<Omit<StandalonePage, "pageNum" | "kind"> & { id: string }>;
802
+ pageNum?: never;
803
+ [key: string]: any;
804
+ }
805
+
806
+ /** Page blueprint in `templateConfig.pages`; metadata applies whenever that layout is used. */
807
+ export interface PageTemplateDefinition {
808
+ label?: LocalizedText;
809
+ className?: string;
810
+ componentKey?: string;
811
+ dataKey?: string;
812
+ component?: ComponentType<any>;
813
+ hasFlow?: boolean;
814
+ repeatable?: boolean;
815
+ maxInstances?: number | null;
816
+ thumbnail?: string;
817
+ integration?: StandalonePage["integration"];
818
+ allowAsSinglePage?: boolean;
819
+ strictPosition?: "start" | "end";
820
+ }
821
+
822
+ export interface PageComponentKeysContext {
823
+ payload: any;
824
+ item?: PageGroup;
825
+ parent?: PageItem;
826
+ }
827
+
828
+ /** Group blueprint; record keys provide `id`, array and inline definitions supply it themselves. */
829
+ export interface GroupDefinition {
830
+ id: string;
831
+ label?: LocalizedText;
832
+ thumbnail?: string;
833
+ repeatable?: boolean;
834
+ maxInstances?: number | null;
835
+ pageComponentKeys?: string[] | ((context: PageComponentKeysContext) => string[]);
836
+ integration?: StandalonePage["integration"];
837
+ strictPosition?: "start" | "end";
838
+ }
839
+
840
+ export interface PageEditorTemplateConfig {
841
+ initial: Array<string | (Partial<StandalonePage> & { id: string }) | GroupDefinition>;
842
+ pageManagement?: PageManagementOptions;
843
+ groups?: GroupDefinition[] | Record<string, Omit<GroupDefinition, "id"> & { id?: string }>;
844
+ pageComponentKeys?: string[];
845
+ pages?: Record<string, PageTemplateDefinition>;
846
+ pageComponents?: Record<string, ComponentType<any>>;
847
+ spine?: PageEditorSpineConfig;
848
+ payload?: any;
849
+ /** Template-owned metadata may accompany the shell configuration. */
850
+ [key: string]: unknown;
851
+ }
852
+
853
+ export interface PageOptionSelectValue {
854
+ label: LocalizedText;
855
+ value: string;
856
+ hex?: string;
857
+ }
858
+
859
+ type PageOptionTarget = string | ((item: PageItem) => boolean);
860
+ type BasePageOption = {
861
+ id: string;
862
+ label: LocalizedText;
863
+ description?: LocalizedText;
864
+ appliesTo?: PageOptionTarget | PageOptionTarget[];
865
+ onChange?: (id: string, value: string, context: { item?: PageItem; updateItem: (patch: Partial<PageItem>) => void }) => void;
866
+ applyPatch?: (value: string, item?: PageItem) => Partial<PageItem> | void;
867
+ };
868
+
869
+ export type SelectPageOption = BasePageOption & {
870
+ type: "select";
871
+ options: PageOptionSelectValue[];
872
+ getValue: (item: PageItem) => string | number | boolean;
873
+ };
874
+ export type TogglePageOption = BasePageOption & { type: "toggle"; getValue: (item: PageItem) => boolean };
875
+ export type SliderPageOption = BasePageOption & {
876
+ type: "slider"; min: number; max: number; step: number; getValue: (item: PageItem) => number;
877
+ };
878
+ export type CounterPageOption = BasePageOption & {
879
+ type: "counter"; min: number; max: number; step: number; getValue: (item: PageItem) => number;
880
+ };
881
+ export type ColorSeriesPageOption = BasePageOption & {
882
+ type: "color-series"; options: PageOptionSelectValue[]; getValue: (item: PageItem) => string;
883
+ };
884
+ export type PageOption = SelectPageOption | TogglePageOption | SliderPageOption | CounterPageOption | ColorSeriesPageOption;
885
+
886
+ type PayloadOptionFor<Option> = Option extends PageOption
887
+ ? Omit<Option, "getValue" | "onChange" | "applyPatch"> & { defaultValue?: any; field?: string }
888
+ : never;
889
+ /** A page option stored automatically in `payload.pages`, without custom callbacks. */
890
+ export type PayloadOptionDefinition = PayloadOptionFor<PageOption>;
891
+
716
892
  export interface PageEditorProps {
717
893
  /** Pages, groups and initial items; `pageManagement` sets the page-management controls. */
718
- templateConfig: Record<string, unknown> & { pageManagement?: PageManagementOptions };
894
+ templateConfig: PageEditorTemplateConfig;
719
895
  payload?: Record<string, unknown>;
720
896
  onPayloadChange?: (nextPayload: Record<string, unknown>) => void;
721
897
  pageFormat?: {
722
898
  width: number;
723
899
  height: number;
724
- preview?: string;
900
+ preview?: "single_page" | "two_pages";
725
901
  bleed?: number;
726
902
  /** Perfect-binding spine/glue; only applied while `pageFilter.mode === 'cover'`. */
727
903
  binding?: BindingConfig | null;
728
904
  [key: string]: unknown;
729
905
  };
730
- pageOptions?: unknown[];
906
+ pageOptions?: Array<PageOption | PayloadOptionDefinition>;
731
907
  pageFilter?: PageFilterConfig;
732
908
  printConfigs?: PrintConfigMap;
733
909
  defaultZoomMode?: PageEditorDefaultZoomMode;
734
- onItemsChange?: (items: PageEditorItem[], state: PageEditorState) => void;
910
+ onItemsChange?: (items: PageItem[], state: PageEditorState) => void;
735
911
  onStateChange?: (state: PageEditorState) => void;
736
912
  /**
737
913
  * What prints over every page. Default: the shell's page number ("Page N / total", untranslated).
738
914
  * `false` or `null`: nothing, for templates that number their own pages.
739
915
  */
916
+ notifyError?: (message: string) => void;
917
+ referenceRenderer?: (page: StandalonePage & { pageNum: number }) => ReactNode;
918
+ renderPage?: (args: { page: StandalonePage & { pageNum: number }; parent?: PageItem }) => ReactNode;
919
+ menuItems?: ReactNode;
920
+ gridColsClass?: string;
921
+ resolveNewItem?: (draft: PageItem) => Promise<PageItem | null | void> | PageItem | null | void;
740
922
  renderOverlay?: false | null | ((context: {
741
- pageNo?: number;
742
- total?: number;
743
- pageId?: string;
744
- parent?: unknown;
923
+ pageNo: number;
924
+ total: number;
925
+ pageId: string;
926
+ parent?: PageItem;
745
927
  }) => ReactNode);
746
928
  reorderTitle?: LocalizedText;
747
929
  reorderDescription?: LocalizedText;
@@ -750,12 +932,18 @@ export interface PageEditorProps {
750
932
  activeBrandKitId?: string;
751
933
  onSelectBrandKit?: (id: string) => void;
752
934
  onAddBrandKit?: (input: string) => void;
753
- [key: string]: unknown;
754
935
  }
755
936
 
937
+ /** Template-owned JSON data and the maps the editor shell updates. */
938
+ export type TemplatePayload = Record<string, unknown> & {
939
+ pages?: Record<string, Record<string, unknown> | undefined>;
940
+ groups?: Record<string, Record<string, unknown> | undefined>;
941
+ integrations?: Record<string, unknown>;
942
+ };
943
+
756
944
  export interface TemplateDataProviderProps {
757
- payload?: Record<string, unknown>;
758
- onPayloadChange?: (nextPayload: Record<string, unknown>) => void;
945
+ payload?: TemplatePayload;
946
+ onPayloadChange?: (nextPayload: TemplatePayload) => void;
759
947
  children: ReactNode;
760
948
  stateKey?: string;
761
949
  }
@@ -775,12 +963,60 @@ export interface IntegrationAdapter {
775
963
  [key: string]: unknown;
776
964
  }
777
965
 
966
+ /**
967
+ * One document-level option: a top-level payload key the template reads (`payload.language`).
968
+ * The shape `uhuu templates theme inspect <templateId> --snippet` prints from a legacy template
969
+ * theme. See docs/document-options.md.
970
+ */
971
+ export interface DocumentOptionDefinition {
972
+ /** The top-level payload key, the same one the platform's theme option wrote. */
973
+ key: string;
974
+ /** Default `select`. */
975
+ type?: "select" | "toggle";
976
+ label: LocalizedText;
977
+ description?: LocalizedText;
978
+ /** A select's choices. A stored value matches a choice as a string: `2` matches `"2"`. */
979
+ options?: Array<{ value: string | number | boolean; label: LocalizedText }>;
980
+ /** Shown while the payload has no value for the key (`payload[key] ?? defaultValue`). */
981
+ defaultValue?: unknown;
982
+ /** Local onChange previews: default `string` for a select, `boolean` for a toggle. Editor selects save strings. */
983
+ valueType?: "string" | "number" | "boolean";
984
+ }
985
+
986
+ export interface DocumentOptionsProps {
987
+ /**
988
+ * The payload the template renders. Required outside a TemplateDataProvider; inside one, the
989
+ * provider's payload is used when it is left out.
990
+ */
991
+ payload?: Record<string, unknown> | null;
992
+ options: DocumentOptionDefinition[];
993
+ /** Heading of the control. Default: "Document options" in the shell's interface language. */
994
+ title?: LocalizedText;
995
+ /**
996
+ * Called for local choices when no supported $uhuu.editDialog is available. The editor field
997
+ * saves through the next payload update and does not call onChange.
998
+ */
999
+ onChange?: (key: string, value: unknown) => void;
1000
+ /**
1001
+ * Inside PageEditor's floating toolbar: the entry's wrapper. Otherwise the control's own
1002
+ * floating bar, bottom right by default (move it with `style`, e.g. `{ top: 16, bottom: "auto" }`).
1003
+ */
1004
+ className?: string;
1005
+ style?: CSSProperties;
1006
+ }
1007
+
778
1008
  export const EditorShell: {
779
1009
  TemplateDataProvider: ComponentType<TemplateDataProviderProps>;
780
1010
  PageEditor: ComponentType<PageEditorProps>;
781
1011
  InteractiveModeProvider: ComponentType<InteractiveModeProviderProps>;
782
1012
  useInteractive: () => InteractiveModeContextValue;
783
1013
  useIntegrationAdapter: (config: IntegrationAdapterConfig) => IntegrationAdapter;
1014
+ /**
1015
+ * Document-level switches for top-level payload keys, edited through the released editor's
1016
+ * `$uhuu.editDialog` field. Joins the nearest InteractiveModeProvider's PageEditor toolbar, else floats bottom right.
1017
+ * Hidden outside edit mode, in the PDF renderer and in print. See docs/document-options.md.
1018
+ */
1019
+ DocumentOptions: ComponentType<DocumentOptionsProps>;
784
1020
  };
785
1021
 
786
1022
  export interface StaticSheetProps {
@@ -816,7 +1052,7 @@ export interface SpineComponentProps {
816
1052
  /** Trim page height (mm); the strip is `height + 2 × bleed` tall and `spine` wide. */
817
1053
  height: number;
818
1054
  totalPages?: number;
819
- pages: { left?: PageEditorItem; right?: PageEditorItem };
1055
+ pages: { left?: StandalonePage; right?: StandalonePage };
820
1056
  }
821
1057
 
822
1058
  /** `templateConfig.spine` — optional artwork for the spine of a perfect-bound cover. */
@@ -869,6 +1105,13 @@ export interface StaticFlowAreaProps {
869
1105
  * and plans every later sheet with that height. Default: every sheet has the first one's height.
870
1106
  */
871
1107
  measureContinuation?: boolean;
1108
+ /**
1109
+ * Room kept free at the bottom of every sheet the flow continues from, so a break never sits on
1110
+ * the sheet's edge; the sheet the flow ends on keeps its full area, and a flow that fits one
1111
+ * sheet lays out as without it. A CSS length with an absolute unit (`"20mm"`, `"12px"`) or a
1112
+ * number of px. `Static.FlowColumns` does not keep it.
1113
+ */
1114
+ breakReserve?: number | string;
872
1115
  }
873
1116
 
874
1117
  export interface StaticFlowPageProps {
@@ -882,6 +1125,8 @@ export interface StaticFlowPageProps {
882
1125
  onFlowMeasurement?: (measurement: StaticFlowMeasurement) => void;
883
1126
  /** See `StaticFlowAreaProps.measureContinuation`. */
884
1127
  measureContinuation?: boolean;
1128
+ /** See `StaticFlowAreaProps.breakReserve`. */
1129
+ breakReserve?: number | string;
885
1130
  }
886
1131
 
887
1132
  export interface StaticFlowProps<TItem = unknown> {
@@ -926,7 +1171,7 @@ export interface StaticFlowProps<TItem = unknown> {
926
1171
  as?: keyof HTMLElementTagNameMap;
927
1172
  }
928
1173
 
929
- export interface StaticFlowTextProps {
1174
+ export interface StaticFlowTextProps extends HTMLAttributes<HTMLElement> {
930
1175
  /**
931
1176
  * A string (may contain `\n`) or inline content: text, `span`, `strong`, `em`, `b`, `i`, `a`,
932
1177
  * `small`, `sub`, `sup`, `br` (and other inline text elements) and fragments. Anything else
@@ -999,6 +1244,8 @@ export interface StaticFlowDocumentProps {
999
1244
  flowAreaStyle?: CSSProperties;
1000
1245
  /** See `StaticFlowAreaProps.measureContinuation`. */
1001
1246
  measureContinuation?: boolean;
1247
+ /** See `StaticFlowAreaProps.breakReserve`. */
1248
+ breakReserve?: number | string;
1002
1249
  id?: string;
1003
1250
  idPrefix?: string;
1004
1251
  flowClassName?: string;
@@ -1092,6 +1339,7 @@ export const Static: {
1092
1339
  FlowText: ComponentType<StaticFlowTextProps>;
1093
1340
  planFlowChunks: (input?: StaticFlowPlanInput) => StaticFlowChunk[];
1094
1341
  planFlowColumnChunks: (input?: StaticFlowColumnPlanInput) => StaticFlowChunk[];
1342
+ /** @internal Instrumentation for library tests; does not affect pagination plans. */
1095
1343
  createFlowPlanMetrics: () => StaticFlowPlanMetrics;
1096
1344
  flowMeasure: StaticFlowMeasureApi;
1097
1345
  FlowDocument: ComponentType<StaticFlowDocumentProps>;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.11",
2
+ "version": "0.4.0",
3
3
  "name": "uhuu-components",
4
4
  "main": "uhuu-components.umd.cjs",
5
5
  "module": "uhuu-components.es.js",