@phis/ui 0.4.3 → 0.4.4

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 (132) hide show
  1. package/components/controls/phi-anchor-nav-control.js +4 -1
  2. package/components/controls/phi-background-control.js +21 -6
  3. package/components/controls/phi-bullet-list-control.d.ts +7 -2
  4. package/components/controls/phi-bullet-list-control.js +4 -3
  5. package/components/controls/phi-color-control.js +4 -2
  6. package/components/controls/phi-drawer-control-adapter.js +3 -2
  7. package/components/controls/phi-inline-text-editor.js +7 -1
  8. package/components/controls/phi-length-control.js +1 -1
  9. package/components/controls/phi-ribbon-control.d.ts +13 -4
  10. package/components/controls/phi-ribbon-control.js +10 -6
  11. package/components/controls/phi-ribbon-wrap-control.d.ts +26 -0
  12. package/components/controls/phi-ribbon-wrap-control.js +25 -0
  13. package/components/controls/phi-select-control.js +5 -2
  14. package/components/controls/phi-series-chart-control.d.ts +48 -0
  15. package/components/controls/phi-series-chart-control.js +82 -0
  16. package/components/controls/phi-text-control.js +57 -3
  17. package/components/controls/phi-tree-control.js +1 -1
  18. package/components/controls/phi-truncated-hover-text.d.ts +15 -0
  19. package/components/controls/phi-truncated-hover-text.js +25 -0
  20. package/components/forms/link-target-form-control.d.ts +1 -1
  21. package/components/forms/link-target-form-control.js +2 -45
  22. package/components/forms/phi-form-widget-frame.js +4 -2
  23. package/components/layouts/clients/phi-three-column-layout-client.js +10 -1
  24. package/components/navigation/phi-link.js +6 -1
  25. package/components/regions/clients/cms-region-container-client.js +6 -1
  26. package/components/regions/phi-cms-region-container.js +3 -0
  27. package/components/regions/phi-cms-region-shell.d.ts +12 -1
  28. package/components/regions/phi-cms-region-shell.js +81 -19
  29. package/components/root/phi-config-provider.d.ts +13 -0
  30. package/components/root/phi-config-provider.js +12 -1
  31. package/components/root/phi-root-background.d.ts +7 -0
  32. package/components/root/phi-root-background.js +20 -1
  33. package/components/root/phi-root-theme-resolver.js +3 -0
  34. package/components/root/phi-shell-chrome-overlay.d.ts +12 -1
  35. package/components/root/phi-shell-chrome-overlay.js +19 -3
  36. package/components/shell/phi-antd-icon.d.ts +1 -0
  37. package/components/shell/phi-antd-icon.js +3 -0
  38. package/components/shell/use-phi-shell-sticky-top.js +61 -16
  39. package/components/surface/phi-surface-tone.d.ts +13 -0
  40. package/components/surface/phi-surface-tone.js +21 -0
  41. package/components/widgets/client/bullet-list-entries-editor.js +7 -1
  42. package/components/widgets/client/html-editor.js +1 -1
  43. package/components/widgets/client/shared/use-page-reference-options.d.ts +17 -0
  44. package/components/widgets/client/shared/use-page-reference-options.js +57 -0
  45. package/components/widgets/config/background.d.ts +32 -1
  46. package/components/widgets/config/background.js +41 -2
  47. package/components/widgets/label-sets/background.js +2 -0
  48. package/components/widgets/label-types/background.d.ts +1 -0
  49. package/components/widgets/label-types/background.js +1 -0
  50. package/components/widgets/shared/bullet-list-vocabulary.d.ts +10 -0
  51. package/components/widgets/shared/bullet-list-vocabulary.js +5 -2
  52. package/components/widgets/shared/card-body-client.d.ts +5 -3
  53. package/components/widgets/shared/card-body-client.js +49 -6
  54. package/components/widgets/shared/card-vocabulary.d.ts +12 -2
  55. package/components/widgets/shared/card-vocabulary.js +7 -0
  56. package/components/widgets/shared/markdown-body-client.js +39 -10
  57. package/helpers/cms-container-chrome.js +6 -1
  58. package/helpers/locale.d.ts +8 -0
  59. package/helpers/locale.js +18 -0
  60. package/helpers/shell-region-style.js +2 -1
  61. package/helpers/surface-style.d.ts +12 -1
  62. package/helpers/surface-style.js +28 -2
  63. package/module-dashboard-cards.d.ts +1 -1
  64. package/module-dashboard-cards.js +1 -1
  65. package/package.json +1 -1
  66. package/plugins/runtime-modules/area-definitions.d.ts +0 -42
  67. package/plugins/runtime-modules/area-definitions.js +3 -8
  68. package/plugins/runtime-modules/builder/clients/inspector-config-field.js +20 -1
  69. package/plugins/runtime-modules/builder/clients/layout-scaffold-overlays.js +10 -5
  70. package/plugins/runtime-modules/builder/edit-scaffold-drawer.js +3 -2
  71. package/plugins/runtime-modules/builder/render-root-node-scaffold.js +25 -15
  72. package/plugins/runtime-modules/builder/trees/phi-default-builder-area-preset-tree.js +4 -0
  73. package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +15 -4
  74. package/plugins/runtime-modules/client-authoring-module.d.ts +8 -1
  75. package/plugins/runtime-modules/client-authoring-module.js +13 -3
  76. package/plugins/runtime-modules/core/widgets/brand/client.d.ts +12 -3
  77. package/plugins/runtime-modules/core/widgets/brand/client.js +3 -11
  78. package/plugins/runtime-modules/core/widgets/brand/home.d.ts +15 -0
  79. package/plugins/runtime-modules/core/widgets/brand/home.js +18 -0
  80. package/plugins/runtime-modules/core/widgets/brand/plugin.js +3 -2
  81. package/plugins/runtime-modules/core/widgets/brand/server.d.ts +2 -2
  82. package/plugins/runtime-modules/core/widgets/brand/server.js +2 -2
  83. package/plugins/runtime-modules/core/widgets/card/config.d.ts +6 -1
  84. package/plugins/runtime-modules/core/widgets/card/config.js +19 -3
  85. package/plugins/runtime-modules/core/widgets/card/editor.js +13 -3
  86. package/plugins/runtime-modules/core/widgets/card/server.js +1 -0
  87. package/plugins/runtime-modules/core/widgets/description/client.js +3 -3
  88. package/plugins/runtime-modules/core/widgets/description/config.d.ts +1 -0
  89. package/plugins/runtime-modules/core/widgets/description/editor.js +3 -2
  90. package/plugins/runtime-modules/core/widgets/description/server.js +3 -2
  91. package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +1 -0
  92. package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +1 -0
  93. package/plugins/runtime-modules/core/widgets/header-navigation/client.js +2 -2
  94. package/plugins/runtime-modules/core/widgets/html/client.js +1 -1
  95. package/plugins/runtime-modules/core/widgets/markdown/config.d.ts +320 -2
  96. package/plugins/runtime-modules/core/widgets/markdown/config.js +18 -4
  97. package/plugins/runtime-modules/core/widgets/markdown/editor.js +9 -1
  98. package/plugins/runtime-modules/core/widgets/markdown-toc/client.js +6 -1
  99. package/plugins/runtime-modules/core/widgets/markdown-toc/config.d.ts +14 -0
  100. package/plugins/runtime-modules/core/widgets/markdown-toc/config.js +14 -2
  101. package/plugins/runtime-modules/core/widgets/quick-links/authoring.js +7 -2
  102. package/plugins/runtime-modules/core/widgets/quick-links/client.js +2 -2
  103. package/plugins/runtime-modules/core/widgets/quick-links/items.d.ts +0 -2
  104. package/plugins/runtime-modules/core/widgets/quick-links/items.js +0 -15
  105. package/plugins/runtime-modules/core/widgets/result/config.d.ts +1 -0
  106. package/plugins/runtime-modules/core/widgets/sidebar-navigation/authoring.js +1 -1
  107. package/plugins/runtime-modules/core/widgets/sidebar-navigation/client.js +1 -1
  108. package/plugins/runtime-modules/core/widgets/simple-text/client.js +8 -3
  109. package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +1 -0
  110. package/plugins/runtime-modules/core/widgets/simple-text/editor.js +2 -2
  111. package/plugins/runtime-modules/core/widgets/table/config.d.ts +3 -0
  112. package/plugins/runtime-modules/dashboard/card-rows.d.ts +3 -1
  113. package/plugins/runtime-modules/dashboard/card-rows.js +30 -2
  114. package/plugins/runtime-modules/dashboard/clients/dashboard-card-view.js +29 -7
  115. package/plugins/runtime-modules/dashboard/trees/phi-default-builder-dashboard-page-tree.js +5 -40
  116. package/plugins/runtime-modules/theme/widgets/brand-controls/client.js +19 -15
  117. package/plugins/runtime-modules/theme/widgets/brand-controls/config.js +5 -9
  118. package/server-helpers/cms-link-targets.js +6 -0
  119. package/styles/controls.css +64 -2
  120. package/styles/layout.css +45 -2
  121. package/styles/shell.css +73 -0
  122. package/theme/phi-antd-token-resolver.js +20 -1
  123. package/theme/phi-theme-tokens.d.ts +1 -1
  124. package/theme/phi-theme-tokens.js +1 -0
  125. package/theme/phi-theme.d.ts +5 -0
  126. package/theme/phi-theme.js +21 -0
  127. package/theme/phi-tokens.d.ts +16 -15
  128. package/theme/phi-tokens.js +16 -15
  129. package/types/cms-plugins.d.ts +2 -0
  130. package/types/dashboard-cards.d.ts +39 -4
  131. package/types/dashboard-cards.js +6 -0
  132. package/types/site-theme.d.ts +10 -2
