@wix/himalaya-cli 0.1237.0 → 0.1239.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.
@@ -192,6 +192,14 @@ export declare function emptyState(props: {
192
192
  actionId: string;
193
193
  params?: Record<string, string>;
194
194
  };
195
+ /** Required with `action` (#3846) — the CTA label; there is no default. */
196
+ actionLabel?: string;
197
+ /** `typography` token names (#3846). Defaults: "bodyEmphasis" / "caption". */
198
+ titleStyle?: string;
199
+ subtitleStyle?: string;
200
+ /** The CTA Button's `style` / `size` (#3846). Defaults: "secondary" / "sm". */
201
+ actionStyle?: "primary" | "secondary" | "plain";
202
+ actionSize?: "sm" | "md";
195
203
  }): Component;
196
204
  export declare function appIntent(pkg: string, type: string, props: unknown, opts?: {
197
205
  id?: string;
@@ -549,13 +549,28 @@ export interface CollapsingHeaderProps {
549
549
  /** 0.0 = static, 1.0 = full parallax (default 0.5). */
550
550
  parallaxFactor?: number;
551
551
  }
552
+ /**
553
+ * The ONE standard empty state: a centred stack of icon tile, title, subtitle and
554
+ * button, in that order. Only `title` is required; an absent part draws nothing and keeps
555
+ * no gap. Padding and gaps come from the `spacing` tokens, text from `typography`.
556
+ */
552
557
  export interface EmptyStateProps {
553
558
  title: string;
554
559
  subtitle?: string;
555
560
  icon?: IconRef;
561
+ /** The CTA. Drawn as a real `Button` (see `actionStyle` / `actionSize`). */
556
562
  action?: Action;
557
- /** #1059 — CTA label; overridable for localized hosts. Default "Take action". */
563
+ /** #1059 — CTA label, also its accessibility label. REQUIRED when `action` is set:
564
+ * there is no default, `himi validate` reports an error, and no button is drawn. */
558
565
  actionLabel?: string;
566
+ /** `typography` token for the title. Default `"bodyEmphasis"`. */
567
+ titleStyle?: string;
568
+ /** `typography` token for the subtitle. Default `"caption"`. */
569
+ subtitleStyle?: string;
570
+ /** The CTA's Button `style`. Default `"secondary"`. */
571
+ actionStyle?: "primary" | "secondary" | "plain";
572
+ /** The CTA's Button `size`. Default `"sm"`. */
573
+ actionSize?: "sm" | "md";
559
574
  }
560
575
  export interface InterpolatedText {
561
576
  template: string;
package/dist/cli.mjs CHANGED
@@ -6878,6 +6878,7 @@ function resolveTranslation(value, catalog, sourceCatalog) {
6878
6878
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
6879
6879
  var init_bindings = __esm({
6880
6880
  "../../core/ts/src/bindings.ts"() {
6881
+ "use strict";
6881
6882
  BINDING_SIGILS = ["service", "flag", "item", "t"];
6882
6883
  BINDING_SIGIL_RE = new RegExp(
6883
6884
  `^\\$(?:${BINDING_SIGILS.filter((s2) => s2 !== "t").join("|")})\\b|^\\$t:`
@@ -10413,6 +10414,41 @@ function mediaFieldFindings(descriptor) {
10413
10414
  visit(descriptor, "", true);
10414
10415
  return out2;
10415
10416
  }
10417
+ function emptyStateFindings(descriptor) {
10418
+ const out2 = [];
10419
+ const visit = (v2, path2, atRoot) => {
10420
+ if (Array.isArray(v2)) {
10421
+ v2.forEach((x2, i2) => visit(x2, `${path2}[${i2}]`, false));
10422
+ return;
10423
+ }
10424
+ if (!v2 || typeof v2 !== "object") return;
10425
+ const record2 = v2;
10426
+ const body = record2.body;
10427
+ if (body?.package === "himalaya" && body.type === "EmptyState") {
10428
+ const p2 = body.props ?? {};
10429
+ const base = path2 ? `${path2}.body.props` : "body.props";
10430
+ const label2 = p2.actionLabel;
10431
+ if (p2.action != null && (typeof label2 !== "string" || label2.trim() === "")) {
10432
+ out2.push({
10433
+ path: `${base}.actionLabel`,
10434
+ message: "EmptyState.action is set but EmptyState.actionLabel is missing. There is no default label: the renderers draw no button, so the action is unreachable. Set `actionLabel` to the button text (it is also the accessibility label), or remove `action`"
10435
+ });
10436
+ }
10437
+ if (p2.actionStyle !== void 0 && !(typeof p2.actionStyle === "string" && EMPTY_STATE_ACTION_STYLES.has(p2.actionStyle))) {
10438
+ out2.push({ path: `${base}.actionStyle`, message: `EmptyState.actionStyle must be "primary", "secondary" or "plain", got ${JSON.stringify(p2.actionStyle)}` });
10439
+ }
10440
+ if (p2.actionSize !== void 0 && !(typeof p2.actionSize === "string" && EMPTY_STATE_ACTION_SIZES.has(p2.actionSize))) {
10441
+ out2.push({ path: `${base}.actionSize`, message: `EmptyState.actionSize must be "sm" or "md", got ${JSON.stringify(p2.actionSize)}` });
10442
+ }
10443
+ }
10444
+ for (const [key2, child] of Object.entries(record2)) {
10445
+ if (isOpaqueAuthorJson(key2, child, atRoot)) continue;
10446
+ visit(child, path2 ? `${path2}.${key2}` : key2, false);
10447
+ }
10448
+ };
10449
+ visit(descriptor, "", true);
10450
+ return out2;
10451
+ }
10416
10452
  function richTextFieldFindings(descriptor) {
10417
10453
  const out2 = [];
10418
10454
  const visit = (v2, path2, atRoot) => {
@@ -10552,6 +10588,9 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
10552
10588
  for (const finding of richTextFieldFindings(descriptor)) {
10553
10589
  issues.push({ severity: "error", pass: "rich-text-field", path: finding.path, message: finding.message });
10554
10590
  }
10591
+ for (const finding of emptyStateFindings(descriptor)) {
10592
+ issues.push({ severity: "error", pass: "empty-state", path: finding.path, message: finding.message });
10593
+ }
10555
10594
  const bindings = collectServiceBindings(descriptor);
10556
10595
  const descriptorActions = collectDescriptorActions(descriptor);
10557
10596
  const workerActions = descriptorActions.filter(({ actionId }) => actionId.startsWith("tier5."));
@@ -11049,7 +11088,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
11049
11088
  services: Object.keys(services)
11050
11089
  };
11051
11090
  }
11052
- var CRAWL_BLOCKING_PASSES, BOOT_EVAL_TIMEOUT_MS, DRIFT_PROBES, DEVICE_PARITY_WITHHELD, defaultEvaluator, bootEvaluator, actionEvaluator, NATIVE_PROP_ENUMS, KNOWN_PROPS, IN_ARRAY_BINDING_PROPS, SERVICE_SIGIL, INTEGER_PROPS, NATIVE_REQUIRED_PROPS, bareNestedClass, NATIVE_NESTED_CLASSES, NATIVE_MODIFIERS_CLASS, NATIVE_DESCRIPTOR_CLASS, NATIVE_DESCRIPTOR_NESTED, NATIVE_NESTED_PROPS, MODIFIER_KEYS, INTEGER_MODIFIERS, MODIFIER_KINDS, SCHEMA_SCORED_NODES, CORE_PACKAGES, LEGACY_UNSCORED_KEYS, DYNAMIC_PLACEHOLDER, LITERAL_ONLY_PROPS, SERVICE_ONLY_BINDING_PROPS, WHOLE_VALUE_BINDING, EMBEDDED_BINDING, OPTIONS_BINDING_PROPS, EDITOR_SERVICE_NAME;
11091
+ var CRAWL_BLOCKING_PASSES, BOOT_EVAL_TIMEOUT_MS, DRIFT_PROBES, DEVICE_PARITY_WITHHELD, defaultEvaluator, bootEvaluator, actionEvaluator, NATIVE_PROP_ENUMS, KNOWN_PROPS, IN_ARRAY_BINDING_PROPS, SERVICE_SIGIL, INTEGER_PROPS, NATIVE_REQUIRED_PROPS, bareNestedClass, NATIVE_NESTED_CLASSES, NATIVE_MODIFIERS_CLASS, NATIVE_DESCRIPTOR_CLASS, NATIVE_DESCRIPTOR_NESTED, NATIVE_NESTED_PROPS, MODIFIER_KEYS, INTEGER_MODIFIERS, MODIFIER_KINDS, SCHEMA_SCORED_NODES, CORE_PACKAGES, LEGACY_UNSCORED_KEYS, DYNAMIC_PLACEHOLDER, LITERAL_ONLY_PROPS, SERVICE_ONLY_BINDING_PROPS, WHOLE_VALUE_BINDING, EMBEDDED_BINDING, OPTIONS_BINDING_PROPS, EDITOR_SERVICE_NAME, EMPTY_STATE_ACTION_STYLES, EMPTY_STATE_ACTION_SIZES;
11053
11092
  var init_validate_descriptor_core = __esm({
11054
11093
  "../../core/dev-server/src/validate-descriptor.core.ts"() {
11055
11094
  init_validator();
@@ -11275,7 +11314,17 @@ var init_validate_descriptor_core = __esm({
11275
11314
  slicesPath: "string",
11276
11315
  thickness: "number"
11277
11316
  },
11278
- EmptyState: { action: "any", actionLabel: "string", icon: "string", subtitle: "string", title: "string" },
11317
+ EmptyState: {
11318
+ action: "any",
11319
+ actionLabel: "string",
11320
+ actionSize: "string",
11321
+ actionStyle: "string",
11322
+ icon: "string",
11323
+ subtitle: "string",
11324
+ subtitleStyle: "string",
11325
+ title: "string",
11326
+ titleStyle: "string"
11327
+ },
11279
11328
  Fab: { action: "any", icon: "string", label: "string", size: "string", tint: "string" },
11280
11329
  FieldError: { severity: "string", text: "string", textPath: "string", visible: "boolean" },
11281
11330
  FilterChips: {
@@ -12006,6 +12055,8 @@ var init_validate_descriptor_core = __esm({
12006
12055
  EMBEDDED_BINDING = /\$service:[A-Za-z0-9_-]+\.[A-Za-z0-9_.[\]]+|\$flag:[A-Za-z0-9_.-]+|\$t:[A-Za-z0-9_.-]+|\$item(?:\.[A-Za-z0-9_.[\]]+)?/;
12007
12056
  OPTIONS_BINDING_PROPS = /* @__PURE__ */ new Set(["optionsPath"]);
12008
12057
  EDITOR_SERVICE_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
12058
+ EMPTY_STATE_ACTION_STYLES = /* @__PURE__ */ new Set(["primary", "secondary", "plain"]);
12059
+ EMPTY_STATE_ACTION_SIZES = /* @__PURE__ */ new Set(["sm", "md"]);
12009
12060
  }
12010
12061
  });
12011
12062
 
@@ -18048,6 +18099,73 @@ var init_icon2 = __esm({
18048
18099
  }
18049
18100
  });
18050
18101
 
18102
+ // ../native-config/watch-companion.ts
18103
+ function watchMode(mode) {
18104
+ if (mode === void 0 || mode === "standalone") return "standalone";
18105
+ if (mode === "companion") return "companion";
18106
+ throw new Error(`native-config: watch.mode must be "standalone" or "companion", got "${mode}"`);
18107
+ }
18108
+ function parseIosBundleId(configTs) {
18109
+ return /\bbundleId\s*:\s*["']([^"']+)["']/.exec(configTs)?.[1];
18110
+ }
18111
+ function companionWatchBundleId(iosBundleId) {
18112
+ return `${iosBundleId}.watchkitapp`;
18113
+ }
18114
+ function requireIosBundleId(iosBundleId) {
18115
+ if (!iosBundleId) {
18116
+ throw new Error(`native-config: watch.mode "companion" needs the app's iOS bundleId in config.ts \u2014 the watch app is paired to it`);
18117
+ }
18118
+ return iosBundleId;
18119
+ }
18120
+ function watchPairingInfo(mode, iosBundleId) {
18121
+ if (mode === "standalone") return { WKWatchOnly: true };
18122
+ return {
18123
+ WKCompanionAppBundleIdentifier: requireIosBundleId(iosBundleId),
18124
+ // The watch still launches and renders its last/baked content with the phone out of reach.
18125
+ WKRunsIndependentlyOfCompanionApp: true
18126
+ };
18127
+ }
18128
+ function watchSettingsPlan(mode, iosBundleId, standaloneBundleId) {
18129
+ if (mode === "standalone") {
18130
+ return standaloneBundleId ? { INFOPLIST_KEY_WKWatchOnly: "YES", PRODUCT_BUNDLE_IDENTIFIER: standaloneBundleId } : { INFOPLIST_KEY_WKWatchOnly: "YES" };
18131
+ }
18132
+ return { PRODUCT_BUNDLE_IDENTIFIER: companionWatchBundleId(requireIosBundleId(iosBundleId)) };
18133
+ }
18134
+ function watchTargetBundleId(watchProjectYml) {
18135
+ return /^\s{4}bundleId:\s*(\S+)\s*$/m.exec(watchProjectYml)?.[1];
18136
+ }
18137
+ function watchProjectName(watchProjectYml) {
18138
+ return /^name:\s*(\S+)\s*$/m.exec(watchProjectYml)?.[1];
18139
+ }
18140
+ function watchProjectReferenceLines(mode, watchName) {
18141
+ if (mode === "standalone") return [];
18142
+ return [
18143
+ "projectReferences:",
18144
+ ` ${WATCH_PROJECT_REF}:`,
18145
+ ` path: ../watchos/${watchName}.xcodeproj`
18146
+ ];
18147
+ }
18148
+ function watchEmbedLines(mode, watchName) {
18149
+ if (mode === "standalone") return [];
18150
+ return [
18151
+ `- target: ${WATCH_PROJECT_REF}/${watchName}`,
18152
+ " embed: true",
18153
+ " codeSign: false",
18154
+ " copy:",
18155
+ " destination: productsDirectory",
18156
+ " subpath: $(CONTENTS_FOLDER_PATH)/Watch"
18157
+ ];
18158
+ }
18159
+ var LABEL_WATCH_SETTINGS, LABEL_WATCH_PROJECT, LABEL_WATCH_EMBED, WATCH_PROJECT_REF;
18160
+ var init_watch_companion = __esm({
18161
+ "../native-config/watch-companion.ts"() {
18162
+ LABEL_WATCH_SETTINGS = "himalaya:watch:settings";
18163
+ LABEL_WATCH_PROJECT = "himalaya:watch:project";
18164
+ LABEL_WATCH_EMBED = "himalaya:watch:embed";
18165
+ WATCH_PROJECT_REF = "Watch";
18166
+ }
18167
+ });
18168
+
18051
18169
  // ../native-config/live-activity.ts
18052
18170
  function liveWidgetSource(deferredActions) {
18053
18171
  const supportedIds = deferredActions ? "[.refresh, .markRead]" : "[.refresh]";
@@ -18348,15 +18466,16 @@ function parseStringArray(configTs, field) {
18348
18466
  return [...body.matchAll(/["']([^"']+)["']/g)].map((x2) => x2[1]);
18349
18467
  }
18350
18468
  function parseWatchConfig(configTs) {
18351
- const block = /watch\s*[:=]\s*\{([^}]*)\}/.exec(configTs);
18469
+ const block = /export\s+const\s+watch\s*=\s*\{([^}]*)\}/.exec(configTs) ?? [...configTs.matchAll(/\bwatch\s*[:=]\s*\{([^}]*)\}/g)].find((m2) => /\bmode\s*:/.test(m2[1]));
18352
18470
  if (!block) return {};
18353
18471
  const body = block[1];
18354
18472
  const mode = /mode\s*:\s*["']([^"']+)["']/.exec(body)?.[1];
18355
18473
  const serve = /serve\s*:\s*["']([^"']+)["']/.exec(body)?.[1];
18356
18474
  return { mode, serve };
18357
18475
  }
18358
- function watchInfoPlan(watch2) {
18359
- const plan = { HimalayaWatchMode: watch2.mode ?? "standalone" };
18476
+ function watchInfoPlan(watch2, iosBundleId) {
18477
+ const mode = watchMode(watch2.mode);
18478
+ const plan = { HimalayaWatchMode: mode, ...watchPairingInfo(mode, iosBundleId) };
18360
18479
  if (watch2.serve) plan.HimalayaWatchServe = watch2.serve;
18361
18480
  return plan;
18362
18481
  }
@@ -18599,7 +18718,19 @@ function genNativeConfig(app, opts = {}) {
18599
18718
  }
18600
18719
  const macos = macosPlanFor(ids, withCopy(ov.macos));
18601
18720
  const android = androidPlanFor(ids, ov.android);
18602
- const watch2 = watchInfoPlan(parseWatchConfig(configTs));
18721
+ const watchConfig = parseWatchConfig(configTs);
18722
+ const iosBundleId = parseIosBundleId(configTs);
18723
+ const watch2 = watchInfoPlan(watchConfig, iosBundleId);
18724
+ const watchYml = watchosProjectYml(app);
18725
+ const watchYmlText = existsSync26(watchYml) ? readFileSync26(watchYml, "utf8") : "";
18726
+ const watchSettings = watchSettingsPlan(watchMode(watchConfig.mode), iosBundleId, watchTargetBundleId(watchYmlText));
18727
+ const watchName = existsSync26(watchYml) ? watchProjectName(readFileSync26(watchYml, "utf8")) : void 0;
18728
+ const watchEmbedFor = (lines) => (indent) => {
18729
+ const mode = watchMode(watchConfig.mode);
18730
+ if (mode === "standalone") return [];
18731
+ if (!watchName) throw new Error(`${app}: watch.mode "companion" but ${watchYml} has no \`name:\` to embed`);
18732
+ return lines(mode, watchName).map((line) => indent + line);
18733
+ };
18603
18734
  const tv = tvInfoPlan(parseTvConfig(configTs));
18604
18735
  const orientation = parseOrientationEnvelope(configTs);
18605
18736
  const carCategory = resolveCarCategory(parseCarConfig(configTs), app);
@@ -18641,6 +18772,10 @@ function genNativeConfig(app, opts = {}) {
18641
18772
  // watchOS (RFC 0047): the watch runtime mode/serve, declared once in config.ts. A watch
18642
18773
  // target opts in by adding the LABEL_WATCH markers; a file without them is skipped.
18643
18774
  applyToFile(watchosProjectYml(app), "yaml", LABEL_WATCH, (i2) => renderPlistYaml(watch2, i2), check),
18775
+ applyToFile(watchosProjectYml(app), "yaml", LABEL_WATCH_SETTINGS, (i2) => renderPlistYaml(watchSettings, i2), check),
18776
+ // #3704 — the iOS half of a companion pair (project reference + embed into <App>.app/Watch).
18777
+ applyToFile(iosProjectYml(app), "yaml", LABEL_WATCH_PROJECT, watchEmbedFor(watchProjectReferenceLines), check),
18778
+ applyToFile(iosProjectYml(app), "yaml", LABEL_WATCH_EMBED, watchEmbedFor(watchEmbedLines), check),
18644
18779
  // tvOS (RFC 0051): HimalayaTvMode managed block; skipped when markers absent.
18645
18780
  applyToFile(tvosProjectYml(app), "yaml", LABEL_TV, (i2) => renderPlistYaml(tv, i2), check),
18646
18781
  // Android TV (RFC 0051 WS7): Leanback features on the `*-tv` module; skipped when markers absent.
@@ -18705,6 +18840,7 @@ var init_gen_native_config = __esm({
18705
18840
  init_paths2();
18706
18841
  init_splash();
18707
18842
  init_icon2();
18843
+ init_watch_companion();
18708
18844
  init_live_activity();
18709
18845
  init_car();
18710
18846
  init_localweb();
@@ -116514,6 +116650,10 @@ function withExtras(name, render) {
116514
116650
  Wrapped.displayName = name;
116515
116651
  return Wrapped;
116516
116652
  }
116653
+ function spacingToken(ctx, [name, fallback]) {
116654
+ const v2 = ctx.tokens?.spacing?.[name];
116655
+ return typeof v2 === "number" && Number.isFinite(v2) ? Math.max(0, v2) : fallback;
116656
+ }
116517
116657
  function sheetTabStops(root) {
116518
116658
  const all = Array.from(root.querySelectorAll(NATIVELY_TABBABLE)).filter((el2) => el2.tabIndex >= 0 && !el2.disabled && !el2.closest('[inert], [hidden], [aria-hidden="true"]'));
116519
116659
  const ordered = all.filter((el2) => el2.tabIndex > 0).sort((a2, b2) => a2.tabIndex - b2.tabIndex);
@@ -117702,12 +117842,7 @@ function renderInner(node, ctx) {
117702
117842
  return /* @__PURE__ */ jsx9("div", { style: { flex: 1 } });
117703
117843
  }
117704
117844
  case "EmptyState":
117705
- return /* @__PURE__ */ jsxs6("div", { style: { textAlign: "center", width: "100%", padding: 12, boxSizing: "border-box", ...wrap }, children: [
117706
- /* @__PURE__ */ jsx9("div", { style: { width: 46, height: 46, borderRadius: 13, background: C2("surfaceTertiary", "#eee"), display: "grid", placeItems: "center", margin: "0 auto 12px", color: C2("textSecondary", "#9aa2ad") }, children: iconSvg(props.icon, { size: 24 }) }),
117707
- /* @__PURE__ */ jsx9("div", { style: { fontFamily: bodyFont(ctx), fontWeight: 600, fontSize: pxToRem(16), color: C2("textPrimary", "#111") }, children: asBoundText(props.title, ctx) || "Empty" }),
117708
- props.subtitle && /* @__PURE__ */ jsx9("div", { style: { fontFamily: bodyFont(ctx), fontSize: pxToRem(13), color: C2("textSecondary", "#888"), marginTop: 3 }, children: txt(props.subtitle, ctx) }),
117709
- actionTextButton(ctx, props.action, asBoundText(props.actionLabel, ctx) || "Take action", accentTextColor(ctx), { margin: "12px auto 0", textAlign: "center" })
117710
- ] });
117845
+ return /* @__PURE__ */ jsx9(EmptyStateNode, { props, ctx, wrap, nodeId: node.id });
117711
117846
  // ───────────── atoms ─────────────
117712
117847
  case "Text":
117713
117848
  return /* @__PURE__ */ jsx9(TextNode, { props, ctx, wrap });
@@ -118130,7 +118265,7 @@ function renderInner(node, ctx) {
118130
118265
  }
118131
118266
  }
118132
118267
  }
118133
- var useIsomorphicLayoutEffect, ResponsiveGrid, SPLIT_EXPANDED_MIN_WIDTH, SPLIT_COLUMN_GAP, ResponsiveSplitView, ABSOLUTE_URL_RE, appearKeyframesInjected, pressStylesInjected, REVEAL_ON_FOCUS_ATTR, FOCUS_RING_CSS, focusStylesInjected, HLIST_CHILD_CSS, hListStylesInjected, listAnimateRuntimeInstalled, transitionKeyframesInjected, countUpRuntimeInjected, MEASURE_PREFIX, measureRuntimeInjected, revealRuntimeInjected, vectorAnimStylesInjected, scrollProgressRuntimeInjected, confettiRuntimeInjected, lottieRuntimeInjected, riveRuntimeInjected, gradientKeyframesInjected, parallaxRuntimeInjected, spinKeyframesInjected, marqueeKeyframesInjected, alignItems, zJustifyItems, hAlignItems, bodyFont, kids2, rowKidCtx, rowKids, staggeredKids, asTextL10n, asFinalTextL10n, asBoundText, useBoundString, InterpolatedTextView, txt, selectFragmentCase, fragmentValueHash, fragmentSlotContent, FragmentSlotNode, RichContentEditorNode, MediaFieldNode, WorkerSubtreeNode, RichContentNode, resolveItemDeep2, resolveSwipeActions, resolveModBindings, resolvedComponentId, directServiceBindings, withRootStyle, ReactiveDirectScalars, HiddenGate, KEYBOARD_INPUT_MODES, AUTOCAPITALIZE_VALUES, ENTER_KEY_HINTS, AUTOGROW_CEILING, EditableField, MenuNode, LoadMoreSentinel, FieldErrorNode, FormView, MediaSlotProvider, MEDIA_ITEM_BINDABLE, MediaPlayerView, VideoNode, PdfDocumentCanvas, PdfViewNode, WebViewNode, InteractivePicker, InteractiveDatePicker, InteractiveSearchField, InteractiveStepper, SYNTHETIC_TAP_ROLE, SYNTHETIC_TABINDEX, INFERRED_DECORATIVE, SELF_RENDERED_LEAF, ROLE_WHEN_NAMED, AUTHORED_ROLE, PENDING_RANGE, RANGE_ROLES, NODE_A11Y_ATTRS, INTERACTIVE_ROLES, ALWAYS_INTERACTIVE_COMPONENT_TYPES, TAP_ACTIVATOR_ATTR, TAP_GROUP_ATTR, VISUALLY_HIDDEN, NAME_SKIP_SELECTOR, activatorNameObservers, ACTIVATOR_STATE_ATTRS, controlNode, ToggleNode, SliderNode, SelectionControlNode, TabsNode, SegmentedControlNode, DisclosureGroupNode, GridNode, SplitViewNode, TextNode, ImageNode, ANIMATION_LEAF, LottieNode, RiveNode, AvatarNode, IconNode, ButtonNode, OtpInputNode, ProgressBarNode, ACTIVITY_DEFAULT_NAME, ActivityIndicatorNode, NATIVELY_TABBABLE, BottomSheetNode, PRESENTED_OVERLAY_ATTR, PopoverNode, ChipsInputNode, ChipNode, TableNode, GaugeRing, GaugeNode, RATING_METRICS, ratingMetrics, RatingNode, AvatarStripNode, CHART_STROKE_CYCLE, chartStrokeAt, chartBarCenter, ChartNode, donutSliceOpacity, DonutChartNode, LegendMarker, LegendNode, cellAlign, DataTableNode, voiceNoteState, voiceNoteDelivery, VOICE_NOTE_DEFAULT_BARS, clampBarCount, VoiceNoteNode, TimelineNode, WheelPickerNode, ColorPickerNode, FilterChipsNode, TILE_PX, MAX_MERCATOR_LAT, DEFAULT_TILE_MAX_ZOOM, CIRCLE_SEGMENTS, METRES_PER_DEG_LAT, MARKER_HIT_RADIUS, SHAPE_HIT_RADIUS, MapNode, CollapsingHeaderNode, UnsupportedOnSurface, CALENDAR_DATE_ONLY_RE, CAL_DEFAULT_MAX_DOTS, CAL_AGENDA_INITIAL_DAYS, CAL_AGENDA_PAGE_DAYS, CAL_DEFAULT_SNAP_MINUTES, CAL_UNASSIGNED_LANE_ID, MonthCalendarNode, TimeGridNode, AgendaNode, ARIA_ROLE, initialFocusScheduled, PRESENTED_SCOPE, focusRoots, A11yHintDescription, WebRenderer;
118268
+ var useIsomorphicLayoutEffect, ResponsiveGrid, SPLIT_EXPANDED_MIN_WIDTH, SPLIT_COLUMN_GAP, ResponsiveSplitView, ABSOLUTE_URL_RE, appearKeyframesInjected, pressStylesInjected, REVEAL_ON_FOCUS_ATTR, FOCUS_RING_CSS, focusStylesInjected, HLIST_CHILD_CSS, hListStylesInjected, listAnimateRuntimeInstalled, transitionKeyframesInjected, countUpRuntimeInjected, MEASURE_PREFIX, measureRuntimeInjected, revealRuntimeInjected, vectorAnimStylesInjected, scrollProgressRuntimeInjected, confettiRuntimeInjected, lottieRuntimeInjected, riveRuntimeInjected, gradientKeyframesInjected, parallaxRuntimeInjected, spinKeyframesInjected, marqueeKeyframesInjected, alignItems, zJustifyItems, hAlignItems, bodyFont, kids2, rowKidCtx, rowKids, staggeredKids, asTextL10n, asFinalTextL10n, asBoundText, useBoundString, InterpolatedTextView, txt, selectFragmentCase, fragmentValueHash, fragmentSlotContent, FragmentSlotNode, RichContentEditorNode, MediaFieldNode, WorkerSubtreeNode, RichContentNode, resolveItemDeep2, resolveSwipeActions, resolveModBindings, resolvedComponentId, directServiceBindings, withRootStyle, ReactiveDirectScalars, HiddenGate, KEYBOARD_INPUT_MODES, AUTOCAPITALIZE_VALUES, ENTER_KEY_HINTS, AUTOGROW_CEILING, EditableField, MenuNode, LoadMoreSentinel, FieldErrorNode, FormView, MediaSlotProvider, MEDIA_ITEM_BINDABLE, MediaPlayerView, VideoNode, PdfDocumentCanvas, PdfViewNode, WebViewNode, InteractivePicker, InteractiveDatePicker, InteractiveSearchField, InteractiveStepper, SYNTHETIC_TAP_ROLE, SYNTHETIC_TABINDEX, INFERRED_DECORATIVE, SELF_RENDERED_LEAF, ROLE_WHEN_NAMED, AUTHORED_ROLE, PENDING_RANGE, RANGE_ROLES, NODE_A11Y_ATTRS, INTERACTIVE_ROLES, ALWAYS_INTERACTIVE_COMPONENT_TYPES, TAP_ACTIVATOR_ATTR, TAP_GROUP_ATTR, VISUALLY_HIDDEN, NAME_SKIP_SELECTOR, activatorNameObservers, ACTIVATOR_STATE_ATTRS, controlNode, ToggleNode, SliderNode, SelectionControlNode, TabsNode, SegmentedControlNode, DisclosureGroupNode, GridNode, SplitViewNode, TextNode, ImageNode, ANIMATION_LEAF, LottieNode, RiveNode, AvatarNode, IconNode, EMPTY_STATE_SPACING, EmptyStateNode, ButtonNode, OtpInputNode, ProgressBarNode, ACTIVITY_DEFAULT_NAME, ActivityIndicatorNode, NATIVELY_TABBABLE, BottomSheetNode, PRESENTED_OVERLAY_ATTR, PopoverNode, ChipsInputNode, ChipNode, TableNode, GaugeRing, GaugeNode, RATING_METRICS, ratingMetrics, RatingNode, AvatarStripNode, CHART_STROKE_CYCLE, chartStrokeAt, chartBarCenter, ChartNode, donutSliceOpacity, DonutChartNode, LegendMarker, LegendNode, cellAlign, DataTableNode, voiceNoteState, voiceNoteDelivery, VOICE_NOTE_DEFAULT_BARS, clampBarCount, VoiceNoteNode, TimelineNode, WheelPickerNode, ColorPickerNode, FilterChipsNode, TILE_PX, MAX_MERCATOR_LAT, DEFAULT_TILE_MAX_ZOOM, CIRCLE_SEGMENTS, METRES_PER_DEG_LAT, MARKER_HIT_RADIUS, SHAPE_HIT_RADIUS, MapNode, CollapsingHeaderNode, UnsupportedOnSurface, CALENDAR_DATE_ONLY_RE, CAL_DEFAULT_MAX_DOTS, CAL_AGENDA_INITIAL_DAYS, CAL_AGENDA_PAGE_DAYS, CAL_DEFAULT_SNAP_MINUTES, CAL_UNASSIGNED_LANE_ID, MonthCalendarNode, TimeGridNode, AgendaNode, ARIA_ROLE, initialFocusScheduled, PRESENTED_SCOPE, focusRoots, A11yHintDescription, WebRenderer;
118134
118269
  var init_renderNode = __esm({
118135
118270
  "../../core/web-renderer/src/renderNode.tsx"() {
118136
118271
  "use strict";
@@ -120212,6 +120347,49 @@ var init_renderNode = __esm({
120212
120347
  const a11y = described ? { role: "img", "aria-label": String(contentDescription), [SELF_RENDERED_LEAF]: true } : { "aria-hidden": "true", [INFERRED_DECORATIVE]: true, [SELF_RENDERED_LEAF]: true, [ROLE_WHEN_NAMED]: "img" };
120213
120348
  return /* @__PURE__ */ jsx9("span", { ...a11y, style: { display: "inline-grid", placeItems: "center", width: size + 6, height: size + 6, lineHeight: 1, flex: "none", ...wrap }, children: iconSvg(iconName, { size, color: C2(resolveScalarStatic(props.tint, ctx) ?? "textPrimary", "#636b78") }) });
120214
120349
  });
120350
+ EMPTY_STATE_SPACING = {
120351
+ padding: ["lg", 16],
120352
+ iconGap: ["md", 12],
120353
+ subtitleGap: ["xs", 4],
120354
+ actionGap: ["lg", 16]
120355
+ };
120356
+ EmptyStateNode = withExtras("EmptyStateNode", ({ props, ctx, wrap, nodeId }) => {
120357
+ const C2 = ctx.color;
120358
+ const subtitleText = useBoundString(props.subtitle, ctx);
120359
+ const titleFont = ctx.font(typeof props.titleStyle === "string" && props.titleStyle ? props.titleStyle : "bodyEmphasis");
120360
+ const subtitleFont = ctx.font(typeof props.subtitleStyle === "string" && props.subtitleStyle ? props.subtitleStyle : "caption");
120361
+ const fontCss = (f2) => ({
120362
+ fontFamily: fontStack(f2.family, ctx.idiom),
120363
+ fontSize: f2.remSize,
120364
+ fontWeight: f2.weight,
120365
+ lineHeight: f2.line,
120366
+ letterSpacing: f2.letterSpacing
120367
+ });
120368
+ const hasIcon2 = typeof props.icon === "string" ? props.icon.length > 0 : props.icon != null;
120369
+ const hasSubtitle = subtitleText.trim().length > 0;
120370
+ const label2 = props.actionLabel;
120371
+ const hasAction = props.action != null && typeof label2 === "string" && label2.trim().length > 0;
120372
+ const padding = spacingToken(ctx, EMPTY_STATE_SPACING.padding);
120373
+ return /* @__PURE__ */ jsxs6("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", width: "100%", padding, boxSizing: "border-box", ...wrap }, children: [
120374
+ hasIcon2 && /* @__PURE__ */ jsx9("div", { "data-empty-state-part": "icon", "aria-hidden": "true", style: { width: 46, height: 46, borderRadius: 13, background: C2("surfaceTertiary", "#eee"), display: "grid", placeItems: "center", color: C2("textSecondary", "#9aa2ad"), flexShrink: 0 }, children: iconSvg(props.icon, { size: 24 }) }),
120375
+ /* @__PURE__ */ jsx9("div", { "data-empty-state-part": "title", style: { ...fontCss(titleFont), color: C2("textPrimary", "#111"), marginTop: hasIcon2 ? spacingToken(ctx, EMPTY_STATE_SPACING.iconGap) : 0, overflowWrap: "anywhere" }, children: txt(props.title, ctx) }),
120376
+ hasSubtitle && /* @__PURE__ */ jsx9("div", { "data-empty-state-part": "subtitle", style: { ...fontCss(subtitleFont), color: C2("textSecondary", "#888"), marginTop: spacingToken(ctx, EMPTY_STATE_SPACING.subtitleGap), overflowWrap: "anywhere" }, children: isBinding(props.subtitle) ? subtitleText : txt(props.subtitle, ctx) }),
120377
+ hasAction && /* @__PURE__ */ jsx9("div", { "data-empty-state-part": "action", style: { marginTop: spacingToken(ctx, EMPTY_STATE_SPACING.actionGap), display: "flex", justifyContent: "center" }, children: renderNode({
120378
+ id: `${nodeId ?? "empty_state"}.action`,
120379
+ body: {
120380
+ package: "himalaya",
120381
+ type: "Button",
120382
+ props: {
120383
+ title: label2,
120384
+ action: props.action,
120385
+ style: props.actionStyle ?? "secondary",
120386
+ size: props.actionSize ?? "sm",
120387
+ fullWidth: false
120388
+ }
120389
+ }
120390
+ }, { ...ctx, rowStretch: false }) })
120391
+ ] });
120392
+ });
120215
120393
  ButtonNode = withExtras("ButtonNode", ({ props, ctx, wrap }) => {
120216
120394
  const C2 = ctx.color;
120217
120395
  const boundTitle = useResolvedScalar(isBinding(props.title) ? props.title : void 0, ctx);
@@ -143473,23 +143651,23 @@ var init_preview_assets_generated = __esm({
143473
143651
  "../../packages/serve/src/preview-assets.generated.ts"() {
143474
143652
  init_preview_assets_format();
143475
143653
  PREVIEW_ASSET_MANIFEST = [
143476
- { key: "assets/index-foA0ySYi.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1191112, sha256: "18d4cc6a89df036267a88103f1c3a1e2bed96cfc78926bf48dc0c2c9b637546d" },
143477
- { key: "assets/pdf.worker.min-BmVo14Nb.mjs", contentType: "application/octet-stream", offset: 1191120, length: 1317034, sha256: "a33cfe728c584fdba4fcc1fd54bcdc2f9f2f13889ddbb5b2bd1d0f8cbe49b84e" },
143478
- { key: "assets/pdfDocument-mwaz-XmD.js", contentType: "application/javascript; charset=utf-8", offset: 2508154, length: 489166, sha256: "709898ff6336a208e677fff9def67063adaa4605492c81597468199e4968d2d3" },
143479
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 2997320, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
143480
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 3001930, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
143481
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 3490514, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
143482
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 3581438, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
143483
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 3672422, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
143484
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 3763402, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
143485
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 3854274, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
143486
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 3945254, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
143487
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 4053890, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
143488
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 4162974, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
143489
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 4272022, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
143490
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 4379722, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
143491
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 4467830, length: 4947, sha256: "58a871eb4e920e5f620d7cbe850b1418ac1cd3e08746145666cad95e2dcb9fd9" },
143492
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 4472777, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
143654
+ { key: "assets/index-BZS8K4zM.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1192364, sha256: "50d2524148ee0aedb74e5e6a548f269eb75b4a289f7a1de101de410c01e60bf5" },
143655
+ { key: "assets/pdf.worker.min-BmVo14Nb.mjs", contentType: "application/octet-stream", offset: 1192372, length: 1317034, sha256: "a33cfe728c584fdba4fcc1fd54bcdc2f9f2f13889ddbb5b2bd1d0f8cbe49b84e" },
143656
+ { key: "assets/pdfDocument-PXxklXbm.js", contentType: "application/javascript; charset=utf-8", offset: 2509406, length: 489166, sha256: "6df079a4bd8eaa2ed5abfb4af8154f2a4c0b8283fb26e1ef2f1964c2d7b6d1ac" },
143657
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 2998572, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
143658
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 3003182, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
143659
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 3491766, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
143660
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 3582690, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
143661
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 3673674, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
143662
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 3764654, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
143663
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 3855526, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
143664
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 3946506, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
143665
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 4055142, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
143666
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 4164226, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
143667
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 4273274, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
143668
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 4380974, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
143669
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 4469082, length: 4947, sha256: "ec96c9af3b4b5bf3113b3ef80c4a6f16b77f40e28903dd20813ce866207e145d" },
143670
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 4474029, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
143493
143671
  ];
143494
143672
  }
143495
143673
  });
Binary file
@@ -98,7 +98,7 @@
98
98
  "category": "Core",
99
99
  "propsType": "EmptyStateProps",
100
100
  "bindings": [],
101
- "propsSource": "export interface EmptyStateProps {\n title: string;\n subtitle?: string;\n icon?: IconRef;\n action?: Action;\n /** #1059 — CTA label; overridable for localized hosts. Default \"Take action\". */\n actionLabel?: string;\n}"
101
+ "propsSource": "export interface EmptyStateProps {\n title: string;\n subtitle?: string;\n icon?: IconRef;\n /** The CTA. Drawn as a real `Button` (see `actionStyle` / `actionSize`). */\n action?: Action;\n /** #1059 — CTA label, also its accessibility label. REQUIRED when `action` is set:\n * there is no default, `himi validate` reports an error, and no button is drawn. */\n actionLabel?: string;\n /** `typography` token for the title. Default `\"bodyEmphasis\"`. */\n titleStyle?: string;\n /** `typography` token for the subtitle. Default `\"caption\"`. */\n subtitleStyle?: string;\n /** The CTA's Button `style`. Default `\"secondary\"`. */\n actionStyle?: \"primary\" | \"secondary\" | \"plain\";\n /** The CTA's Button `size`. Default `\"sm\"`. */\n actionSize?: \"sm\" | \"md\";\n}"
102
102
  },
103
103
  {
104
104
  "name": "Text",
@@ -396,9 +396,19 @@ export interface EmptyStateProps {
396
396
  title: string;
397
397
  subtitle?: string;
398
398
  icon?: IconRef;
399
+ /** The CTA. Drawn as a real `Button` (see `actionStyle` / `actionSize`). */
399
400
  action?: Action;
400
- /** #1059 — CTA label; overridable for localized hosts. Default "Take action". */
401
+ /** #1059 — CTA label, also its accessibility label. REQUIRED when `action` is set:
402
+ * there is no default, `himi validate` reports an error, and no button is drawn. */
401
403
  actionLabel?: string;
404
+ /** `typography` token for the title. Default `"bodyEmphasis"`. */
405
+ titleStyle?: string;
406
+ /** `typography` token for the subtitle. Default `"caption"`. */
407
+ subtitleStyle?: string;
408
+ /** The CTA's Button `style`. Default `"secondary"`. */
409
+ actionStyle?: "primary" | "secondary" | "plain";
410
+ /** The CTA's Button `size`. Default `"sm"`. */
411
+ actionSize?: "sm" | "md";
402
412
  }
403
413
  ```
404
414
 
@@ -331,7 +331,7 @@ to drive them:
331
331
  | State | Compose with | Notes |
332
332
  |---|---|---|
333
333
  | **Loading** | `Skeleton` (shape of the eventual content) or `StatefulSection` / `ActivityIndicator` | Prefer a content-shaped `Skeleton` over a bare spinner — it previews the layout. The descriptor renders against the state-skeleton, then repaints as worker state lands. |
334
- | **Empty** | `EmptyState` (`title`, `subtitle`, `icon`) **plus a directive action** | An empty screen is an invitation to act, not a dead end — give it the next step (e.g. "Add your first item"). **Caveat:** `EmptyState`'s `action` renders a generic **"Take action"** button label (no field to set the wording). When the CTA text matters ("Clear filters", "Add your first item"), compose a *custom* empty block instead — a centered `icon` + `title` text + `caption` subtitle + a real labeled `button` — rather than the `EmptyState` component. |
334
+ | **Empty** | `EmptyState` (`title`, optional `icon` / `subtitle`) **plus a directive action** (`action` + `actionLabel`) | An empty screen is an invitation to act, not a dead end. Give it the next step, for example `"actionLabel": "Add your first item"`. Use `EmptyState` for **every** empty state, so all empty states in the app have the same layout and style. See [Empty states](#empty-states-one-component-one-layout) below. |
335
335
  | **Error** | `Banner` (or inline) **with a retry action** | Say what failed and offer recovery; never a blank screen or a raw error string. |
336
336
  | **Loaded** | the real content | The one everyone remembers to build. |
337
337
 
@@ -340,12 +340,43 @@ Bind visibility to worker state with `hidden_path` (`$service:…`/`$item.` data
340
340
  components precisely so states are first-class — use them. (Reproducing an existing app? Enumerate
341
341
  every state the *original* shows; the same discipline applies to net-new screens.)
342
342
 
343
+ ### Empty states: one component, one layout
344
+
345
+ `EmptyState` draws one standard layout on iOS, Android, macOS and web. Do not build an empty
346
+ state by hand from `Icon`, `Text` and `Button` nodes.
347
+
348
+ - The layout is a centred vertical stack, in this order: icon tile, title, subtitle, button.
349
+ - Only `title` is required. `icon`, `subtitle` and the button are optional. A missing part
350
+ takes no space.
351
+ - The title uses the `typography` token in `titleStyle`. The default is `bodyEmphasis`.
352
+ - The subtitle uses the `typography` token in `subtitleStyle`. The default is `caption`.
353
+ - The padding and the gaps come from the `spacing` tokens `lg`, `md` and `xs`.
354
+ - The button is a real `Button`. It uses the app's button tokens. Set `actionStyle`
355
+ (`primary`, `secondary` or `plain`) and `actionSize` (`sm` or `md`). The defaults are
356
+ `secondary` and `sm`.
357
+ - `actionLabel` is the button text and its accessibility label. It has no default. If you set
358
+ `action` without `actionLabel`, `himi validate` shows an error and no button is drawn.
359
+
360
+ ```jsonc
361
+ { "id": "saved.empty",
362
+ "body": { "package": "himalaya", "type": "EmptyState",
363
+ "props": { "icon": "bookmark", "title": "No saved items",
364
+ "subtitle": "Save a product to see it here.", "subtitleStyle": "footnoteRegular",
365
+ "action": { "actionId": "himi.open", "params": { "screenId": "shop" } },
366
+ "actionLabel": "Browse products" } },
367
+ "modifiers": { "hiddenPath": "$service:saved.notEmpty" } }
368
+ ```
369
+
370
+ To show the empty state only when the list is empty, bind `hiddenPath` to a worker boolean
371
+ (see `himi docs recipe-four-state`). Write the title and the subtitle as literal copy or as
372
+ `$t:` keys. Today, iOS and Android do not resolve a `$service:` binding in `title` or `subtitle`.
373
+
343
374
  **Loading & empty states are first-class — use the `Skeleton` and `EmptyState` component types.**
344
375
 
345
376
  There is **no builder-DSL package** — the `@wix/himalaya` umbrella exposes `./core`, `./state`,
346
377
  `wix-*`, … but no `skRect` / `emptyBlock` helpers. Author these states as the **raw descriptor
347
378
  nodes** `himi init` scaffolds: a `Skeleton` node for loading, and an `EmptyState` node (`icon` +
348
- `title`/`subtitle` + a labeled `cta`) for empty. Run `himi components EmptyState` /
379
+ `title`/`subtitle` + `action` with its `actionLabel`) for empty. Run `himi components EmptyState` /
349
380
  `himi components Skeleton` for the exact props. Written as raw nodes, these states ship over the
350
381
  air and render on every current binary.
351
382
 
@@ -5,7 +5,8 @@ from **one flat layout** — no conditional trees, no separate loading screen. T
5
5
  worker computes a **display string per state** (non-empty only in its state) and
6
6
  the descriptor binds each to a `Text` with `collapseWhenEmpty: true`. Sections
7
7
  that must fully appear/disappear gate with the `hiddenWhen`/`hiddenPath` modifier
8
- bound to a worker boolean.
8
+ bound to a worker boolean. The empty state is always an `EmptyState` node, gated
9
+ the same way.
9
10
 
10
11
  This is the corpus norm: of the in-repo worker bundles, ~3 in 4 model loading,
11
12
  more than half model error, and the reference apps (`apps/himi-owner-lite`,
@@ -19,8 +20,8 @@ interface State {
19
20
  status: "loading" | "ready" | "error";
20
21
  items: Row[];
21
22
  loadingText: string; // "Loading…" only while loading
22
- emptyText: string; // "No products yet." only when ready + 0 rows
23
23
  error: string; // the message only when failed
24
+ notEmpty: boolean; // gates the EmptyState (hiddenPath hides when TRUE)
24
25
  notErrored: boolean; // gates the Retry button (hiddenPath hides when TRUE)
25
26
  }
26
27
 
@@ -30,24 +31,29 @@ function shape(s: Partial<State>): State {
30
31
  return {
31
32
  status, items,
32
33
  loadingText: status === "loading" ? "Loading products…" : "",
33
- emptyText: status === "ready" && items.length === 0 ? "No products in this store yet." : "",
34
34
  error: s.error ?? "",
35
+ notEmpty: !(status === "ready" && items.length === 0),
35
36
  notErrored: status !== "error",
36
37
  };
37
38
  }
38
39
  ```
39
40
 
40
41
  Route **every** state change through `shape()` — set `status` + raw data, let it
41
- recompute the strings. Hand-maintaining the strings per action is how screens end
42
- up loading-and-errored at once.
42
+ recompute the strings and flags. Hand-maintaining them per action is how screens
43
+ end up loading-and-errored at once.
43
44
 
44
- ## 2. The descriptor: one Text per state + a gated Retry
45
+ ## 2. The descriptor: one node per state + a gated Retry
45
46
 
46
47
  ```jsonc
47
48
  { "id": "loading", "…Text": { "text": "$service:products.loadingText",
48
49
  "color": "textSecondary", "collapseWhenEmpty": true } },
49
- { "id": "empty", "…Text": { "text": "$service:products.emptyText",
50
- "color": "textSecondary", "collapseWhenEmpty": true } },
50
+ { "id": "empty",
51
+ "body": { "package": "himalaya", "type": "EmptyState",
52
+ "props": { "icon": "bag", "title": "No products in this store yet",
53
+ "subtitle": "Add a product to see it here.",
54
+ "action": { "actionId": "tier5.products.create" },
55
+ "actionLabel": "Add your first product" } },
56
+ "modifiers": { "hiddenPath": "$service:products.notEmpty" } },
51
57
  { "id": "error", "…Text": { "text": "$service:products.error",
52
58
  "color": "danger", "collapseWhenEmpty": true } },
53
59
  { "id": "retry",
@@ -64,6 +70,13 @@ up loading-and-errored at once.
64
70
  the binding reads without negation.
65
71
  - Ready state is simply the data itself (the list rows, the detail fields) — it
66
72
  needs no dedicated marker.
73
+ - **Use `EmptyState` for every empty state.** It draws one standard layout on
74
+ every platform: icon, title, subtitle and button, with type and gaps from the
75
+ app's tokens. Only `title` is required. `actionLabel` is required when you set
76
+ `action`: there is no default label, and `himi validate` reports a missing one.
77
+ - Write the `EmptyState` title and subtitle as literal copy or `$t:` keys. Today,
78
+ iOS and Android do not resolve a `$service:` binding in those props, so gate the
79
+ node with `hiddenPath` instead of binding its text.
67
80
 
68
81
  ## 3. Which state when
69
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1237.0",
3
+ "version": "0.1239.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -67,5 +67,5 @@
67
67
  "artifactId": "himalaya-cli"
68
68
  }
69
69
  },
70
- "falconPackageHash": "c18693816eefde3cc9259c9dd46e8deaffa629c1762386017a0ea310"
70
+ "falconPackageHash": "7c4036664e3989c5654ed281a7f34ceaa99b28fc6c6747a6d4bcdc06"
71
71
  }