@phis/ui 0.4.1 → 0.4.2

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.
@@ -1,6 +1,6 @@
1
1
  import type { PhiClientBlockBaseProps, PhiNoLabels } from "../../../types";
2
2
  import type { PhiCodeToken } from "./markdown-code-tokens";
3
- import type { PhiMarkdownTextAlign } from "../../../types/core-widget-placements";
3
+ import type { PhiMarkdownImageFloat, PhiMarkdownTextAlign } from "../../../types/core-widget-placements";
4
4
  import { type PhiSpacingScaleKey } from "../config/spacing-options";
5
5
  import type { PhiMarkdownTocHeading } from "../../../types/markdown";
6
6
  import { type PhiBulletListStyle } from "./bullet-list-vocabulary";
@@ -75,6 +75,7 @@ export type PhiMarkdownWidgetClientProps = PhiClientBlockBaseProps<PhiNoLabels,
75
75
  tocKey?: string;
76
76
  headings?: PhiMarkdownTocHeading[];
77
77
  textAlign?: PhiMarkdownTextAlign;
78
+ imageFloat?: PhiMarkdownImageFloat;
78
79
  textBlockSpacingBefore?: PhiSpacingScaleKey;
79
80
  textBlockSpacingAfter?: PhiSpacingScaleKey;
80
81
  headingBlockSpacingBefore?: PhiSpacingScaleKey;
@@ -162,7 +162,9 @@ export function PhiMarkdownWidgetClient({ config }) {
162
162
  marginBlockStart: resolvePhiSpacingScaleValue(config?.headingBlockSpacingBefore ?? "none"),
163
163
  marginBlockEnd: resolvePhiSpacingScaleValue(config?.headingBlockSpacingAfter ?? "sm"),
164
164
  };