@@ -4,7 +4,9 @@
4
4
  * `text` is a heading. `stat` is a labelled figure: the label is the heading, so a card that counts
5
5
  * something says what it counted in the same place and type a text card says what it is about, and the
6
6
  * figure is drawn by the house's one statistic Control. `list` is a heading over a few lines the caller
7
- * draws and hands in (`listContent`), which only a Dashboard does today. `plan` is an offer: a price
7
+ * draws and hands in (`listContent`), which only a Dashboard does today. `series` is a figure over time:
8
+ * the headline as a `stat` draws it, over a chart the caller draws and hands in the same way -- a Dashboard
9
+ * again, so the chart Control never loads with a Card on a Public page. `plan` is an offer: a price
8
10
  * and what it is paid per, a monthly and a yearly one of each, over a list of what the plan includes and
9
11
  * what it does not (`PhiBulletListEntry.state`). Several plan cards side by side are a Layout's row, and
10
12
  * the recommended one is the card that is `highlight`ed.
@@ -12,7 +14,7 @@
12
14
  * The body is chosen, never inferred from whether a value happens to have arrived. A card that decided
13
15
  * by presence would draw as text and reflow into a statistic the moment its figure landed.
14
16
  */
15
- export type PhiCardWidgetBody = "text" | "stat" | "list" | "plan";
17
+ export type PhiCardWidgetBody = "text" | "stat" | "list" | "series" | "plan";
16
18
  /**
17
19
  * Which of a plan card's two prices it shows. A plan states one, the other, or both; one that states
18
20
  * only the monthly price shows it under `yearly` as well, so a period switch on the page never leaves a
@@ -36,6 +38,14 @@ export type PhiCardHeadingLevel = (typeof PHI_CARD_HEADING_LEVELS)[number];
36
38
  /** Logical, so `start` is the left in a left-to-right page and the right in a right-to-left one. */
37
39
  export declare const PHI_CARD_TEXT_ALIGNS: readonly ["start", "center", "end", "justify"];
