poseui 0.0.3 → 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>>;
@@ -798,6 +801,24 @@ interface PoseElement<TProps extends Record<string, unknown>, TSchema extends St
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
@@ -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,6 +29,25 @@ 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("");
@@ -40,15 +59,17 @@ 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] ?? {};
@@ -85,6 +108,7 @@ function createBuilder(state) {
85
108
  el.input = (schema) => createBuilder({
86
109
  tag: state.tag,
87
110
  classes: state.classes,
111
+ attrs: state.attrs,
88
112
  children: state.children,
89
113
  schema
90
114
  });
@@ -491,6 +515,19 @@ function createBuilder(state) {
491
515
  });
492
516
  }
493
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
+ };
494
531
  el.cls = (value) => typeof value === "function" ? derive([value]) : derive([value]);
495
532
  el.render = async (props, opts) => {
496
533
  const html = render(props);
@@ -504,6 +541,7 @@ function createBuilder(state) {
504
541
  el.child = (value) => createBuilder({
505
542
  ...state,
506
543
  classes: [...state.classes],
544
+ attrs: [...state.attrs],
507
545
  children: [...state.children, value]
508
546
  });
509
547
  return el;
@@ -520,6 +558,7 @@ const pose = { as(tag) {
520
558
  return createBuilder({
521
559
  tag,
522
560
  classes: [],
561
+ attrs: [],
523
562
  children: [],
524
563
  schema: void 0
525
564
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poseui",
3
- "version": "0.0.3",
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>",