165
- return (_jsx("div", { style: { display: "grid", gap: 0, width: "100%", textAlign: config?.textAlign }, children: renderBlocks(blocks, token, paragraphBlockStyle, headingBlockStyle, {
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, {
166
168
  style: resolvePhiBulletListStyle(config?.list),
167
169
  depth: 1,
168
170
  }) }));
@@ -56,6 +56,13 @@ function normalizePotentialPhiScheme(value) {
56
56
  * accepted let `javascript:` and `data:` through here while the inline path refused them.
57
57
  */
58
58
  const PHI_EXTERNAL_DOCUMENT_URL_SCHEMES = /^(?:https?:|mailto:|tel:)$/iu;
59
+ /*
60
+ * A picture written into the text itself may also be a data URL of an image type -- the one way a Module
61
+ * brings a drawing with its copy, since it cannot put a file into the Site's `public/`. An `<img>` runs
62
+ * nothing it loads, an SVG's scripts included, so the type list is what keeps this a picture. Links
63
+ * stay on the scheme list above, and a fetched document gets no data URLs at all.
64
+ */
65
+ const PHI_INLINE_IMAGE_DATA_URL = /^data:image\/(?:png|jpeg|gif|webp|svg\+xml)[;,]/iu;
59
66
  function resolveExternalDocumentUrl(value, sourceUrl) {
60
67
  const normalized = value.trim();
61
68
  if (!normalized || normalizePotentialPhiScheme(normalized).startsWith("phis:"))
@@ -119,7 +126,7 @@ async function projectInternalMarkdownNodes(tree, runtime) {
119
126
  const src = resolved.assetUrls.get(reference.assetId);
120
127
  return src ? [{ ...node, url: src, children }] : [];
121
128
  }
122
- if (!/^https?:/iu.test(node.url ?? ""))
129
+ if (!/^https?:/iu.test(node.url ?? "") && !PHI_INLINE_IMAGE_DATA_URL.test(node.url ?? ""))
123
130
  return [];
124
131
  return [{ ...node, children }];
125
132
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phis/ui",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,4 +1,4 @@
1
- import type { PhiMarkdownTextAlign } from "../../../../../types/core-widget-placements";
1
+ import type { PhiMarkdownImageFloat, PhiMarkdownTextAlign } from "../../../../../types/core-widget-placements";
2
2
  import { type PhiSpacingScaleKey } from "../../../../../components/widgets/config/spacing-options";
3
3
  import type { PhiCmsResolvedContent } from "../../../../../types/cms";
4
4
  import { type PhiBulletListStyle } from "../../../../../components/widgets/shared/bullet-list-vocabulary";
@@ -13,6 +13,8 @@ export type PhiCmsMarkdownWidgetConfig = PhiCmsWidgetConfigBase & {
13
13
  /** Set at render from the node's `NoTranslate` flag, or by a caller that knows; never authored. */
14
14
  translate?: boolean;
15
15
  textAlign?: PhiMarkdownTextAlign;
16
+ /** Pictures stand at this edge and the text flows past them; absent, a picture is a line of its own. */
17
+ imageFloat?: PhiMarkdownImageFloat;
16
18
  textBlockSpacingBefore?: PhiSpacingScaleKey;
17
19
  textBlockSpacingAfter?: PhiSpacingScaleKey;
18
20
  headingBlockSpacingBefore?: PhiSpacingScaleKey;
@@ -9,6 +9,13 @@ const PHI_MARKDOWN_TEXT_ALIGN_OPTIONS = [
9
9
  { value: "end", label: "End" },
10
10
  { value: "justify", label: "Justify" },
11
11
  ];
12
+ const PHI_MARKDOWN_IMAGE_FLOAT_OPTIONS = [
13
+ { value: "start", label: "Start" },
14
+ { value: "end", label: "End" },
15
+ ];
16
+ function readMarkdownImageFloat(value) {
17
+ return value === "start" || value === "end" ? value : undefined;
18
+ }
12
19
  // The one spacing scale every padding and gap speaks (`theme/phi-tokens.ts`), stored by its key.
13
20
  const PHI_MARKDOWN_SPACING_OPTIONS = PHI_SPACING_SCALE_KEYS.map((key) => ({ value: key, label: key }));
14
21
  function readMarkdownSourceMode(value) {
@@ -41,6 +48,7 @@ export function parsePhiCmsMarkdownWidgetConfig(config) {
41
48
  sourceLocale: readString(config.sourceLocale),
42
49
  revalidateSeconds: readNumber(config.revalidateSeconds),
43
50
  textAlign: readMarkdownTextAlign(config.textAlign),
51
+ imageFloat: readMarkdownImageFloat(config.imageFloat),
44
52
  textBlockSpacingBefore: readMarkdownSpacingKey(config.textBlockSpacingBefore),
45
53
  textBlockSpacingAfter: readMarkdownSpacingKey(config.textBlockSpacingAfter),
46
54
  headingBlockSpacingBefore: readMarkdownSpacingKey(config.headingBlockSpacingBefore),
@@ -115,6 +123,12 @@ export const PHI_MARKDOWN_WIDGET_DEFINITION = {
115
123
  label: "Align",
116
124
  options: PHI_MARKDOWN_TEXT_ALIGN_OPTIONS,
117
125
  },
126
+ {
127
+ key: "imageFloat",
128
+ type: "choice",
129
+ label: "Image Float",
130
+ options: PHI_MARKDOWN_IMAGE_FLOAT_OPTIONS,
131
+ },
118
132
  {
119
133
  key: "textBlockSpacingBefore",
120
134
  type: "choice",
@@ -598,3 +598,29 @@ h5.phi-typography, .phi-typography h5 {
598
598
  .phi-plain-table tbody tr:last-child td {
599
599
  border-block-end: none;
600
600
  }
601
+
602
+ /*
603
+ * Markdown pictures the text flows past (`imageFloat` on the Markdown Widget).
604
+ *
605
+ * Only where there is room for both: below 560px a picture beside the words leaves each of them a column
606
+ * nobody reads, so there the picture stands on its own line at full width, as an unfloated one does. The
607
+ * width is a share of the text with a ceiling, so a wide column does not grow the picture past a glance.
608
+ */
609
+ .phi-markdown-body :is(img) {
610
+ display: block;
611
+ margin-block-end: var(--ant-margin, 16px);
612
+ }
613
+
614
+ @container (min-width: 560px) {
615
+ .phi-markdown-body--float-start :is(img) {
616
+ float: inline-start;
617
+ width: min(45%, 440px);
618
+ margin-inline-end: var(--ant-margin-lg, 24px);
619
+ }
620
+
621
+ .phi-markdown-body--float-end :is(img) {
622
+ float: inline-end;
623
+ width: min(45%, 440px);
624
+ margin-inline-start: var(--ant-margin-lg, 24px);
625
+ }
626
+ }
@@ -42,6 +42,8 @@ export type PhiWidgetPlacementSignalRoutes = {
42
42
  type PhiControlPlacement = Omit<PhiControlConfig, "signalRoutes">;
43
43
  /** Logical, not left and right: a Site in an RTL locale reads "start" as the right-hand edge. */
44
44
  export type PhiMarkdownTextAlign = "start" | "center" | "end" | "justify";
45
+ /** Which edge the text's pictures stand at, with the words flowing past them; logical like the align. */
46
+ export type PhiMarkdownImageFloat = "start" | "end";
45
47
  export type PhiMarkdownWidgetPlacement = PhiWidgetPlacementBase & {
46
48
  sourceMode?: "inline" | "url";
47
49
  markdown?: string;
@@ -49,6 +51,7 @@ export type PhiMarkdownWidgetPlacement = PhiWidgetPlacementBase & {
49
51
  sourceLocale?: string;
50
52
  revalidateSeconds?: number;
51
53
  textAlign?: PhiMarkdownTextAlign;
54
+ imageFloat?: PhiMarkdownImageFloat;
52
55
  textBlockSpacingBefore?: PhiSpacingScaleKey;
53
56
  textBlockSpacingAfter?: PhiSpacingScaleKey;
54
57
  headingBlockSpacingBefore?: PhiSpacingScaleKey;