38
40
  export type PhiCardTextAlign = (typeof PHI_CARD_TEXT_ALIGNS)[number];
41
+ /**
42
+ * How a card's badge is drawn. `corner` is a band flush in the top end corner, `tag` a rounded mark a
43
+ * little down the end edge -- both inside the card. `ribbon` hangs over the end edge with a fold behind
44
+ * it, and needs room beside the card. `none` draws nothing and keeps the words, to hide a badge for a
45
+ * while. Absent is `none`: a card has a badge once a style is chosen for it.
46
+ */
47
+ export declare const PHI_CARD_BADGE_STYLES: readonly ["none", "corner", "tag", "ribbon"];
48
+ export type PhiCardBadgeStyle = (typeof PHI_CARD_BADGE_STYLES)[number];
39
49
  /**
40
50
  * Where the icon stands: before the heading, small, or at the top, large -- at the start, the middle or
41
51
  * the end of the line, over the picture when the card has one, on a ground of its own so it reads on any
@@ -24,6 +24,13 @@ export function readPhiCardBillingPeriod(value) {
24
24
  export const PHI_CARD_HEADING_LEVELS = ["h2", "h3", "h4"];
25
25
  /** Logical, so `start` is the left in a left-to-right page and the right in a right-to-left one. */
26
26
  export const PHI_CARD_TEXT_ALIGNS = ["start", "center", "end", "justify"];
27
+ /**
28
+ * How a card's badge is drawn. `corner` is a band flush in the top end corner, `tag` a rounded mark a
29
+ * little down the end edge -- both inside the card. `ribbon` hangs over the end edge with a fold behind
30
+ * it, and needs room beside the card. `none` draws nothing and keeps the words, to hide a badge for a
31
+ * while. Absent is `none`: a card has a badge once a style is chosen for it.
32
+ */
33
+ export const PHI_CARD_BADGE_STYLES = ["none", "corner", "tag", "ribbon"];
27
34
  /**
28
35
  * Where the icon stands: before the heading, small, or at the top, large -- at the start, the middle or
29
36
  * the end of the line, over the picture when the card has one, on a ground of its own so it reads on any
@@ -68,9 +68,16 @@ function renderInlineNodes(inlines) {
68
68
  }
69
69
  });
70
70
  }
71
- function renderBlocks(blocks, token, paragraphBlockStyle, headingBlockStyle, lists) {
71
+ function renderBlocks(blocks, token, paragraphStyle, headingStyle, lists, atStart = false) {
72
72
  return blocks.map((block, index) => {
73
73
  const key = `${block.kind}-${index}`;
74
+ /*
75
+ * The first block of a text, or of a quote, keeps no space before it: the space before a block
76
+ * parts it from the block above, and there is none -- the Widget's own edge stands there.
77
+ */
78
+ const leading = atStart && index === 0;
79
+ const paragraphBlockStyle = leading ? { ...paragraphStyle, marginBlockStart: 0 } : paragraphStyle;
80
+ const headingBlockStyle = leading ? { ...headingStyle, paddingBlockStart: 0 } : headingStyle;
74
81
  switch (block.kind) {
75
82
  case "heading":
76
83
  return (_jsx(PhiTypographyControl, { presentation: "title", id: block.id, level: block.level, style: headingBlockStyle, children: renderInlineNodes(block.inlines) }, key));
@@ -81,19 +88,31 @@ function renderBlocks(blocks, token, paragraphBlockStyle, headingBlockStyle, lis
81
88
  margin: 0,
82
89
  paddingInlineStart: PHI_SPACE.md,
83
90
  borderInlineStart: `3px solid ${token.colorBorderSecondary}`,
84
- }, children: _jsx("div", { style: { display: "grid", gap: 0 }, children: renderBlocks(block.children, token, paragraphBlockStyle, headingBlockStyle, lists) }) }, key));
91
+ }, children: _jsx("div", { style: { display: "grid", gap: 0 }, children: renderBlocks(block.children, token, paragraphStyle, headingStyle, lists, true) }) }, key));
85
92
  case "list":
86
93
  /*
87
94
  * The house's list, set by the Widget's list settings. A `1.` list counts whatever its level's
88
95
  * marker says: the numbers are the text's, not a look. A list inside an entry is one level down.
89
96
  */
