poseui 0.0.2 → 0.0.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.
package/dist/index.d.ts CHANGED
@@ -46,6 +46,9 @@ type Child<TProps> = ChildValue | ((props: TProps) => ChildValue);
46
46
  type RenderReturn<TSchema extends StandardSchemaV1 | undefined> = TSchema extends StandardSchemaV1 ? ReturnType<TSchema["~standard"]["validate"]> extends Promise<any> ? Promise<string> : string : string;
47
47
  type CallArgs<TProps extends Record<string, unknown>, TSchema> = TSchema extends StandardSchemaV1 ? [TProps?] : [keyof TProps] extends [never] ? [TProps?] : [TProps];
48
48
  type ClassEntry<TProps> = string | ((props: TProps) => string);
49
+ /** null means the attribute is omitted from the rendered output */
50
+ type AttrValue = string | null;
51
+ type AttrRecord<TProps> = Record<string, Dyn<TProps, AttrValue>>;
49
52
  interface PoseElement<TProps extends Record<string, unknown>, TSchema extends StandardSchemaV1 | undefined = undefined> {
50
53
  (...args: CallArgs<TProps, TSchema>): RenderReturn<TSchema>;
51
54
  readonly classes: ReadonlyArray<ClassEntry<TProps>>;
@@ -782,22 +785,40 @@ interface PoseElement<TProps extends Record<string, unknown>, TSchema extends St
782
785
  * Apply styles conditionally — predicate form or value-switch form.
783
786
  *
784
787
  * Predicate form (apply when true):
785
- * ```ts
788
+ * \`\`\`ts
786
789
  * .when(({ disabled }) => disabled, (b) => b.opacity(50).cursor_not_allowed())
787
- * ```
790
+ * \`\`\`
788
791
  *
789
792
  * Value form (switch on a prop key):
790
- * ```ts
793
+ * \`\`\`ts
791
794
  * .when("variant", {
792
795
  * primary: (b) => b.bg("indigo-600").text_color("white"),
793
796
  * secondary: (b) => b.bg("slate-200").text_color("slate-900"),
794
797
  * })
795
- * ```
798
+ * \`\`\`
796
799
  * Cases are Partial — unmatched values emit no classes.
797
800
  * Multiple .when() calls are independent and all evaluated at render time.
798
801
  */
799
802
  when(pred: (props: TProps) => boolean, apply: (b: PoseElement<TProps, undefined>) => PoseElement<TProps, any>): PoseElement<TProps, TSchema>;
800
803
  when<K extends keyof TProps>(key: K, cases: Partial<Record<TProps[K] & PropertyKey, (b: PoseElement<TProps, undefined>) => PoseElement<TProps, any>>>): PoseElement<TProps, TSchema>;
804
+ /**
805
+ * Set a single HTML attribute. Value can be static or derived from props.
806
+ * Pass `null` to omit the attribute.
807
+ *
808
+ * @example
809
+ * pose.as('a').attr('href', ({ url }) => url).attr('target', '_blank')
810
+ */
811
+ attr(name: string, value: Dyn<TProps, AttrValue>): PoseElement<TProps, TSchema>;
812
+ /**
813
+ * Set multiple HTML attributes at once. Each value can be static or a
814
+ * `(props) => string | null` function. `null` omits the attribute.
815
+ *
816
+ * @example
817
+ * pose.as('input').attrs({ type: 'text', name: ({ field }) => field, required: ({ req }) => req ? '' : null })
818
+ * // or with a props function for multi-field logic:
819
+ * pose.as('a').attrs(({ url, external }) => ({ href: url, target: external ? '_blank' : null }))
820
+ */
821
+ attrs(record: AttrRecord<TProps> | ((props: TProps) => Record<string, AttrValue>)): PoseElement<TProps, TSchema>;
801
822
  /**
802
823
  * Append any raw Tailwind class — static or derived from props.
803
824
  * @example
@@ -808,7 +829,7 @@ interface PoseElement<TProps extends Record<string, unknown>, TSchema extends St
808
829
  child(fn: (props: TProps) => ChildValue): PoseElement<TProps, TSchema>;
809
830
  child(value: ChildValue): PoseElement<TProps, TSchema>;
810
831
  /**
811
- * Render to `{ html, css }` using UnoCSS + presetWind4.
832
+ * Render to \`{ html, css }\` using UnoCSS + presetWind4.
812
833
  * @example
813
834
  * const { html, css } = await card.render({ name: 'Ada' })
814
835
  */
@@ -825,4 +846,4 @@ interface Pose {
825
846
  declare const pose: Pose;
826
847
  declare const div: PoseElement<Record<never, never>, undefined>;
827
848
  //#endregion
828
- export { Child, ChildValue, Dyn, Pose, PoseElement, PoseValidationError, StandardSchemaV1, pose as default, div };
849
+ export { AttrRecord, AttrValue, Child, ChildValue, Dyn, Pose, PoseElement, PoseValidationError, StandardSchemaV1, pose as default, div };
package/dist/index.js CHANGED
@@ -29,26 +29,47 @@ function arbitrary(value) {
29
29
  function resolveClasses(classes, props) {
30
30
  return classes.map((c) => typeof c === "function" ? c(props) : c).filter(Boolean).join(" ");
31
31
  }
32
+ function renderAttrPair(name, value) {
33
+ if (value === null) return "";
34
+ return value === "" ? name : `${name}="${value}"`;
35
+ }
36
+ function resolveAttrs(attrs, props) {
37
+ const parts = [];
38
+ for (const entry of attrs) if (entry[0] === "single") {
39
+ const [, name, value] = entry;
40
+ const rendered = renderAttrPair(name, typeof value === "function" ? value(props) : value);
41
+ if (rendered) parts.push(rendered);
42
+ } else {
43
+ const [, fn] = entry;
44
+ for (const [name, value] of Object.entries(fn(props))) {
45
+ const rendered = renderAttrPair(name, value);
46
+ if (rendered) parts.push(rendered);
47
+ }
48
+ }
49
+ return parts.join(" ");
50
+ }
32
51
  function renderChild(child, props) {
33
52
  if (typeof child === "function") return renderChild(child(props), props);
34
53
  if (Array.isArray(child)) return child.filter((c) => c != null).map((c) => renderChild(c, props)).join("");
35
54
  if (child != null && typeof child === "object" && "__pose" in child) return child(props);
36
55
  return child == null ? "" : String(child);
37
56
  }
38
- /** A tagless builder used inside .when() callbacks — only accumulates classes. */
57
+ /** A tagless builder used inside .when() callbacks — only accumulates classes and children. */
39
58
  function createBlankBuilder() {
40
59
  return createBuilder({
41
60
  tag: "div",
42
61
  classes: [],
62
+ attrs: [],
43
63
  children: [],
44
64
  schema: void 0
45
65
  });
46
66
  }
47
67
  function createBuilder(state) {
48
- function derive(extraClasses = [], extraChildren = []) {
68
+ function derive(extraClasses = [], extraChildren = [], extraAttrs = []) {
49
69
  return createBuilder({
50
70
  ...state,
51
71
  classes: [...state.classes, ...extraClasses],
72
+ attrs: [...state.attrs, ...extraAttrs],
52
73
  children: [...state.children, ...extraChildren]
53
74
  });
54
75
  }
@@ -64,9 +85,11 @@ function createBuilder(state) {
64
85
  }
65
86
  function buildHtml(resolvedProps) {
66
87
  const classStr = resolveClasses(state.classes, resolvedProps);
88
+ const attrsStr = resolveAttrs(state.attrs, resolvedProps);
67
89
  const childrenStr = state.children.map((c) => renderChild(c, resolvedProps)).join("");
68
90
  const classAttr = classStr ? ` class="${classStr}"` : "";
69
- return `<${state.tag}${classAttr}>${childrenStr}</${state.tag}>`;
91
+ const attrsAttr = attrsStr ? ` ${attrsStr}` : "";
92
+ return `<${state.tag}${classAttr}${attrsAttr}>${childrenStr}</${state.tag}>`;
70
93
  }
71
94
  function render(...args) {
72
95
  const props = args[0] ?? {};
@@ -76,6 +99,7 @@ function createBuilder(state) {
76
99
  return buildHtml(result);
77
100
  }
78
101
  render.__pose = true;
102
+ render.__state = state;
79
103
  const el = render;
80
104
  Object.defineProperty(el, "classes", {
81
105
  get: () => state.classes,
@@ -84,6 +108,7 @@ function createBuilder(state) {
84
108
  el.input = (schema) => createBuilder({
85
109
  tag: state.tag,
86
110
  classes: state.classes,
111
+ attrs: state.attrs,
87
112
  children: state.children,
88
113
  schema
89
114
  });
@@ -464,22 +489,45 @@ function createBuilder(state) {
464
489
  el.stroke_w = (n) => dynCls(n, (v) => tw("stroke", v));
465
490
  el.sr_only = () => cls("sr-only");
466
491
  el.not_sr_only = () => cls("not-sr-only");
492
+ function applyBranch(getBranch) {
493
+ const classEntry = (props) => {
494
+ const branch = getBranch(props);
495
+ return branch ? resolveClasses(branch.classes, props) : "";
496
+ };
497
+ const childEntry = (props) => {
498
+ const branch = getBranch(props);
499
+ if (!branch) return null;
500
+ const branchState = branch.__state;
501
+ if (!branchState.children.length) return null;
502
+ return branchState.children.map((c) => typeof c === "function" ? c(props) : c);
503
+ };
504
+ return derive([classEntry], [childEntry]);
505
+ }
467
506
  el.when = (...args) => {
468
507
  if (typeof args[0] === "function") {
469
508
  const [pred, apply] = args;
470
- return derive([(props) => {
471
- if (!pred(props)) return "";
472
- return resolveClasses(apply(createBlankBuilder()).classes, props);
473
- }]);
509
+ return applyBranch((props) => pred(props) ? apply(createBlankBuilder()) : null);
474
510
  } else {
475
511
  const [key, cases] = args;
476
- return derive([(props) => {
512
+ return applyBranch((props) => {
477
513
  const branch = cases[props[key]];
478
- if (!branch) return "";
479
- return resolveClasses(branch(createBlankBuilder()).classes, props);
480
- }]);
514
+ return branch ? branch(createBlankBuilder()) : null;
515
+ });
481
516
  }
482
517
  };
518
+ el.attr = (name, value) => derive([], [], [[
519
+ "single",
520
+ name,
521
+ value
522
+ ]]);
523
+ el.attrs = (recordOrFn) => {
524
+ if (typeof recordOrFn === "function") return derive([], [], [["record", recordOrFn]]);
525
+ return derive([], [], Object.entries(recordOrFn).map(([name, value]) => [
526
+ "single",
527
+ name,
528
+ value
529
+ ]));
530
+ };
483
531
  el.cls = (value) => typeof value === "function" ? derive([value]) : derive([value]);
484
532
  el.render = async (props, opts) => {
485
533
  const html = render(props);
@@ -493,6 +541,7 @@ function createBuilder(state) {
493
541
  el.child = (value) => createBuilder({
494
542
  ...state,
495
543
  classes: [...state.classes],
544
+ attrs: [...state.attrs],
496
545
  children: [...state.children, value]
497
546
  });
498
547
  return el;
@@ -509,6 +558,7 @@ const pose = { as(tag) {
509
558
  return createBuilder({
510
559
  tag,
511
560
  classes: [],
561
+ attrs: [],
512
562
  children: [],
513
563
  schema: void 0
514
564
  });
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "poseui",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Type-safe HTML templating engine on steroids",
5
5
  "license": "MIT",
6
6
  "author": "Ryuz <ryuzer@proton.me>",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/guarana-studio/poseui.git"
9
+ "url": "git+https://github.com/guarana-studio/pose.git"
10
10
  },
11
11
  "files": [
12
12
  "dist"