90
- return (_jsx(PhiBulletListControl, { listStyle: lists.style, depth: lists.depth, ordered: block.ordered, start: block.start, items: block.items.map((itemBlocks) => ({
91
- content: (_jsx("div", { style: { display: "grid", gap: PHI_SPACE.xs, minWidth: 0 }, children: renderBlocks(itemBlocks, token, paragraphBlockStyle, headingBlockStyle, { ...lists, depth: lists.depth + 1 }) })),
97
+ return (_jsx(PhiBulletListControl, { listStyle: lists.style, depth: lists.depth, ordered: block.ordered, start: block.start,
98
+ /*
99
+ * The list stands among the text's blocks as a paragraph does: the entries draw close, but the
100
+ * block after the last one keeps the text's spacing. A list inside an entry gets the entry's
101
+ * zeroed spacing, so it sits in its entry's grid.
102
+ */
103
+ blockSpacing: {
104
+ marginBlockStart: paragraphBlockStyle.marginBlockStart,
105
+ marginBlockEnd: paragraphBlockStyle.marginBlockEnd,
106
+ }, items: block.items.map((itemBlocks) => ({
107
+ content: (_jsx("div", { style: { display: "grid", gap: PHI_SPACE.xs, minWidth: 0 }, children: renderBlocks(itemBlocks, token, { ...paragraphStyle, marginBlockStart: 0, marginBlockEnd: 0 }, headingStyle, { ...lists, depth: lists.depth + 1 }) })),
92
108
  })) }, key));
93
109
  case "code":
94
110
  return (_jsx("pre", { "data-phi-code-language": block.language ?? undefined, style: {
95
111
  margin: 0,
96
- padding: PHI_SPACE.md,
112
+ // A code block stands among the text's blocks as a paragraph does, with the text's spacing.
113
+ marginBlockStart: paragraphBlockStyle.marginBlockStart,
114
+ marginBlockEnd: paragraphBlockStyle.marginBlockEnd,
115
+ padding: PHI_SPACE.base,
97
116
  overflowX: "auto",
98
117
  borderRadius: token.borderRadiusSM,
99
118
  background: token.colorFillQuaternary,
@@ -157,15 +176,25 @@ export function PhiMarkdownWidgetClient({ config }) {
157
176
  marginBlockStart: resolvePhiSpacingScaleValue(config?.textBlockSpacingBefore ?? "none"),
158
177
  marginBlockEnd: resolvePhiSpacingScaleValue(config?.textBlockSpacingAfter ?? "sm"),
159
178
  };
179
+ /*
180
+ * The space before a heading is padding, not margin, so it adds to the space after the block above
181
+ * however the text is laid out: in the grid margins never collapse, in the flow that pictures need
182
+ * they would, and the same setting would part a heading from its paragraph by less there.
183
+ */
160
184
  const headingBlockStyle = {
161
185
  margin: 0,
162
- marginBlockStart: resolvePhiSpacingScaleValue(config?.headingBlockSpacingBefore ?? "none"),
186
+ paddingBlockStart: resolvePhiSpacingScaleValue(config?.headingBlockSpacingBefore ?? "sm"),
163
187
  marginBlockEnd: resolvePhiSpacingScaleValue(config?.headingBlockSpacingAfter ?? "sm"),
164
188
  };
165
- return (_jsx("div", { className: config?.imageFloat ? `phi-markdown-body phi-markdown-body--float-${config.imageFloat}` : undefined, style: config?.imageFloat
166
- ? { display: "flow-root", width: "100%", textAlign: config.textAlign, containerType: "inline-size" }
167
- : { display: "grid", gap: 0, width: "100%", textAlign: config?.textAlign }, children: renderBlocks(blocks, token, paragraphBlockStyle, headingBlockStyle, {
189
+ return (_jsx("div", { className: config?.imageFloat
190
+ ? `phi-markdown-text phi-markdown-body phi-markdown-body--float-${config.imageFloat}`
191
+ : "phi-markdown-text",
192
+ // The text's line height for everything in it, lists included: a list entry otherwise takes the
193
+ // browser's `normal`, and its marker centres on a line shorter than the one its words stand on.
194
+ style: config?.imageFloat
195
+ ? { display: "flow-root", width: "100%", textAlign: config.textAlign, containerType: "inline-size", lineHeight: token.lineHeight }
196
+ : { display: "grid", gap: 0, width: "100%", textAlign: config?.textAlign, lineHeight: token.lineHeight }, children: renderBlocks(blocks, token, paragraphBlockStyle, headingBlockStyle, {
168
197
  style: resolvePhiBulletListStyle(config?.list),
169
198
  depth: 1,
170
- }) }));
199
+ }, true) }));
171
200
  }
@@ -9,7 +9,12 @@ import { resolvePhiSurfaceStyle } from "./surface-style";
9
9
  * would show through as the default after all.
10
10
  */
11
11
  export function resolvePhiCmsContainerSurface(config) {
12
- const resolved = resolvePhiSurfaceStyle(config.surface, { groundLayer: false });
12
+ /*
13
+ * An Overlay draws no tone -- neither the class nor the scope -- so it does not paint a tone's ground
14
+ * either: without the class the ground would be the page's, laid over the Overlay's own.
15
+ */
16
+ const surface = config.surface?.tone ? { ...config.surface, tone: undefined } : config.surface;
17
+ const resolved = resolvePhiSurfaceStyle(surface, { groundLayer: false });
13
18
  const background = config.surface?.background
14
19
  ? normalizePhiBackgroundWidgetConfig(config.surface.background)
15
20
  : null;
@@ -31,6 +31,14 @@ export declare function isPhiLocaleReadableAsSource(locale: string, source: stri
31
31
  export declare function resolvePhiTranslationLocale(input: string | null | undefined): string;
32
32
  export declare function normalizeLocale(input: string | null | undefined, options?: NormalizeLocaleOptions): SiteLocale;
33
33
  export declare function localizePath(locale: SiteLocale | string, targetPath: string): string;
34
+ /**
35
+ * A Public path in the language being read, unless it already names one.
36
+ *
37
+ * A path stored without a locale (`/home`) gets `locale` in front, as `/` does; one whose first segment
38
+ * is one of the Site's locales (`/de/home`) and an external URL stay as they are. The locales are needed
39
+ * to tell a locale segment from a page, which `matchPhiLocalePrefixSegment` does.
40
+ */
41
+ export declare function localizePathUnlessLocalized(locale: SiteLocale | string, targetPath: string, availableLocales: readonly string[]): string;
34
42
  /**
35
43
  * The path for a staff Area, which carries no locale prefix.
36
44
  *
package/helpers/locale.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { PHI_CMS_AREA_KEYS } from "../constants/cms-areas";
2
+ import { isPhiExternalHref } from "./external-href";
2
3
  export { PHI_CANONICAL_SOURCE_LOCALE } from "@phis/contracts/locale";
3
4
  import { PHI_CANONICAL_SOURCE_LOCALE } from "@phis/contracts/locale";
4
5
  export const SUPPORTED_CMS_AREAS = PHI_CMS_AREA_KEYS;
@@ -117,6 +118,23 @@ function prefixPath(prefix, targetPath) {
117
118
  export function localizePath(locale, targetPath) {
118
119
  return prefixPath(locale, targetPath);
119
120
  }
121
+ /**
122
+ * A Public path in the language being read, unless it already names one.
123
+ *
124
+ * A path stored without a locale (`/home`) gets `locale` in front, as `/` does; one whose first segment
125
+ * is one of the Site's locales (`/de/home`) and an external URL stay as they are. The locales are needed
126
+ * to tell a locale segment from a page, which `matchPhiLocalePrefixSegment` does.
127
+ */
128
+ export function localizePathUnlessLocalized(locale, targetPath, availableLocales) {
129
+ if (isPhiExternalHref(targetPath)) {
130
+ return targetPath;
131
+ }
132
+ const firstSegment = targetPath.split("/").filter(Boolean)[0];
133
+ if (firstSegment && matchPhiLocalePrefixSegment(firstSegment, { availableLocales })) {
134
+ return targetPath;
135
+ }
136
+ return localizePath(locale, targetPath);
137
+ }
120
138
  /**
121
139
  * The path for a staff Area, which carries no locale prefix.
122
140
  *
@@ -209,7 +209,8 @@ export function resolvePhiShellRegionGroundStyle(ground) {
209
209
  export function resolvePhiShellSiderWidth(shellTheme) {
210
210
  return (resolvePhiShellMetric(shellTheme, "width", { family: "sider", region: "left" }) ??
211
211
  resolvePhiShellMetric(shellTheme, "width", { family: "sider", region: "right" }) ??
212
- 200);
212
+ // The scale's sider width, which the Builder's defaults and the page grid already use.
213
+ PHI_LAYOUT.sidebarWidth);
213
214
  }
214
215
  export function resolvePhiShellSiderCollapsedWidth(shellTheme) {
215
216
  return (resolvePhiShellMetric(shellTheme, "collapsedWidth", { family: "sider", region: "left" }) ??
@@ -50,6 +50,14 @@ export type PhiSurfaceStyleOptions = {
50
50
  */
51
51
  groundLayer?: boolean;
52
52
  };
53
+ /** The ground a toned Surface paints where it states none: the container ground of the tone's mode. */
54
+ export declare const PHI_SURFACE_TONE_GROUND = "var(--ant-color-bg-container)";
55
+ /**
56
+ * Whether a Surface takes its tone's ground (`PHI_SURFACE_TONE_GROUND`): it asks for a mode of its own and
57
+ * its Background paints nothing. A stated ground wins, and the Theme's Overlay (`base.kind: "theme"`) is
58
+ * one. The one rule for every box with a Surface -- this resolver's and the Region shell's.
59
+ */
60
+ export declare function phiSurfaceTakesToneGround(tone: PhiSurface["tone"] | null | undefined, paintsGround: boolean): boolean;
53
61
  /**
54
62
  * A Surface as CSS, in one place for every box that has one.
55
63
  *
@@ -59,5 +67,8 @@ export type PhiSurfaceStyleOptions = {
59
67
  * (a pane's) combined with the configured one.
60
68
  */
61
69
  export declare function resolvePhiSurfaceStyle(surface: PhiSurface | null | undefined, options?: PhiSurfaceStyleOptions): PhiResolvedSurface;
62
- /** Whether a Surface draws anything a reader could see: a ground, a line, or depth. */
70
+ /**
71
+ * Whether a Surface draws anything a reader could see: a ground (its own, or its tone's), a line, or
72
+ * depth.
73
+ */
63
74
  export declare function phiSurfaceDrawsChrome(surface: PhiSurface | null | undefined): boolean;
@@ -1,8 +1,18 @@
1
- import { normalizePhiBackgroundWidgetConfig, phiBackgroundWidgetConfigPaintsGround, resolvePhiBackgroundGroundFilter, resolvePhiBackgroundMotion, resolvePhiBackgroundMotionHostStyle, resolvePhiBackgroundWidgetStyle, } from "../components/widgets/config/background";
1
+ import { PHI_BACKGROUND_CONTAINER_GROUND, normalizePhiBackgroundWidgetConfig, phiBackgroundWidgetConfigPaintsGround, resolvePhiBackgroundGroundFilter, resolvePhiBackgroundMotion, resolvePhiBackgroundMotionHostStyle, resolvePhiBackgroundWidgetStyle, } from "../components/widgets/config/background";
2
2
  import { resolvePhiCmsBorderSource } from "../types/cms-border-source";
3
3
  import { resolvePhiSurfaceToneClassName } from "../types/surface";
4
4
  import { resolvePhiSourcedBorderStyle } from "./border-widget-style";
5
5
  import { combinePhiBoxShadows, resolvePhiShadow } from "./layout-style";
6
+ /** The ground a toned Surface paints where it states none: the container ground of the tone's mode. */
7
+ export const PHI_SURFACE_TONE_GROUND = PHI_BACKGROUND_CONTAINER_GROUND;
8
+ /**
9
+ * Whether a Surface takes its tone's ground (`PHI_SURFACE_TONE_GROUND`): it asks for a mode of its own and
10
+ * its Background paints nothing. A stated ground wins, and the Theme's Overlay (`base.kind: "theme"`) is
11
+ * one. The one rule for every box with a Surface -- this resolver's and the Region shell's.
12
+ */
13
+ export function phiSurfaceTakesToneGround(tone, paintsGround) {
14
+ return tone != null && tone !== "inherit" && !paintsGround;
15
+ }
6
16
  const PHI_SURFACE_EMPTY = { style: {}, ground: null, paintsGround: false };
7
17
  function resolvePhiSurfaceCorners(fallback, configured) {
8
18
  const corner = (value) => value ?? fallback ?? undefined;
@@ -30,8 +40,13 @@ export function resolvePhiSurfaceStyle(surface, options = {}) {
30
40
  const paintsGround = phiBackgroundWidgetConfigPaintsGround(background);
31
41
  const groundFilter = background ? resolvePhiBackgroundGroundFilter(background) : null;
32
42
  const motion = background ? resolvePhiBackgroundMotion(background) : null;
43
+ /*
44
+ * The Theme's Overlay never goes on a layer: it is a window onto one viewport-sized painting, so there
45
+ * is nothing to move or zoom, and its pane has to frost the page behind the box rather than the box.
46
+ */
33
47
  const needsGroundLayer = options.groundLayer !== false
34
48
  && paintsGround
49
+ && background?.base.kind !== "theme"
35
50
  && (groundFilter != null || motion != null || options.forceGroundLayer === true);
36
51
  const paint = background ? resolvePhiBackgroundWidgetStyle(background) : {};
37
52
  const hostPaint = needsGroundLayer
@@ -44,6 +59,12 @@ export function resolvePhiSurfaceStyle(surface, options = {}) {
44
59
  motion: motion ? background : null,
45
60
  }
46
61
  : null;
62
+ /*
63
+ * A tone turns the ground as well (LAYOUTING.md "Surface tone"): content in another mode on the page's
64
+ * ground would be light words on a light page. Where the Background paints nothing, the box takes the
65
+ * mode's own container ground, which resolves under the tone's class it carries. A stated ground wins.
66
+ */
67
+ const toneGround = phiSurfaceTakesToneGround(surface.tone, paintsGround);
47
68
  const borderSource = resolvePhiCmsBorderSource(surface.borderSource, surface.border);
48
69
  const edge = resolvePhiSourcedBorderStyle(borderSource, surface.border);
49
70
  const { border: line, ...configuredCorners } = edge;
@@ -51,6 +72,7 @@ export function resolvePhiSurfaceStyle(surface, options = {}) {
51
72
  return {
52
73
  style: {
53
74
  ...hostPaint,
75
+ ...(toneGround ? { backgroundColor: PHI_SURFACE_TONE_GROUND } : {}),
54
76
  ...(line == null ? {} : { border: line }),
55
77
  ...resolvePhiSurfaceCorners(options.cornerFallback, configuredCorners),
56
78
  ...(boxShadow == null ? {} : { boxShadow }),
@@ -63,12 +85,16 @@ export function resolvePhiSurfaceStyle(surface, options = {}) {
63
85
  ...(surface.tone ? { className: resolvePhiSurfaceToneClassName(surface.tone) } : {}),
64
86
  };
65
87
  }
66
- /** Whether a Surface draws anything a reader could see: a ground, a line, or depth. */
88
+ /**
89
+ * Whether a Surface draws anything a reader could see: a ground (its own, or its tone's), a line, or
90
+ * depth.
91
+ */
67
92
  export function phiSurfaceDrawsChrome(surface) {
68
93
  if (!surface) {
69
94
  return false;
70
95
  }
71
96
  return phiBackgroundWidgetConfigPaintsGround(surface.background ?? null)
97
+ || (surface.tone != null && surface.tone !== "inherit")
72
98
  || resolvePhiCmsBorderSource(surface.borderSource, surface.border) !== "none"
73
99
  || (surface.shadow != null && surface.shadow !== "none");
74
100
  }
@@ -11,5 +11,5 @@
11
11
  * headers itself, which is a copy of a protocol that is this package's to change. See DASHBOARD.md.
12
12
  */
13
13
  import "server-only";
14
- export { createPhiDashboardCardId, isPhiDashboardCardId, type PhiDashboardCardContext, type PhiDashboardCardDescriptor, type PhiDashboardCardForm, type PhiDashboardCardId, type PhiDashboardCardPayload, type PhiDashboardCardProvider, } from "./types/dashboard-cards";
14
+ export { createPhiDashboardCardId, isPhiDashboardCardId, PHI_DASHBOARD_CARD_SERIES_LIMIT, type PhiDashboardCardContext, type PhiDashboardCardDescriptor, type PhiDashboardCardForm, type PhiDashboardCardId, type PhiDashboardCardPayload, type PhiDashboardCardProvider, type PhiDashboardCardSeries, type PhiDashboardCardSeriesPoint, } from "./types/dashboard-cards";
15
15
  export { fetchPhiSiteApi, type PhiSiteApiRequest } from "./gateway/site-api-request";
@@ -11,5 +11,5 @@
11
11
  * headers itself, which is a copy of a protocol that is this package's to change. See DASHBOARD.md.
12
12
  */
13
13
  import "server-only";
14
- export { createPhiDashboardCardId, isPhiDashboardCardId, } from "./types/dashboard-cards";
14
+ export { createPhiDashboardCardId, isPhiDashboardCardId, PHI_DASHBOARD_CARD_SERIES_LIMIT, } from "./types/dashboard-cards";
15
15
  export { fetchPhiSiteApi } from "./gateway/site-api-request";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phis/ui",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -34,20 +34,6 @@ export declare const PHI_PUBLIC_RUNTIME_AREA_DEFINITIONS: {
34
34
  readonly routePresetKey: "public-terms-page";
35
35
  }];
36
36
  readonly exportedItemKeys: readonly ["@phis/ui/modules/public/nav/terms"];
37
- }, {
38
- readonly navKey: "public:quicklinks";
39
- readonly label: {
40
- defaultMessage: string;
41
- };
42
- readonly items: readonly [{
43
- readonly itemKey: "@phis/ui/modules/public/nav/terms";
44
- readonly label: {
45
- defaultMessage: string;
46
- };
47
- readonly icon: "antd:file-text";
48
- readonly routePresetKey: "public-terms-page";
49
- }];
50
- readonly exportedItemKeys: readonly ["@phis/ui/modules/public/nav/terms"];
51
37
  }, PhiCmsNavigationSurfaceDescriptor];
52
38
  }[];
53
39
  export declare const PHI_APP_ACCOUNT_NAV_ITEM_KEY = "@phis/ui/modules/app/nav/account";
@@ -226,20 +212,6 @@ export declare const PHI_ALL_RUNTIME_AREA_DEFINITIONS: ({
226
212
  readonly routePresetKey: "public-terms-page";
227
213
  }];
228
214
  readonly exportedItemKeys: readonly ["@phis/ui/modules/public/nav/terms"];
229
- }, {
230
- readonly navKey: "public:quicklinks";
231
- readonly label: {
232
- defaultMessage: string;
233
- };
234
- readonly items: readonly [{
235
- readonly itemKey: "@phis/ui/modules/public/nav/terms";
236
- readonly label: {
237
- defaultMessage: string;
238
- };
239
- readonly icon: "antd:file-text";
240
- readonly routePresetKey: "public-terms-page";
241
- }];
242
- readonly exportedItemKeys: readonly ["@phis/ui/modules/public/nav/terms"];
243
215
  }, PhiCmsNavigationSurfaceDescriptor];
244
216
  } | {
245
217
  area: "app";
@@ -457,20 +429,6 @@ export declare function resolvePhiRuntimeAreaDefinition(area: PhiCmsAreaDefiniti
457
429
  readonly routePresetKey: "public-terms-page";
458
430
  }];
459
431
  readonly exportedItemKeys: readonly ["@phis/ui/modules/public/nav/terms"];
460
- }, {
461
- readonly navKey: "public:quicklinks";
462
- readonly label: {
463
- defaultMessage: string;
464
- };
465
- readonly items: readonly [{
466
- readonly itemKey: "@phis/ui/modules/public/nav/terms";
467
- readonly label: {
468
- defaultMessage: string;
469
- };
470
- readonly icon: "antd:file-text";
471
- readonly routePresetKey: "public-terms-page";
472
- }];
473
- readonly exportedItemKeys: readonly ["@phis/ui/modules/public/nav/terms"];
474
432
  }, PhiCmsNavigationSurfaceDescriptor];
475
433
  } | {
476
434
  area: "app";
@@ -116,8 +116,9 @@ function sidebarAnchors(settingsItemKey) {
116
116
  * The Public surfaces carry one intrinsic entry, the terms, and export it as the one anchor.
117
117
  *
118
118
  * The terms are Core's, so their entry is; the contact entry hangs after it, and a Site package's
119
- * landing places its Home entry before it. Public has no sidebar surface: the Public Shell draws a
120
- * header and a footer, and a surface nothing renders is a promise nobody can see kept.
119
+ * landing places its Home entry before it. Public has no sidebar and no quick-links surface: the Public
120
+ * Shell draws a header and a footer -- the footer's Quick Links Widget reads `public:footer` -- and a
121
+ * surface nothing renders is a promise nobody can see kept.
121
122
  */
122
123
  const publicTermsItem = {
123
124
  itemKey: "@phis/ui/modules/public/nav/terms",
@@ -138,12 +139,6 @@ const publicNavigationSurfaces = [
138
139
  items: [publicTermsItem],
139
140
  exportedItemKeys: [publicTermsItem.itemKey],
140
141
  },
141
- {
142
- navKey: "public:quicklinks",
143
- label: label("Public quick links"),
144
- items: [publicTermsItem],
145
- exportedItemKeys: [publicTermsItem.itemKey],
146
- },
147
142
  accountNavigationSurface(PHI_PUBLIC_ACCOUNT_NAV_ITEM_KEY, "public:account"),
148
143
  ];
149
144
  export const PHI_PUBLIC_RUNTIME_AREA_DEFINITIONS = [
@@ -736,7 +736,7 @@ function renderPhiInspectorConfigFieldBody({ field, value, defaultValue, config,
736
736
  ? String(value)
737
737
  : typeof defaultValue === "string" || typeof defaultValue === "number"
738
738
  ? String(defaultValue)
739
- : "", disabled: disabled || !onChange, inputType: field.type === "url" ? "url" : "text", style: { width: "100%" }, onChange: (nextValue) => onChange?.({
739
+ : "", disabled: disabled || !onChange, inputType: field.type === "url" ? "url" : "text", ...("placeholder" in field && field.placeholder ? { placeholder: field.placeholder } : {}), style: { width: "100%" }, onChange: (nextValue) => onChange?.({
740
740
  [field.key]: nextValue || ("emptyValue" in field ? field.emptyValue : undefined),
741
741
  }) }));
742
742
  }
@@ -755,6 +755,25 @@ function renderPhiInspectorConfigFieldBody({ field, value, defaultValue, config,
755
755
  }
756
756
  if (field.type === "flags") {
757
757
  const mask = typeof value === "number" ? value : typeof defaultValue === "number" ? defaultValue : 0;
758
+ /*
759
+ * One flag is a yes or no, and reads as one: the field's name with a switch behind it on its row,
760
+ * like a boolean field, rather than a group of a single checkbox under a heading. The other bits of
761
+ * the mask stay as they are.
762
+ */
763
+ const [onlyOption] = field.options.length === 1 ? field.options : [];
764
+ if (onlyOption) {
765
+ return renderPhiInspectorSwitchRow({
766
+ key: field.key,
767
+ label: renderPhiInspectorConfigFieldLabel(field),
768
+ checked: (mask & onlyOption.value) !== 0,
769
+ disabled: disabled || !onChange,
770
+ ...(onChange
771
+ ? {
772
+ onChange: (checked) => onChange({ [field.key]: checked ? mask | onlyOption.value : mask & ~onlyOption.value }),
773
+ }
774
+ : {}),
775
+ });
776
+ }
758
777
  return renderPhiInspectorConfigFieldBlock(field, _jsx(PhiCheckboxGroupControl, { vertical: true, value: field.options.filter((option) => (mask & option.value) !== 0).map((option) => option.value), options: field.options.map((option) => ({ value: option.value, label: option.label })), disabled: disabled || !onChange, onChange: (ticked) => onChange?.({ [field.key]: ticked.reduce((next, flag) => next | flag, 0) }) }));
759
778
  }
760
779
  if (field.type === "number-list") {
@@ -1,14 +1,19 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { usePhiConfig } from "../../../../components/root/phi-config-provider";
3
+ import { usePhiPageModeToken } from "../../../../components/root/phi-config-provider";
4
+ import { usePhiPageModeTone } from "../../../../components/surface/phi-surface-tone";
4
5
  import {} from "../node-kind-theme";
5
6
  import { PhiLayoutAnchoredOverlay } from "../../../../components/layouts/clients/phi-layout-anchored-overlay";
6
7
  import { PhiLayoutConfigButtonWidget, PhiLayoutDeleteButtonWidget, PhiLayoutDragButtonWidget, PhiPlusButtonWidget, } from "./layout-scaffold-buttons";
7
8
  export function PhiLayoutPlusButtonOverlay({ slotIndex, label, onInsert, anchor, slotRole, inset, ariaLabel, dropTarget, children, }) {
8
- return (_jsx(PhiLayoutAnchoredOverlay, { anchor: anchor, slotRole: slotRole, inset: inset, children: children ?? (_jsx(PhiPlusButtonWidget, { slotIndex: slotIndex, label: label, ariaLabel: ariaLabel, onInsert: onInsert, dropTarget: dropTarget })) }));
9
+ // In the editor's mode, not a tone's: the insert button stays the Builder's inside a toned Surface.
10
+ const pageModeTone = usePhiPageModeTone();
11
+ return (_jsx(PhiLayoutAnchoredOverlay, { anchor: anchor, slotRole: slotRole, inset: inset, children: pageModeTone.wrap(children ?? (_jsx(PhiPlusButtonWidget, { slotIndex: slotIndex, label: label, ariaLabel: ariaLabel, onInsert: onInsert, dropTarget: dropTarget }))) }));
9
12
  }
10
13
  export function PhiLayoutDeleteButtonOverlay({ onDelete, onOpenInspector, ariaLabel, inspectorAriaLabel, top, right, bottom, left, placement, transform, zIndex = 10, nodeKind, leading, children, }) {
11
- const { token } = usePhiConfig();
14
+ // The editor's colours, not a tone's: a toolbar inside a toned Surface stays in the page's mode.
15
+ const token = usePhiPageModeToken();
16
+ const pageModeTone = usePhiPageModeTone();
12
17
  const resolvedTop = top ?? (placement ? "auto" : -10);
13
18
  const resolvedRight = right ?? (placement === "bottom-left" ? "auto" : 0);
14
19
  const resolvedBottom = bottom ?? (placement ? -10 : "auto");
@@ -42,7 +47,7 @@ export function PhiLayoutDeleteButtonOverlay({ onDelete, onOpenInspector, ariaLa
42
47
  color: "inherit",
43
48
  };
44
49
  })();
45
- return (_jsx("div", { className: "phi-layout-scaffold-delete", "data-phi-builder-scaffold-action": "true", "data-phi-builder-node-kind": nodeKind ?? undefined, onClick: (event) => event.stopPropagation(), onMouseDown: (event) => event.stopPropagation(), onPointerDown: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), style: {
50
+ return (_jsx("div", { className: pageModeTone.className ? `phi-layout-scaffold-delete ${pageModeTone.className}` : "phi-layout-scaffold-delete", "data-phi-builder-scaffold-action": "true", "data-phi-builder-node-kind": nodeKind ?? undefined, onClick: (event) => event.stopPropagation(), onMouseDown: (event) => event.stopPropagation(), onPointerDown: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), style: {
46
51
  /*
47
52
  * The bar is as wide as its buttons, and it says so itself.
48
53
  *
@@ -63,7 +68,7 @@ export function PhiLayoutDeleteButtonOverlay({ onDelete, onOpenInspector, ariaLa
63
68
  "--phi-layout-scaffold-delete-border": chromeTheme.border,
64
69
  "--phi-layout-scaffold-delete-color": chromeTheme.color,
65
70
  "--phi-layout-scaffold-delete-shadow": "var(--ant-box-shadow-tertiary)",
66
- }, children: children ?? (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [leading, onOpenInspector ? (_jsx(PhiLayoutConfigButtonWidget, { onOpenInspector: onOpenInspector, ariaLabel: inspectorAriaLabel })) : null, _jsx(PhiLayoutDeleteButtonWidget, { onDelete: onDelete, ariaLabel: ariaLabel })] })) }));
71
+ }, children: pageModeTone.wrap(children ?? (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [leading, onOpenInspector ? (_jsx(PhiLayoutConfigButtonWidget, { onOpenInspector: onOpenInspector, ariaLabel: inspectorAriaLabel })) : null, _jsx(PhiLayoutDeleteButtonWidget, { onDelete: onDelete, ariaLabel: ariaLabel })] }))) }));
67
72
  }
68
73
  export function PhiLayoutDragButtonOverlay({ dragData, ariaLabel, nodeKind, top = "50%", left = 0, zIndex = 41, }) {
69
74
  return (_jsx(PhiLayoutDeleteButtonOverlay, { onDelete: () => undefined, top: top, right: "auto", bottom: "auto", left: left, transform: "translate(-50%, -50%)", zIndex: zIndex, nodeKind: nodeKind, children: _jsx(PhiLayoutDragButtonWidget, { ariaLabel: ariaLabel, dragData: dragData }) }));
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { usePhiConfig } from "../../../components/root/phi-config-provider";
3
+ import { usePhiPageModeToken } from "../../../components/root/phi-config-provider";
4
4
  import { PhiButtonControl } from "../../../components/controls/phi-button-control";
5
5
  import { usePhiDeveloperBuilderStateValue } from "./developer-workspace-store";
6
6
  import { PhiIcon } from "../../../components/shell/phi-icon";
7
7
  export function PhiEditScaffoldDrawer({ kind, nodeId, regionKey, scaffoldLabel, className, onOpenInsert, onClick, onKeyDown, children, chrome, style, }) {
8
- const { token } = usePhiConfig();
8
+ // The editor's colours, not a tone's: a scaffold inside a toned Surface marks it in the page's mode.
9
+ const token = usePhiPageModeToken();
9
10
  const selected = usePhiDeveloperBuilderStateValue((state) => state.selectedRootRegionKey === regionKey &&
10
11
  state.nodeId === nodeId &&
11
12
  (state.nodeKind === "layout"));